WinCE下所有的驅動都是以DLL的形式,被device.exe進程載入的,所以每個驅動程式中都要實現DllEntry函數。
在註冊表的HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\鍵下儲存了USB Host的驅動程式資訊。當我們第一次插入USB裝置時。因為不存在這樣的資訊,所以系統會彈出一個“
未能識別的USB裝置”的對話方塊,要求使用者輸入驅動程式的名稱。該名稱就是USB Host驅動DLL的檔案名稱。在輸入了名稱後,系統會自動調用該DLL的USBInstallDriver函數。該函數
負責向註冊表添加USB Host驅動的資訊,以便再次插入裝置時,能夠識別該USB裝置。其原型如下:
BOOL USBInstallDriver(LPCWSTR szDriverLibFile);
其中szDriverLibFile就是輸入的DLL檔案名稱。返回TRUE表示註冊成功。
在向註冊表註冊USB Host資訊時,不能使用普通的註冊表函數,只能使用USBD提供的註冊函數。
BOOL RegisterClientDriverID(LPCWSTR szUniqueDriverId);
BOOL RegisterClientSettings(LPCWSTR szDriverLibFile, LPCWSTR szUniqueDriverId, LPCWSTR erved, LPCUSB_DRIVER_SETTINGS lpDriverSettings);
這兩個函數在USBD.DLL中,可以通過動態方式調用,也可以通過靜態方式調用。
動態方式如下:
HINSTANCE hInst = LoadLibrary(L"USBD.DLL");
if(hInst) {
LPREGISTER_CLIENT_DRIVER_ID lpRegisterClientId =
(LPREGISTER_CLIENT_DRIVER_ID)GetProcAddress(
hInst,
L"RegisterClientDriverID");
if(!lpRegisterClientId)
return FALSE;
LPREGISTER_CLIENT_SETTINGS lpRegisterClientSetting =
(LPREGISTER_CLIENT_SETTINGS)GetProcAddress(
hInst,
L"RegisterClientSettings");
if(!lpRegisterClientSetting)
return FALSE;
else
return FALSE;
此後,就可以通過lpRegisterClientId和lpRegisterClientSetting函數指標調用這些函數,最後記得要FreeLibrary。
靜態方式:
在.cpp源檔案中加入
#pragma comment(lib,"usbd.lib")
並在source檔案的TARGETLIBS變數中加入$(_SYSGENOAKROOT)\lib\$(_CPUINDPATH)\usbd.lib
如此一來,就可以直接使用這兩個函數了。
1) BOOL RegisterClientDriverID(LPCWSTR szUniqueDriverId)
該函數註冊USB Host驅動程式的ID。
2) BOOL RegisterClientSettings(LPCWSTR szDriverLibFile, LPCWSTR szUniqueDriverId, LPCWSTR erved, LPCUSB_DRIVER_SETTINGS lpDriverSettings)
該函數負責註冊驅動程式的資訊。
szDriverLibFile 設定為USBInstallDriver函數傳入的DLL驅動程式名稱。
szUniqueDriverId 設定為調用RegisterClientDriverID註冊的驅動程式ID。
erved 設定為NULL
lpDriverSettings 該參數是一個USB_DRIVER_SETTINGS結構體。其聲明如下:
typedef struct {
DWORD dwCount;
DWORD dwVendorId;
DWORD dwProductId;
DWORD dwReleaseNumber;
DWORD dwDeviceClass;
DWORD dwDeviceSubClass;
DWORD dwDeviceProtocol;
DWORD dwInterfaceClass;
DWORD dwInterfaceSubClass;
DWORD dwInterfaceProtocol;
} USB_DRIVER_SETTINGS;
Count為結構體大小,其他項對應USB描述符。
其中除Count外的各欄位,如果不設定具體的值,可以設定為USB_NO_INFO。
這個結構體中的資訊講反應到註冊表的HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\鍵下,用於在USB裝置插入時,尋找USB驅動。下面以一個例子說明:
BOOL USBInstallDriver(LPCWSTR szDriverLibFile)
{
RETAILMSG(1,(TEXT("USBInstallDriver\r\n")));
RETAILMSG(1,(TEXT("USBInstallDriver:%s\r\n"), szDriverLibFile));
BOOL fRet = FALSE;
USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings);
DriverSettings.dwVendorId = 0x10C4;
DriverSettings.dwProductId = 0x0003;
DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO;
DriverSettings.dwDeviceSubClass = USB_NO_INFO;
DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0;
DriverSettings.dwInterfaceSubClass = 0;
DriverSettings.dwInterfaceProtocol = 0;
fRet = RegisterClientDriverID(L"USBTest");
if (fRet) {
fRet = RegisterClientSettings(
szDriverLibFile,
L"USBTest",
NULL,
&DriverSettings);
if(!fRet)
RETAILMSG(1,(TEXT("RegisterClientSettings error\r\n")));
} else
RETAILMSG(1,(TEXT("RegisterClientDriverID error\r\n")));
return fRet;
}
在WinCE中,將設定資訊分為了三組,每組3個值,
第一組:
dwVendorId、dwProductId、dwReleaseNumber
第二組:
dwDeviceClass、dwDeviceSubClass、dwDeviceProtocol
第三組:
dwInterfaceClass、dwInterfaceSubClass、dwInterfaceProtocol
如果註冊成功,將會在HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\鍵下出現 “第一組\第二組\第三組\註冊ID\DLL”這樣的建,索引值為DLL驅動名稱。其中每組又是由三個值中間加底線組成。如果有一個值設定為USB_NO_INFO,則鍵名不包括該值。如果整個組中每個值都設定成USB_NO_INFO,則鍵名為Default。
據上面的例子,在我的系統下,將會產生如下鍵名:
HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\4292_3\Default\0_0_0\USBTest\DLL = "MyUSBTest" (我的驅動程式為MyUSBTest.dll)
當使用者插入USB裝置時,它會讀取USB裝置的描述符,根據描述符中的值在註冊表中尋找驅動程式名稱。
現在假設,我們要WinCE只支援USB鍵盤,另外我們自己實現一個USB滑鼠驅動程式。如果不加註意,我們的USB滑鼠驅動程式將不能被調用。原因正是在於這個尋找USB裝置驅動的過程。WINCE提供的USBHID驅動程式的註冊表資訊是
HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\Default\Default\3\Hid_Class\DLL = "USBHID.DLL"
其中第三組資訊只使用了dwInterfaceClass,而USB鍵盤和USB滑鼠只有dwInterfaceProtocol不同。所以,一個3概括了所有的HID,當我們的USB滑鼠插入系統後,將會調用USBHID.DLL驅動程式處理,但是它只包括鍵盤的驅動,沒有滑鼠的驅動,所以滑鼠不能使用。要想使得自訂的USB滑鼠可以使用,則將第三組的值都設定上,如下:
HKEY_LOCAL_MACHINE\Drivers\USB\LoadClients\Default\Default\3_1_1\Hid_Class\DLL = "USBHID.DLL"
如此一來,當值為3_1_2的滑鼠插入後,因為找不到對應的索引值,將提示要求我們輸入USB滑鼠的驅動
當使用者需要卸載USB Host裝置驅動時,將會調用USBUnInstallDriver函數
BOOL USBUnInstallDriver();
它與USBInstallDriver類似,不過是調用如下兩個函數
UnRegisterClientSettings
BOOL UnRegisterClientSettings(LPCWSTR szUniqueDriverId, LPCWSTR szReserved, LPCUSB_DRIVER_SETTINGS lpDriverSettings);
BOOL UnRegisterClientDriverID(LPCWSTR szUniqueDriverId);
其中szUniqueDriverId是註冊時,使用的ID,szReserved保留,故設定為NULL,lpDriverSettings則是驅動程式設定資訊。
常式如下:
BOOL USBUnInstallDriver()
{
RETAILMSG(1,(TEXT("USBUninstallDriver\r\n")));
BOOL fRet = FALSE;
USB_DRIVER_SETTINGS DriverSettings;
DriverSettings.dwCount = sizeof(DriverSettings);
DriverSettings.dwVendorId = 0x10C4;
DriverSettings.dwProductId = 0x0003;
DriverSettings.dwReleaseNumber = USB_NO_INFO;
DriverSettings.dwDeviceClass = USB_NO_INFO;
DriverSettings.dwDeviceSubClass = USB_NO_INFO;
DriverSettings.dwDeviceProtocol = USB_NO_INFO;
DriverSettings.dwInterfaceClass = 0;
DriverSettings.dwInterfaceSubClass = 0;
DriverSettings.dwInterfaceProtocol = 0;
fRet = UnRegisterClientSettings(L"USBTest", NULL, &DriverSettings);
if(fRet) {
fRet = UnRegisterClientDriverID(L"USBTest");
if(!fRet)
RETAILMSG(1,(TEXT("UnRegisterClientDriverID error\r\n")));
} else
RETAILMSG(1,(TEXT("UnRegisterClientSettings error\r\n")));
return fRet;
}
其中DriverSettings必須與USBInstallDriver的DriverSettings一致。
回到原來的流程,WinCE註冊表中已經包含了驅動資訊,WinCE系統自動尋找註冊表,在找到裝置對應索引值的DLL後,將會調用該DLL的USBDeviceAttach函數。
BOOL USBDeviceAttach(
USB_HANDLE hDevice,
LPCUSB_FUNCS lpUsbFuncs,
LPCUSB_INTERFACE lpInterface,
LPCWSTR szUniqueDriverId,
LPBOOL fAcceptControl,
DWORD dwUnused)
hDevice 裝置控制代碼,操作USB裝置時,需要使用該控制代碼
lpUsbFuncs 指向一個包含各種USB操作的函數指標
lpInterface USB介面資訊,這裡需要注意的是,如果在DriverSettings裡dwInterfaceClass、dwInterfaceSubClass、dwInterfaceProtocol設定為USB_NO_INFO,則該指標為NULL
szUniqueDriverId 註冊裝置ID
fAcceptControl 該值被賦值為TRUE,表示該驅動能操作該裝置。如果不能操作該裝置,則“未能識別的USB裝置”對話方塊會再次出現,要求使用者輸入驅動程式名稱
dwUnused 未使用
在該函數內,主要是做一些檢查,判斷是否能驅動裝置,還有就是註冊USB事件通知回呼函數,以及啟用流驅動。對於檢查部分,這裡不再詳細說明。
首先,介紹一下啟用流驅動。
流驅動為應用程式提供了一個訪問裝置的介面,利用該介面可以像訪問檔案一樣訪問裝置。USB裝置同樣可以使用該介面來為應用程式提供支援。在註冊表的
HKEY_LOCAL_MACHINE\Drivers\BuiltIn鍵下,儲存了各種WinCE內建流驅動程式的入口。這些驅動通過device.exe在系統啟動時被啟用。像USB這樣的裝置,只有插入時,才存在流
驅動介面,所以我們需要手動啟用流驅動。啟用流驅動的函數是:
HANDLE ActivateDevice(LPCWSTR lpszDevKey, DWORD dwClientInfo);
lpszDevKey 字串指明了流驅動所在註冊表的鍵。獲悉流驅動的人都知道,流驅動在註冊表中必須包含兩個鍵Prefix和Dll。
流驅動中所有介面函數都有類似XXX_的首碼,而這個Prefix則指明XXX對應的字串,如Prefix為COM,則流驅動包含如COM_Open、COM_Close、COM_Write、COM_Read這樣介面函數。Dll則說明了這些函數所在的動態連結程式庫。
在我的例子中存在如下的註冊表鍵:
[HKEY_LOCAL_MACHINE\Drivers\USB\ClientDrivers\USBTest]
"Prefix"="TST"
"Dll"="MyUSBTest.dll"
通過dwClientInfo,可以把參數間接傳給驅動的XXX_init。我們可以把hDevice、lpUsbFuncs、lpInterface這樣資訊放置在一個結構體中,通過該函數傳遞給流驅動使用。
USB通知回呼函數,可以用來判斷各種USB事件的發生,如USB拔出。當發生事件後,系統會根據註冊的回呼函數做相應的處理,在USB裝置拔出後,所要做的事情,就是卸載流驅動,並釋放佔用的各種資源。
註冊回呼函數是一個包含在lpUsbFuncs中的函數指標:
LPUN_REGISTER_NOTIFICATION_ROUTINE lpUnRegisterNotificationRoutine
該函數的聲明如下:
typedef BOOL (* LPREGISTER_NOTIFICATION_ROUTINE)(
USB_HANDLE hDevice,
LPDEVICE_NOTIFY_ROUTINE lpNotifyRoutine,
LPVOID lpvNotifyParameter
);
hDevice 裝置控制代碼
lpNotifyRoutine 回呼函數
lpvNotifyParameter 傳遞給回呼函數的參數
在回呼函數中卸載流驅動使用
BOOL DeactivateDevice(HANDLE hDevice);
其中,hDevice 傳入ActivateDevice時返回的控制代碼。
下面是具體的樣本:
typedef struct {
DWORD dwSize;
USB_HANDLE hDevice,
LPCUSB_FUNCS lpUsbFuncs,
LPCUSB_INTERFACE lpInterface,
HANDLE hStreamDevice;
} TESTUSBINFO, PTESTUSBINFO;
//回呼函數
extern "C" BOOL USBDeviceNotifications(
LPVOID lpvNotifyParameter,
DWORD dwCode,
LPDWORD *dwInfo1,
LPDWORD *dwInfo2,
LPDWORD *dwInfo3,
LPDWORD *dwInfo4)
{
if (dwCode == USB_CLOSE_DEVICE) {
PTESTUSBINFO pDrv = (PDRVCONTEXT) lpvNotifyParameter;
DeactivateDevice(pDrv->hStreamDevice); //卸載流驅動
LocalFree(pDrv); //釋放資源
}
RETAILMSG(1,(TEXT("Free Driver Resources!\r\n")));
return TRUE;
}
BOOL USBDeviceAttach(
USB_HANDLE hDevice,
LPCUSB_FUNCS lpUsbFuncs,
LPCUSB_INTERFACE lpInterface,
LPCWSTR szUniqueDriverId,
LPBOOL fAcceptControl,
DWORD dwUnused)
{
RETAILMSG(1,(TEXT("USBDeviceAttach\r\n")));
*fAcceptControl = FALSE;
//顯示USB裝置的一些資訊
if(lpInterface != NULL) {
RETAILMSG(1,(TEXT("usbserialhost: DeviceAttach, IF %u, #EP:%u, Class:%u, Sub:%u, Prot:%u\r\n"),
lpInterface->Descriptor.bInterfaceNumber,
lpInterface->Descriptor.bNumEndpoints,
lpInterface->Descriptor.bInterfaceClass,
lpInterface->Descriptor.bInterfaceSubClass,
lpInterface->Descriptor.bInterfaceProtocol));
RETAILMSG(1,(TEXT("Endpoint 1:%u\r\n"),
lpInterface->lpEndpoints[0].Descriptor.bmAttributes));
RETAILMSG(1,(TEXT("Endpoint 2:%u\r\n"),
lpInterface->lpEndpoints[1].Descriptor.bmAttributes));
RETAILMSG(1,(TEXT("Endpoint 3:%u\r\n"),
lpInterface->lpEndpoints[2].Descriptor.bmAttributes));
}
LPCUSB_DEVICE lpUsbDev = (lpUsbFuncs->lpGetDeviceInfo)(hDevice);
if(!lpUsbDev)
{
RETAILMSG(1,(TEXT("Unable to get USB device!\r\n")));
return FALSE;
}
//儲存必要的資訊供驅動程式其他部分使用
PTESTUSBINFO pDrv = (PTESTUSBINFO)LocalAlloc (LPTR, sizeof (PTESTUSBINFO));
pDrv->dwSize = sizeof (DRVCONTEXT);
pDrv->hDevice = hDevice;
pDrv->lpUsbFuncs = lpUsbFuncs;
pDrv->lpInterface = lpInterface;
//啟用流驅動
pDrv->hStreamDevice = ActivateDevice (L"Drivers\\USB\\ClientDrivers\\USBTest", (DWORD)pDrv);
if (pDrv->hStreamDevice) {
//註冊回呼函數
(*lpUsbFuncs->lpRegisterNotificationRoutine)(
hDevice,
USBDeviceNotifications,
pDrv);
} else {
RETAILMSG(1, (TEXT("Can't activate stream device! rc=%d\r\n"), GetLastError()));
LocalFree(pDrv);
return FALSE;
}
//驅動可以操作該裝置
*fAcceptControl = TRUE;
return TRUE;
}
至此,USB Host端裝置驅動程式所必須實現的功能都已經實現。並且和流驅動相串連。應用程式已經可以使用流驅動的介面來操作USB裝置了。