WriteFile(), GetLastError(), 87

調試了好長時間,終於發現這個 87的用法錯誤的妙處! 凡是格式不符合設備接收協議的 都應該是這個返回值。 剛開始我可能湊巧沒有初始化的時候,他自己偶然自動這樣, 所以,有時候可以僥倖過關。 不錯,記錄之,以供後人參考。 以下轉載自: 說明: - 以下結論都是基於Windows XP系統所得出的,不保證在其他系統的適用性。 - 在此討論的是HID自定義設備,對於標準設備,譬如USB鼠標和鍵盤,由於操縱系統對其獨佔,很多操縱未必能正確執行。 1. 所使用的典型Windows API CreateFile ReadFile WriteFile 以下函數是DDK的內容: HidD_SetFeature HidD_GetFeature HidD_SetOutputReport HidD_GetInputReport 其中,CreateFile用於打開設備;ReadFile、HidD_GetFeature、HidD_GetInputReport用於設備到主機方向的數據通訊;WriteFile、HidD_SetFeature、HidD_SetOutputReport用於主機到設備方向的數據通訊。鑑於實際應用,後文主要討論CreateFile,WriteFile,ReadFile,HidD_SetFeature四個函數,明白了這四個函數,其它的可以類推之。 2. 幾個常見錯誤 當使用以上API時,假如操縱失敗,調用GetLastError()會得到以下常見錯誤: 6: 句柄無效 23: 數據錯誤(循環冗餘碼檢查) 87: 參數錯誤 1784: 用戶提供的buffer無效 後文將會具體說明這些錯誤情況。 3. 主機端設備枚舉程序流程 4. 函數使用說明 CreateFile(devDetail->DevicePath, //設備路徑 GENERIC_READ | GENERIC_WRITE, //訪問方式 FILE_SHARE_READ | FILE_SHARE_WRITE, //共享模式 NULL, OPEN_EXISTING, //文件不存在時,返回失敗 FILE_FLAG_OVERLAPPED, //以重疊(異步)模式打開 NULL); 在這裏,CreateFile用於打開HID設備,其中設備路徑通過函數SetupDiGetInte***ceDeviceDetail取得。CreateFile有以下幾點需要留意: - 訪問方式: 假如是系***佔設備,例如鼠標、育兒嫂鍵盤等等,應將此參數設置爲0,否則後續函數操縱將失敗(譬如HidD_GetAttributes);也就是說,不能對獨佔設備進行除了查詢以外的任何操縱,所以能夠使用的函數也是很有限的,下文的一些函數並不一定適合這些設備。在此順便列出MSDN上關於此參數的說明: If this parameter is zero, the application can query file and device attributes without accessing the device. This is useful if an application wants to determine the size of a floppy disk drive and the formats it supports without requiring a floppy in the drive. It can also be used to test for the file's or directory's existence without opening it for read or write access。 - 重疊(異步)模式:此參數並不會在此處表現出明顯的意義,它主要是對後續的WriteFile,ReadFile有影響。假如這裏設置爲重疊(異步)模式,那麼在使用WriteFile,ReadFile時也應該使用重疊(異步)模式,反之亦然。這首先要求WriteFile,ReadFile的最後一個參數不能爲空(NULL)。否則,便會返回87(參數錯誤)錯誤號。當然,87號錯誤並不代表就是此參數不正確,更多的信息將在具體講述這兩個函數時指出。此參數爲0時,代表同步模式,即WriteFile,ReadFile操縱會在數據處理完成之後才返回,否則阻塞在函數內部。 ReadFile(hDev, //設備句柄,即CreateFile的返回值 recvBuffer, //用於接收數據的buffer IN_REPORT_LEN, //要讀取數據的長度 &recvBytes, //實際收到的數據的字節數 &ol); //異步模式 在這裏,ReadFile用於讀取HID設備通過中斷IN傳輸發來的輸進報告。有以下幾點要留意: 1、白癜風ReadFile的調用不會引起設備的任何反應,即HID設備與主機之間的中斷IN傳輸不與ReadFile打交道。實際上主機會在最大間隔時間(由設備的端點描述符來指定)內輪詢設備,發出中斷IN傳輸的請求。“讀取”即意味着從某個buffer裏面取回數據,實際上這個buffer就是HID設備驅動中的buffer。這個buffer的大小可以通過HidD_SetNumInputBuffers來改變。在XP上缺省值是32(個報告)。 2、讀取的數據對象是輸進報告,也即通過中斷輸進管道傳進的數據。所以,假如設備不支持中斷IN傳輸,那麼是無法使用此函數來得到預期結果的。實際上這種情況不可能在HID中出現,由於協議指明瞭至少要有一箇中斷IN端點。 3、IN_REPORT_LEN代表要讀取的數據的長度(實際的數據正文+一個byte的報告ID),這裏是一個常數,主要是由於設備固件的信息我是完全知道的,當然知道要讀取多少數據(也就是報告的長度);不過也可以通過另外的函數(HidD_GetPreparsedData)來事先取得報告的長度,這裏不做具體討論。由於很難想象在不瞭解固件信息的情況下來做自定義設備的HID通訊,在實際應用中一般來說就是固件與PC程序匹配着來開發。此參數假如設置過大,不會有實質性的錯誤,在recvBytes參數中會輸出實際讀到的長度;假如設置過小,即小於報告的長度,會返回1784號錯誤(用戶提供的buffer無效)。 4、關於異步模式。前面已經提過,此參數的設置必須與CreateFile時的設置相對應,否則會返回87號錯誤(參數錯誤)。假如不需要異步模式,此參數需置爲NULL。在這種情況下,ReadFile會一直等待直到數據讀取成功,所以會阻塞住程序確當前過程。 WriteFile(hDev, //設備句柄,即CreateFile的返回值 reportBuf, //存有待發送數據的buffer OUT_REPORT_LEN, //待發送數據的長度 &sendBytes, //實際收到的數據的字節數 &ol); //異步模式 在這裏,WriteFile用於傳輸一個輸出報告給HID設備。有以下幾點要留意: 1、 與ReadFile不同,WriteFile函數被調用後,固然也是經過驅動程序,但是終極會反映到設備中。也就是說,調用WriteFile後,設備會接收到輸出報告的請求。假如設備使用了中斷OUT傳輸,則WriteFile會通過中斷OUT管道來進行傳輸;否則會使用SetReport請求通過控制管道來傳輸。 2、 OUT_REPORT_LEN代表要寫進的數據長度(實際的數據正文+一個byte的報告ID)。硬度計假如大於實際報告的長度,則使用實際報告長度;假如小於實際報告長度,會返回1784號錯誤(用戶提供的buffer無效)。 3、 reportBuf[0]必須存有待發送報告的ID,並且此報告ID指示的必須是輸出報告,否則會返回87號錯誤(參數錯誤)。這種情況可能輕易被程序員忽略,結果不知錯誤號所反映的是什麼,網上也經常有類似疑問的帖子。順便指出,輸進報告、輸進報告、特徵報告這些報告類型,是反映在HID設備的報告描述符中。後文將做舉例討論。 4、 關於異步模式。前面已經提過,此參數的設置必須與CreateFile時的設置相對應,否則會返回87號錯誤(參數錯誤)。假如不需要異步模式,此參數需置爲NULL。在這種情況下,WriteFile會一直等待直到數據讀取成功,所以會阻塞住程序確當前過程。 HidD_SetFeature(hDev, //設備句柄,即CreateFile的返回值 reportBuf, //存有待發送數據的buffer FEATURE_REPORT_LEN); //buffer的長度 HidD_SetOutputReport(hDev, //設備句柄,即CreateFile的返回值 reportBuf, //存有待發送數據的buffer OUT_REPORT_LEN); //buffer的長度 HidD_SetFeature發送一個特徵報告給設備,HidD_ SetOutputReport發送一個輸出報告給設備。留意以下幾點: 1、 跟WriteFile類似,必須在reportBuf[0]中指明要發送的報告的ID,並且和各自適合的類型相對應。也就是說,HidD_SetFeature只能發送特徵報告,因此報告ID必須是特徵報告的ID;HidD_SetOutputReport只能發送輸出報告,因此報告ID只能是輸出報告的ID。 2、 這兩個函數最常返回的錯誤代碼是23(數據錯誤)。包括但不僅限於以下情況: - 報告ID與固件描述的不符。 - 傳進的buffer長度少於固件描述的報告的長度。 佔有關資料反映(非官方文檔),只要是驅動程序對請求無反應,都會產生此錯誤。 5. 常見錯誤彙總 - HID ReadFile - Error Code 6 (handle is invalid) 傳進的句柄無效 - Error Code 87 (參數錯誤) 很可能是createfile時聲明瞭異步方式,但是讀取時按同步讀取。 - Error Code 1784 (用戶提供的buffer無效): 傳參時傳進的“讀取buffer長度”與實際的報告長度不符。 - HID WriteFile - Error Code 6 (handle is invalid) 傳進的句柄無效 - Error Code 87(參數錯誤) - CreateFile時聲明的同步/異步方式與實際調用WriteFile時傳進的不同。 - 報告ID與固件中定義的不一致(buffer的首字節是報告ID) - Error Code 1784 (用戶提供的buffer無效) 傳參時傳進的“寫進buffer長度”與實際的報告長度不符。 - HidD_SetFeature - HidD_SetOutputReport - Error Code 1 (incorrect function) 不支持此函數,很可能是設備的報告描述符中未定義這樣的報告類型(輸進、輸出、特徵) - Error Code 6 (handle is invalid) 傳進的句柄無效 - Error Code 23(數據錯誤(循環冗餘碼檢查)) - 報告ID與固件中定義的不相符(buffer的首字節是報告ID) - 傳進的buffer長度少於固件定義的報告長度(報告正文+1byte, 1byte爲報告ID) - 據相關資料反映(非官方文檔),只要是驅動程序不接受此請求(對請求無反應),都會產生此錯誤 6. 報告描述符及數據通訊程序示例 報告描述符(由於是彙編代碼,所以不必留意其語法,僅需留意表中的每個數據都佔1個字節): _ReportDescriptor: //報告描述符 .dw 0x06, 0x00, 0xff //用法頁 .dw 0x09, 0x01 //用法(供給商用法1) .dw 0xa1, 0x01 //集合開始 .dw 0x85, 0x01 //報告ID(1) .dw 0x09, 0x01 //用法(供給商用法1) .dw 0x15, 0x00 //邏輯最小值(0) .dw 0x26, 0xff, 0x0 //邏輯最大值(255) .dw 0x75, 0x08 //報告大小(8) .dw 0x95, 0x07 //報告計數(7) .dw 0x81, 0x06 //輸進(數據,變量,相對值) .dw 0x09, 0x01 //用法(供給商用法1) .dw 0x85, 0x03 //報告ID(3) .dw 0xb1, 0x06 //特徵(數據,變量,相對值) .dw 0x09, 0x01 //用法(供給商用法1) .dw 0x85, 0x02 //報告ID(2) .dw 0xb1, 0x06 //特徵(數據,變量,相對值) .dw 0x09, 0x01 //用法(供給商用法1) .dw 0x85, 0x04 //報告ID(4) .dw 0x91, 0x06 //輸出(數據,變量,相對值) .dw 0xc0 //結合結束 _ReportDescriptor_End: 這個報告描述符,定義了4個不同的報告:輸進報告1,特徵報告2,特徵報告3,輸出報告4(數字代表其報告ID)。led照明爲了簡化,每個報告都是7個字節(加上報告ID就是8個字節)。下面用一個簡單的示例來描述PC端與USB HID設備進行通訊的一般方法。 #define USB_VID 0xFC0 #define USB_PID 0x420 HANDLE OpenMyHIDDevice(int overlapped); void HIDSampleFunc() { HANDLE hDev; BYTE recvDataBuf[8]; BYTE reportBuf[8]; DWORD bytes; hDev = OpenMyHIDDevice(0); //打開設備,不使用重疊(異步)方式; if (hDev == INVALID_HANDLE_VALUE) return; reportBuf[0] = 4; //輸出報告的報告ID是4 memset(reportBuf, 0, 8); reportBuf[1] = 1; if (!WriteFile(hDev, reportBuf, 8, &bytes, NULL)) //寫進數據到設備 return; ReadFile(hDev, recvDatatBuf, 8, &bytes, NULL); //讀取設備發給主機的數據 } HANDLE OpenMyHIDDevice(int overlapped) { HANDLE hidHandle; GUID hidGuid; HidD_GetHidGuid(&hidGuid); HDEVINFO hDevInfo = SetupDiGetClassDevs( &hidGuid, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE)); if (hDevInfo == INVALID_HANDLE_VALUE) { return INVALID_HANDLE_VALUE; } SP_DEVICE_INTERFACE_DATA devInfoData; devInfoData.cbSize = sizeof (SP_DEVICE_INTERFACE_DATA); int deviceNo = 0; SetLastError(NO_ERROR); while (GetLastError() != ERROR_NO_MORE_ITEMS) { if (SetupDiEnumInte***ceDevice (hDevInfo, 0, &hidGuid, deviceNo, &devInfoData)) { ULONG requiredLength = 0; SetupDiGetInte***ceDeviceDetail(hDevInfo, &devInfoData, NULL, 0, &requiredLength, NULL); PSP_INTERFACE_DEVICE_DETAIL_DATA devDetail = (SP_INTERFACE_DEVICE_DETAIL_DATA*) malloc (requiredLength); devDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); if(!SetupDiGetInte***ceDeviceDetail(hDevInfo, &devInfoData, devDetail, requiredLength, NULL, NULL)) { free(devDetail); SetupDiDestroyDeviceInfoList(hDevInfo); return INVALID_HANDLE_VALUE; } if (overlapped) { hidHandle = CreateFile(devDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL); } else { hidHandle = CreateFile(devDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL); } free(devDetail); if (hidHandle==INVALID_HANDLE_VALUE) { SetupDiDestroyDeviceInfoList(hDevInfo); free(devDetail); return INVALID_HANDLE_VALUE; } _HIDD_ATTRIBUTES hidAttributes; if(!HidD_GetAttributes(hidHandle, &hidAttributes)) { CloseHandle(hidHandle); SetupDiDestroyDeviceInfoList(hDevInfo); return INVALID_HANDLE_VALUE; } if (USB_VID == hidAttributes.VendorID && USB_PID == hidAttributes.ProductID) { break; } else { CloseHandle(hidHandle); ++deviceNo; } } } SetupDiDestroyDeviceInfoList(hDevInfo); return hidHandle; }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章