.NET Micro Framework動態調用C/C++底層代碼(原理篇)

來源:互聯網
上載者:User

.NET Micro Framework和WinCE系統不同,從應用開發角度來說,僅支援C#開發(從V4.2版本開始,才支援VB.NET開發),而不像WinCE應用開發,既可以用C#/VB.Net,也可以用EVC等工具進行C/C++開發。針對.NET Micro Framework平台由於C#等.NET語言是Managed 程式碼,系統需要對中繼語言進行解釋執行,所以運行效率上和原生的C/C++相比,效率是打了一個折扣的,這樣對一些即時性要求比較高的應用來說,是很難實現的。

如果非要用.NET Micro Framework開發一些即時性高的應用,通常的做法就是從底層移植(Porting kit)入手,專門用C/C++寫一個驅動,然後再封裝一個可供C#調用的介面,以供應用開發人員調用(參見《Micro Framework Interop功能實現》)。但是這種方法,必須要熟悉.NET Micro Framework系統移植,另外手頭還必須有一套系統源碼,不僅需要熟悉C/C++,還需要熟悉C#,以需上下結合,完成相關功能。從這個角度來說,對普通開發人員來說太苛刻了,不僅對技術能力要求高,開發週期長,並且還需要重新編譯韌體,對原有系統進行升級。

在幾年前,我就一直考慮能否採用Windows或WinCE平台的dll動態調用的思路,來實現.NET Micro Framework動態調用C/C++代碼。所以後續也看了不少PE檔案結構的文章,還有一些編譯原理的書籍,但是由於自己知識儲備不夠,再加上該技術實現難度也比較高,一直不得其門。

在.NET Micro Framework系統移植和開發過程中,深深感受到,封裝一個專有的硬體驅動介面是一件比較麻煩的事,所以受WinCE平台上流式驅動開發(可以參見我以前寫的《我的第一個WINCE驅動》)的啟迪,我封裝了一套基於.NET Micro Framework的流式介面(目前基於這個介面,我已經開發了DHT11溫濕度模組、超聲波模組和看門狗的驅動程式,後續將發博文一一介紹),其C#語言的介面如下:

public sealed class GeneralStream                   {        public GeneralStream();        public event GeneralStreamEventHandler Notice;        public int Close();        public int IOControl(int code);        public int IOControl(int code, int parameter);        public int IOControl(int code, byte[] inBuffer, int inCount, byte[] outBuffer, int outCount);        public int Open(string name);        public int Open(string name, int config);        public int Open(string name, string config);        public int Read(byte[] buffer, int offset, int count);        public int Write(byte[] buffer, int offset, int count);    }

比較有特色的是,還提供了一個事件通知介面,這樣就為各種硬體驅動開發提供了更靈活的支援。

有了這個流式介面,一般情況下,為上層C#語言提供專有的硬體底層功能,就不需要再編寫介面相關的代碼了,直接寫相關的C/C++代碼,然後編譯連結即可。

由此,我突然想到,能否把基於流式介面開發的驅動,實現動態載入。

最初我開發的流式介面和各個流式驅動是在一個項目裡面的,最終會編譯成一個obj檔案,後來考慮到便於調試和維護,把流式介面部分和各個流式驅動分開,每一個流式驅動都是一個單獨的obj檔案。

又受到ER_Config和ER_DAT的啟發,在編譯的時候,它們可以指定編譯的起始位置,並且可以獨立編譯成一個bin(或hex)檔案,所以我們的一個流式驅動也是可以獨立的編譯成一個bin檔案的,這樣部署的時候就可以單獨部署了。

由於每個流式驅動的介面都是一致的,我們自然就可以想到,這個bin檔案理論上是可以替換的,比如剛開始我們載入的是A功能的流式驅動,那麼我們根據需要也可以替換為B功能的流式驅動。

那使用者怎麼開發這種相對獨立的流式驅動模組呢?

如果還是基於.NET Micro Framework整個Porting kit開發環境,那對一般開發人員來說,簡直就是夢魘,因為光搭建環境,熟悉環境就得花很長時間。

所以最好的辦法,就是用MDK開發環境,並且可以基於一個簡單的流式驅動模組工程來開發驅動。

先等一下,我們暫且先不要考慮如何搭建MDK開發環境,讓我們理一下思路,即使我們解決了開發和編譯問題,但是最重要的是——需要解決流式驅動模組如何和宿主(也就是TinyCLR)進行互動的問題。

