WINCE驅動程式編寫者指南在CE中,最簡單的一個驅動程式莫過於一個內建(Built-in)裝置的流介面驅動。對於一個不支援熱拔插的裝置,最快捷的方法就是為其實現一個內建的流介面的驅動。對於這樣一類驅動程式,我們只需要按一種特定的規則實現一個動態庫,其中實現對所有的硬體功能的調用,再將這個動態庫加入系統中,然後設定相關的登錄機碼,使得在系統啟動時裝置管理員能識別並且載入這個裝置即可。
1 .實現動態連結程式庫
此動態連結程式庫與應用程式層所用的庫並不很大差別,源檔案可以是C、C++、甚至彙編,,只是它要實現以下函數。
DllEntry(HINSTANCE DllInstance, INT Reason, LPVOID Reserved )
這個函數是動態連結程式庫的入口,每個動態連結程式庫都需要輸出這個函數,它只在動態庫被載入和卸載時被調用,也就是裝置管理員調用LoadLibrary而引起它被裝入記憶體和調用UnloadLibrary將其從記憶體釋放時被調用, 因而它是每個動態連結程式庫最早被調用的函數,一般用它做一些全域變數的初始化。
參數:
DllInstance:DLL的控制代碼,與一個EXE檔案的控制代碼功能類似,一般可以通過它在得到DLL中的一些資源,例如對話方塊,除此之外一般沒什麼用處。
Reason:
一般我們只關心兩個值:DLL_PROCESS_ATTACH與DLL_PROCESS_DETACH,Reason等於前者是動態庫被載入,等於後者是動態庫被釋放。
所以,我們可以在Reason等於前者是初始化一些資源,等於後者時將其釋放。
DWORD XXX_Init( LPCTSTR pContext, LPCVOID lpvBusContext);
它是驅動程式的動態庫被成功裝載以後第一個被調用的函數。其調用時間僅次與DllEntry,而且,當一個庫用來產生多於一個的驅動程式執行個體時僅調用一次DllEntry,而xxx_Init會被調用多次。驅動程式應當在這個函數中初始化硬體,如果初始化成功,就分配一個自已的記憶體空間(通常用結構體表示),將自已的狀態儲存起來,並且將此記憶體塊的地址做為一個DWORD值返回給上層。裝置管理員就會用在調用XXX_Open時將此控制代碼傳回,我們就能訪問自已的狀態。如果初始化失敗,則返回0以通知這個驅動程式沒有成功載入,先前所分配的系統資源應該全部釋放,此程式的生命即告終至。
當這個函數成功返回,裝置管理員對這個程式就不做進一步處理,除非它設定了更多的特性。至此一個各為XXX的裝置就已經載入成功,當使用者程式調用CreateFile來開啟這個裝置時,裝置管理員就會調XXX_Open函數。
參數:
pContext:系統傳入的註冊表鍵,通過它可以講到我們在註冊表中設定的配置資訊。
lpvBusContext:一般不用,在這先不做講解
實際上,很多程式中將這個函數寫成了DWORD XXX_Init( DWORD pContext )
我們只需要將pContext轉化成LPCTSTR即可。
DWORD XXX_Open(DWORD hDeviceContext,DWORD dwAccess, DWORD dwShareMode);
當使用者程式調用CreateFile開啟這個裝置時,裝置管理員就會調用此驅動程式的XXX_Open函數。
參數:
hDeviceContext XXX_Init 返回給上層的值,也就是我們在XXX_Init中分配的用來記錄驅動程式資訊的那個結構體的指標,我們可以在這個函數中直接將其轉化成所定義的結構,從而擷取驅動程式的資訊。
dwAccess 上層所要求的訪問方式,可以是讀或者寫,或者是0,即不讀也不寫
dwShareMode 上層程式所請求的共用模式,可以是共用讀、共用寫這兩個值的邏輯或,或者是0,即獨佔式訪問。
系統層對裝置檔案的存取許可權及共用方法已經做了處理,所以在驅動程式中對這兩個參數一般可以不用理會。
這個函數一般不用做太多處理,可以直接返回hDeviceContext表示成功,對於一個不支援多個使用者的硬體,在裝置已經開啟後,應該總是返回0以至失敗,則CreateFile調用不成功。
DWORD XXX_Close( DWORD hDeviceContext );
當使用者程式調用CloseHandle關閉這個裝置控制代碼時,這個函數就會被裝置管理員調用。
參數:
hDeviceContext 為XXX_Open返回給上層的那個值。
這個函數應該做與XXX_Open相反的事情,具體包括:釋放XXX_Open分配的記憶體,將驅動程式被開啟的記數減少等。
DWORD XXX_Deinit( DWORD hDeviceContext );
這個函數在裝置被卸載時被調用,它應該實現與XXX_Init相反的操作,主要為釋放前者佔用的所有系統資源。
參數:
hDeviceContext XXX_Init函數返回給上層的那個控制代碼
void XXX_PowerUp( DWORD hDeviceContext );
void XXX_PowerDown(DWORD hDeviceContext );
正如其名稱中體現的那樣,這兩個函數在系統PowerUp與PowerDown時被調用,這兩個函數中不能使用任何可能引起線程切換的函數,否則會引起系統死機。所以,在這兩個函數中,實際上幾乎是什麼做不了,一般在PowerDown時做一個標誌,讓驅動程式知道自已曾經被Power Down過。在Power Down/On的過程中硬體可能會掉電,所以,儘管Power On以後,原來的IO操作仍然會從接著執行,但可能會失敗。這時,當我們發現一次IO操作失敗是因為程式曾經進入過Power Down狀態,就重新初始化一次硬體,再做同樣的IO操作。
BOOL XXX_IOControl(
DWORD hDeviceContext,
DWORD dwCode,
PBYTE pBufIn,
DWORD dwLenIn,
PBYTE pBufOut,
DWORD dwLenOut,
PDWORD pdwActualOut
);
幾乎可以說一個驅動程式的所有功能都可以在這個函數中實現。對於一類CE自身已經支援的裝置,它們已經被定義了一套IO操作定,我們只需按照各類裝置已經定義的內容去實現所有的IO操作。但當我們實現一個自訂的裝置時,我們就可以隨心所欲定義我們自已的IO操作。
參數:
hDeviceContext XXX_Open返回給上層的那個控制代碼,即我們自已定義的,用來存放程式所有資訊的一個結構。
dwCode IO作業碼,如果是CE已經支援的裝置類,就用它已經定義好碼值,否則就可以自已定義。
pBufIn 傳入的Buffer,每個IO作業碼都會定義自已的Buffer結構
dwLenIn pBufIn以位元組記的大小
pBufOut,dwLenOut分別為傳出的Buffer,及其以位元組記的大小
pdwActualOut 驅動程式實際在pBufOut中填入的資料以位元組記的大小
其中,前兩個參數是必須的,其它的任何一個都有可能是NULL或0。
所以,當給pdwActualOut賦值時應該先判斷它是否為一個有效地址
3.2註冊表設定
在註冊表中添加如下項目。(一般放在Platform.reg)
[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SampleDev]
"Prefix"="XXX"
"Dll"="MyDev.Dll"
"Order"=dword:1
補充說明:
第一條語句:[HKEY_LOCAL_MACHINE\Drivers\BuiltIn\SampleDev]
SampleDev 所建立的驅動目錄名
第二條語句:"Prefix"="xxx"
xxx 表示裝置驅動名稱,也就是在書寫應用程式運用creatfile函數,開啟的檔案名稱就是xxx1,要記得在後頭加個1,這是在wince下面為了區分多個驅動時的索引區分。
第三條語句:MyDev.dll
表示Dll檔案,就是叫做MyDev.dll,而這個檔案在platform.bib檔案中已經被建立了
第四條語句:Order 表示載入DLL的優先順序,最小的Order值將優先被載入
在BIB檔案中添加項目,將所用到的檔案加入BIN檔案(一般放在Platform.bib)。
MyDev.dll $(_FLATRELEASEDIR)\MyDev.dll NK SH
註:
SampleDev為任意與其它項目不重名的字串.
每個函數名的首碼XXX可以是任意大寫的字串,只要保證與註冊表中Prefix後面的值相同就行。
3.3編譯器
現在,已經知道了需要實現哪些東西,一定想知道如何去實現它。一個最直接的方法就是在platform/BSP/drivers 下建立一個目錄,然後在drivers目錄中的dirs檔案中加入以你剛建立的目錄名。
在剛建立的目錄下,建立你的C原始碼檔案,在其中實現上面所述的函數,及其功能。建立名稱分別為sources, makefile, mydev.def的檔案。其內容如下:
makefile: 只需要這樣一行
!INCLUDE $(_MAKEENVROOT)\makefile.def
mydriver.def檔案定義需要輸出的函數,這些函數能夠被其它代碼用動態載入的方法調用。格式:
LIBRARY MyDev(這個字串要和將要產生的動態庫的檔案名稱一樣)
EXPORTS
XXX_Init
XXX_Deinit
XXX_Open
XXX_Close
XXX_PowerOff
XXX_Power_Down
XXX_IOControl
Sources:這個檔案很重要,內容也多,最基本的一個檔案該有如下內容。補充說明:
MDD一般在編譯時間會產LIB庫檔案(可以在SOURCES檔案中看到),在PDD層通過SOURCES 檔案將MDD產生的LIB庫關聯進來.PDD編譯後產生一個DLL檔案(這個DLL檔案包括LIB檔案在裡面),應用在用這個驅動時可以動態將它載入.而MDD為上層提供的函數是通過DEF檔案匯出的.TARGETNAME= MyDev(指定要產生的動態庫的名稱)
TARGETTYPE=DYNLINK(指定要產生的是一個動態庫)
(下面兩項指定需要與哪些動態庫連結,一般要第一項就足夠了)
TARGETLIBS=$(_COMMONSDKROOT)\lib\$(_CPUINDPATH)\coredll.lib
SOURCELIBS= $(_COMMONOAKROOT)\lib\$(_CPUINDPATH)\ceddk.lib
DEFFILE=MyDev.def (指定def檔案)
DLLENTRY=DllEntry(指定動態庫的入口函數)
SOURCES=(請在這寫上你所有源檔案的名字,它們將會被編譯)
好了,現在萬事俱備,只剩編譯啦。
3.4用一個Project檔案來編譯出驅動程式庫檔案
如果您在用CE5.0,那用一個Project來構造一個驅動程式將是一個不錯的選擇。即在建立一個Project時設定其類型為DLL,其它設定根據提示即可。並且可以將註冊表設定放在Project所在檔案夾。
3.5基本調試方法
一般驅動程式可以用DEBUG版來調試,也可以用輸出調試資訊的方法。我們一般用這兩個函數輸出調試資訊:RETAILMSG和DEBUGMS,後者只能在DEBUG版中輸出,而前者在RELEASE和DEBUG版中都可以輸出,而且,可以在系統運行時刻根據Debug Zone選擇讓DEBUGMSG輸出哪些調試資訊。
驅動程式的調試一般可以分為以下幾步:
1.看驅動程式的DllEntry是否被調用。如果這個函數被調用,說明驅動程式的檔案已經在CE的image中,而且與註冊表中設定的檔案名稱相同。
2.看Init 函數是否被調用。如果它被調用,剛說明註冊表設定正確。如果它沒有被調用,一般是因為註冊表中的Prefix設定與Init函數前面那三個字元不相同。或者def檔案中沒有定義Init函數。如果這個函數能夠被調用,但驅動程式還是不能正確載入,請詳細檢查代碼。