记录一下以便回顾
//参数
int deviceNo;
bool result;
HANDLE hidHandle;
GUID hidGuid;
ULONG requiredLength;
SP_DEVICE_INTERFACE_DATA devInfoData;
DWORD dwSend;
OVERLAPPED osWriter = { 0 };
BYTE write_HID[] = {0xAA, 0xBB, 0xCC, 0x90, 0x1C, 0x00, 0x00};
BYTE read_HID[] = {0};
//初始化
deviceNo = 0;
hidHandle = NULL;
devInfoData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);
首先使用HidD_GetHidGuid函数,像GUID之类的类型,应该都是头文件中定义好的,可以直接使用。
HidD_GetHidGuid(&hidGuid);
使用SetupDiGetClassDevs函数,其中的参数可以去查看官方文档,这个函数会返回一个包含本机上所有被请求的设备信息的设备信息集句柄,需要使用到setupapi.h头文件,在使用setupapi.h头文件时需要先使用windows.h头文件,什么原因不清楚,也是找了好久才解决。
SetupDiGetClassDevsExA function (setupapi.h) - Win32 apps |微软文档 (microsoft.com)
HDEVINFO hDevInfo = SetupDiGetClassDevs(&hidGuid, NULL, NULL, (DIGCF_PRESENT | DIGCF_DEVICEINTERFACE));
句柄:
系统为每个进程在内存中分配一定的区域,用来存放各个句柄,即一个个32位无符号整型值(32位操作系统中)。每个32位无符号整型值相当于一个指针,指向内存中的另一个区域(我们不妨称之为区域A)。而区域A中存放的正是对象在内存中的地址。当对象在内存中的位置发生变化时,区域A的值被更新,变为当前时刻对象在内存中的地址,而在这个过程中,区域A的位置以及对应句柄的值是不发生变化的。这种机制,用一种形象的说法可以表述为:有一个固定的地址(句柄),指向一个固定的位置(区域A),而区域A中的值可以动态地变化,它时刻记录着当前时刻对象在内存中的地址。这样,无论对象的位置在内存中如何变化,只要我们掌握了句柄的值,就可以找到区域A,进而找到该对象。而句柄的值在程序本次运行期间是绝对不变的,我们(即系统)当然可以掌握它。这就是以不变应万变,按图索骥,顺藤摸瓜。(摘抄)
再使用SetupDiEnumInterfaceDevice函数SetupDiEnumDeviceInterfaces function (setupapi.h) - Win32 apps |微软文档 (microsoft.com)
result = SetupDiEnumInterfaceDevice(hDevInfo, 0, &hidGuid, deviceNo, &devInfoData);
再说一下GetLastError()函数,它会检索调用线程的最后一个错误代码值。最后一个错误代码基于每个线程进行维护。多个线程不会覆盖彼此的最后一个错误代码。代码值意义可以参考
GetLastError错误码大全_jiangqin115的博客-CSDN博客_getlasterror错误码
再使用SetupDiGetInterfaceDeviceDetail函数,我之前找的是SetupDiGetDeviceInterfaceDetail 函数,他们两好像是一样的功能,没细究,我用的第一个是可以运行的,虽然在官方文档没有找到相关函数。
SetupDiGetDeviceInterfaceDetailA function (setupapi.h) - Win32 apps |微软文档 (microsoft.com)
这个函数要调用两次,第一次调用为了获取requiredLength,第二次调用为了获取devDetail,好像是刚开始不知道缓冲区的长度,调用了一次之后会返回长度,具体自己查吧。
requiredLength = 0; /* 先将变量置零,以便于下一步进行获取 */
SetupDiGetInterfaceDeviceDetail(hDevInfo, &devInfoData, NULL, 0, &requiredLength, NULL); /* 第一次调用,为了获取requiredLength */
PSP_INTERFACE_DEVICE_DETAIL_DATA devDetail = (SP_INTERFACE_DEVICE_DETAIL_DATA*)malloc(requiredLength); /* 根据获取到的长度申请动态内存 */
devDetail->cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA); /* 先对变量进行部分初始化 */
result = SetupDiGetInterfaceDeviceDetail(hDevInfo, &devInfoData, devDetail, requiredLength, NULL, NULL); /* 第二次调用,为了获取devDetail */
再使用CreateFile(),具体的参数含义自己看官方文档吧,我自己也解释不清楚(笑哭)
CreateFileA 函数 (fileapi.h) - Win32 应用程序|微软文档 (microsoft.com)
hidHandle = CreateFile(devDetail->DevicePath, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE, NULL, OPEN_EXISTING, 0, NULL);
这里注意:返回值hidHandle == INVALID_HANDLE_VALUE时,说明设备被设置成了独占模式。
再使用WriteFile(1,2,3,4,5),通过传递2参数向下位机发送HID协议,这里注意第三个参数说是写入设备的字节数,好像是有默认值,但是我使用sizeof函数获取HID长度+1用进去是行不通的,GetLastError报87参数错误,但是直接写65是可以的,长度+1是因为上位机实际发送数据需加一位Report id来区分,byte[0]即为Report id,底层没有设置的话默认为0即可。
WriteFile 函数 (fileapi.h) - Win32 应用程序|微软文档 (microsoft.com)
result = WriteFile(hidHandle, write_HID, 65, &dwSend, nullptr);
最后,使用ReadFile(1,2,3,4,5),参数2传递的是char数组的首地址,后面两个参数是和多线程有关的好像,我这里没用到,如果要使用的话,要和createFile()里的参数配合使用。
ReadFile function (fileapi.h) - Win32 apps |微软文档 (microsoft.com)
result = ReadFile(hidHandle, read_HID, 65, &dwSend, nullptr);
再提一嘴,向下位机传递的HID协议应该是与下位机程序统一好的,要不然就像语言不通的两个人,无法交流。
我也是嵌入式开发初学者,哪里写的不对轻点骂,欢迎评论区讨论!