這裡面有兩個問題需要解決,一是宿主如何擷取流式驅動模組的介面地址?二是流式驅動模組如何訪問宿主的資源(我們在windows或wince平台就是通過所謂的API介面,訪問系統資源的)。

第一個問題,看似簡單,但是實現起來我走了不少彎路。

首先,我們很容易想到,我們把流式驅動函數介面的指標儲存到一個變數中去,如下面的代碼所示:

const IGeneralStream g_GeneralStream_UserDriver  ={         &GeneralStream_Open1_UserDriver,         &GeneralStream_Open2_UserDriver,            &GeneralStream_Close_UserDriver,              &GeneralStream_IOControl1_UserDriver,          &GeneralStream_IOControl2_UserDriver,          &GeneralStream_Read_UserDriver,         &GeneralStream_Write_UserDriver,     };

我們只要知道g_GeneralStream_UserDriver的地址,就知道各個函數介面的地址了,換句話說,我們編譯的時候,其實可以指定g_GeneralStream_UserDriver變數的地址的。但是問題來了,如果我編譯的時候指定g_GeneralStream_UserDriver變數的地址,我們就無法固定流式驅動模組編譯的起始地址,這樣我們就不知道這個編譯好的bin檔案該部署到什麼位置。另外g_GeneralStream_UserDriver變數也無法保持在和bin檔案一個相對確定的位置上去(這和實際代碼的多寡都有關係),所以解決這個問題我還是頗費周折的(如果大家有更好的方法確定g_GeneralStream_UserDriver地址的方法,可以交流一下)。

第一個問題,我們算解決了,我們實現了宿主載入和調用流式驅動介面。

第二個問題,我最初的做法是絕對位置,先根據系統函數的原型聲明一個函數指標,然後根據編譯後的map檔案,查到這個函數的絕對位址,做一個轉換。如下面的代碼:

typedef  void (*MF_lcd_printf)(char const * format,...);#define lcd_printf   ((MF_lcd_printf)0x0805ab73)

經過這一步後,我們就可以在流式驅動介面裡直接調用這個系統函數了。但是這樣做有一個明顯的問題,就是一旦系統韌體升級(需重新編譯),那麼這些絕對的地址,可能會發生變化。一旦有變化,這對流式驅動來說是致命的,不僅調用失敗,並且非常可能導致系統掛起(如果是windows系統,此時就是藍屏了)。

所以,我採用了另一種方式,和流式驅動提供流式驅動介面的方式一樣,系統的API介面,也定義在一個變數中,如下面的代碼所示:

const IGeneralStream_Function g_GeneralStream_Function  ={             &Notice_GenerateEvent,           &lcd_printf,           &debug_printf,             &HAL_Time_Sleep_MicroSeconds_InterruptEnabled,             &CPU_GPIO_DisablePin,            &CPU_GPIO_EnableInputPin,            &CPU_GPIO_EnableOutputPin,           &CPU_GPIO_GetPinState,            &CPU_GPIO_SetPinState,            &CPU_TIMER_Initialize,            &CPU_TIMER_Uninitialize,           &CPU_TIMER_Start,           &CPU_TIMER_Stop,           &CPU_TIMER_GetState,           &CPU_TIMER_SetState,};

宿主調用流式驅動介面的時候,把這個g_GeneralStream_Function地址直接傳個流式驅動即可。這種方法的優點是,不受系統韌體的升級影響,但是缺點也很明顯,就是系統給你提供了什麼介面,你才能用什麼介面(其實這個時候,第一種方法仍然有效,不過可以算作駭客的做法了)。

另外值得一提的是,由於這是驅動層面的開發,驅動程式理論上可以訪問系統的任何資源,所以驅動程式一定儘可能在預先為自己規劃好的代碼區和RAM區工作,以免對系統的穩定性造成影響。

以上思路僅僅是一個初步,我們完全也可以像PE檔案一樣,為流式驅動程式加上一個類PE頭,把匯出的函數指標和需要引用的系統API指標等等資源,填寫到類PE頭上去。這樣系統就可以根據PE頭資訊,自動載入各種流式驅動,以供上層應用程式調用。

下面一張是MDK開發流式驅動的情境,至於如何具體編寫流式驅動和使用,後續我會專門寫一篇.NET Micro Framework動態載入C/C++代碼的應用篇。 

 MF簡介:http://blog.csdn.net/yefanqiu/article/details/5711770

MF資料:http://www.sky-walker.com.cn/News.asp?Id=25

 

 

 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.