.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