本文示範如何使用 Windows HID (Human Interface Device) API 列舉系統 USB 裝置,識別遊戲搖桿,並即時讀取其輸入報告資料。編譯環境需 Windows 2000/XP + VC++ 6.0 + Win2K DDK。
HidD_GetHidGuid 取得 HID 類別 GUIDSetupDiGetClassDevs 列舉所有 HID 裝置介面SetupDiEnumDeviceInterfaces 取得各裝置介面資料SetupDiGetDeviceInterfaceDetail 取得裝置路徑CreateFile 開啟裝置控制代碼HidD_GetAttributes、HidD_GetManufacturerString、HidD_GetProductString 取得廠商/產品資訊HidD_GetPreparsedData + HidP_GetCaps 取得輸入報告長度ReadFile 讀取輸入報告,解析按鍵狀態#include <windows.h>
#include <setupapi.h>
#include <hidsdi.h>
#include <hidpi.h>
#include <stdio.h>
#include <tchar.h>
#pragma comment(lib, "setupapi.lib")
#pragma comment(lib, "hid.lib")
extern "C" { /* DDK 標頭檔已包含必要宣告 */ }
void main()
{
GUID HidGuid;
HidD_GetHidGuid(&HidGuid);
_tprintf(_T("HID Class GUID: %08X-%04X-%04X-%02X%02X-%02X%02X%02X%02X%02X%02X\n"),
HidGuid.Data1, HidGuid.Data2, HidGuid.Data3,
HidGuid.Data4[0], HidGuid.Data4[1], HidGuid.Data4[2],
HidGuid.Data4[3], HidGuid.Data4[4], HidGuid.Data4[5],
HidGuid.Data4[6], HidGuid.Data4[7]);
HDEVINFO hDevInfo = SetupDiGetClassDevs(&HidGuid, NULL, NULL,
DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
if (hDevInfo == INVALID_HANDLE_VALUE) return;
SP_DEVICE_INTERFACE_DATA did = { sizeof(SP_DEVICE_INTERFACE_DATA) };
for (DWORD idx = 0; ; idx++)
{
if (!SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &HidGuid, idx, &did))
{
if (GetLastError() == ERROR_NO_MORE_ITEMS) break;
continue;
}
_tprintf(_T("\nFound USB Device #%d:\n"), idx);
// 取得裝置路徑
DWORD len = 0;
SetupDiGetDeviceInterfaceDetail(hDevInfo, &did, NULL, 0, &len, NULL);
PSP_DEVICE_INTERFACE_DETAIL_DATA pDetail = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(len);
pDetail->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
SetupDiGetDeviceInterfaceDetail(hDevInfo, &did, pDetail, len, NULL, NULL);
_tprintf(_T("Device Path: %s\n"), pDetail->DevicePath);
// 開啟裝置
HANDLE hDev = CreateFile(pDetail->DevicePath, GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
if (hDev == INVALID_HANDLE_VALUE) { free(pDetail); continue; }
// 取得裝置屬性
HIDD_ATTRIBUTES attr = { sizeof(HIDD_ATTRIBUTES) };
HidD_GetAttributes(hDev, &attr);
_tprintf(_T("Vendor ID: 0x%04X\nProduct ID: 0x%04X\nVersion: 0x%04X\n"),
attr.VendorID, attr.ProductID, attr.VersionNumber);
WCHAR wStr[256]; TCHAR tStr[256];
HidD_GetManufacturerString(hDev, wStr, sizeof(wStr));
wcstombs(tStr, wStr, 256); _tprintf(_T("Manufacturer: %s\n"), tStr);
HidD_GetProductString(hDev, wStr, sizeof(wStr));
wcstombs(tStr, wStr, 256); _tprintf(_T("Product: %s\n"), tStr);
// 取得輸入報告長度
PHIDP_PREPARSED_DATA pPreparsed;
HidD_GetPreparsedData(hDev, &pPreparsed);
HIDP_CAPS caps;
HidP_GetCaps(pPreparsed, &caps);
_tprintf(_T("Input Report Length: %d bytes\n"), caps.InputReportByteLength);
// 讀取輸入報告迴圈
BYTE* pReport = new BYTE[caps.InputReportByteLength];
DWORD bytesRead;
do
{
ReadFile(hDev, pReport, caps.InputReportByteLength, &bytesRead, NULL);
if (bytesRead == caps.InputReportByteLength)
{
for (UINT i = 0; i < bytesRead; i++)
_tprintf(_T("%02X%s"), pReport[i], (i == bytesRead-1) ? _T("\r") : _T("-"));
// 範例:偵測特定按鍵 (第 n-1 位元組 = 0x20) 退出
if (bytesRead >= 2 && pReport[bytesRead-2] == 0x20)
{ _tprintf(_T("\n")); break; }
}
Sleep(10);
} while (bytesRead == caps.InputReportByteLength);
delete[] pReport;
CloseHandle(hDev);
free(pDetail);
}
SetupDiDestroyDeviceInfoList(hDevInfo);
}
SetupDiGetDeviceInterfaceDetail 需兩次呼叫:第一次取得緩衝區大小,第二次填入資料pDetail->cbSize 必須正確設定,否則 API 失敗0x20 作為退出條件,實際應解析具體按鍵位元setupapi.lib、hid.lib 並包含 DDK 標頭檔掌握此架構後,可進一步: