一、前言,最近又遇到一个奇葩的需求,同一个客户,老外的要求真的非常多,上一次要求的提示公司产品的基于Linux的USB HID 开发包,花几天时间加班加点搞定之后。这两天又有个奇葩的需求,需要我们提供,基于Windows x64 和 x86 平台的USB - HID 开发包。
我一般在公司提供的是x86平台windows 开发包,电脑是使用window 7 64位,本意以为平时提供的即使用与32位平台也使用与64位平台,后面亲自尝试,才发现原来不能使用。于是乎就有这篇文章记录这件事。
二、开发环境: Windows10 x64 Visual Studio 2013
1、 win32 USB - HID动态库创建, 新建Win32 动态库工程,如下图:
a. 创建Win32 控制台应用程序
b. 选择dll 工程 和 空项目
c、添加USB - HID 相关文件到当前工作目录下,并添加到工程中,如下图:
d 在自定义库.cpp 文件 包含相关头文件和HID的库文件,如下图:
extern "C" //hid 库是使用C语言编译的,不支持c++ 的函数重载
{ //因此,在C++ 中,导入时,需要使用extern "C" 包含头文件
#include "hidsdi.h"
#include "setupapi.h"
}
#pragma comment( lib, "setupapi.lib")
#pragma comment( lib, "hid.lib")
下面为自定义库函数,UsbHidTest.h 中
#ifndef _USB_HID_TEST_H_
#define _USB_HID_TEST_H_
//函数调用约定
#ifdef USBDLL
#define UsbCall __stdcall
#else
#define UsbCall __cdecl
#endif
//函数导出方式
#ifdef USBDLL
#define UsbPort extern "C" _declspec(dllexport)
#else
#define UsbPort extern "C" __declspec(dllimport)
#endif
UsbPort int UsbCall FindUsbHidDevice();
#endif
UsbHidTest.cpp 中
//以下函数函数实现代码
UsbPort int UsbCall FindUsbHidDevice()
{
DWORD MyVid = 0x0416, MyPid = 0xFFFF, MyPvn = 0x0035;
GUID HidGuid;
HDEVINFO hDevInfoSet;
DWORD MemberIndex;
SP_DEVICE_INTERFACE_DATA DevInterfaceData;
BOOL Result;
DWORD RequiredSize;
//定义一个指向设备详细信息的结构体指针。
PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData;
//定义一个用来保存打开设备的句柄。
HANDLE hDevHandle;
//定义一个HIDD_ATTRIBUTES的结构体变量,保存设备的属性。
HIDD_ATTRIBUTES DevAttributes;
BOOL MyDevFound = FALSE;
//初始化读、写句柄为无效句柄。
HANDLE hReadHandle = INVALID_HANDLE_VALUE;
HANDLE hWriteHandle = INVALID_HANDLE_VALUE;
//对DevInterfaceData结构体的cbSize初始化为结构体大小
DevInterfaceData.cbSize = sizeof(DevInterfaceData);
//对DevAttributes结构体的Size初始化为结构体大小
DevAttributes.Size = sizeof(DevAttributes);
//调用HidD_GetHidGuid函数获取HID设备的GUID,并保存在HidGuid中。
HidD_GetHidGuid(&HidGuid);
hDevInfoSet = SetupDiGetClassDevs(&HidGuid,
NULL,
NULL,
DIGCF_DEVICEINTERFACE | DIGCF_PRESENT);
MemberIndex = 0;
while (1)
{
Result = SetupDiEnumDeviceInterfaces(hDevInfoSet,
NULL,
&HidGuid,
MemberIndex,
&DevInterfaceData);
//如果获取信息失败,则说明设备已经查找完毕,退出循环。
if (Result == FALSE) break;
//将MemberIndex指向下一个设备
MemberIndex++;
//如果获取信息成功,则继续获取该设备的详细信息。在获取设备
//详细信息时,需要先知道保存详细信息需要多大的缓冲区,这通过
//第一次调用函数SetupDiGetDeviceInterfaceDetail来获取。这时
//提供缓冲区和长度都为NULL的参数,并提供一个用来保存需要多大
//缓冲区的变量RequiredSize。
Result = SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
&DevInterfaceData,
NULL,
NULL,
&RequiredSize,
NULL);
//然后,分配一个大小为RequiredSize缓冲区,用来保存设备详细信息。
pDevDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)malloc(RequiredSize);
if (pDevDetailData == NULL) //如果内存不足,则直接返回。
{
//MessageBox("内存不足!");
SetupDiDestroyDeviceInfoList(hDevInfoSet);
return FALSE;
}
//并设置pDevDetailData的cbSize为结构体的大小(注意只是结构体大小,
//不包括后面缓冲区)。
pDevDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);
//然后再次调用SetupDiGetDeviceInterfaceDetail函数来获取设备的
//详细信息。这次调用设置使用的缓冲区以及缓冲区大小。
Result = SetupDiGetDeviceInterfaceDetail(hDevInfoSet,
&DevInterfaceData,
pDevDetailData,
RequiredSize,
NULL,
NULL);
//将设备路径复制出来,然后销毁刚刚申请的内存。
char *MyDevPathName = pDevDetailData->DevicePath;
free(pDevDetailData);
//如果调用失败,则查找下一个设备。
if (Result == FALSE) continue;
//如果调用成功,则使用不带读写访问的CreateFile函数
//来获取设备的属性,包括VID、PID、版本号等。
//对于一些独占设备(例如USB键盘),使用读访问方式是无法打开的,
//而使用不带读写访问的格式才可以打开这些设备,从而获取设备的属性。
hDevHandle = CreateFile(MyDevPathName,
NULL,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
//如果打开成功,则获取设备属性。
if (hDevHandle != INVALID_HANDLE_VALUE)
{
//获取设备的属性并保存在DevAttributes结构体中
Result = HidD_GetAttributes(hDevHandle,
&DevAttributes);
//关闭刚刚打开的设备
CloseHandle(hDevHandle);
//获取失败,查找下一个
if (Result == FALSE) continue;
//如果获取成功,则将属性中的VID、PID以及设备版本号与我们需要的
//进行比较,如果都一致的话,则说明它就是我们要找的设备。
if (DevAttributes.VendorID == MyVid) //如果VID相等
if (DevAttributes.ProductID == MyPid) //并且PID相等
if (DevAttributes.VersionNumber == MyPvn) //并且设备版本号相等
{
MyDevFound = TRUE; //设置设备已经找到
//AddToInfOut("设备已经找到");
//那么就是我们要找的设备,分别使用读写方式打开之,并保存其句柄
//并且选择为异步访问方式。
//读方式打开设备
hReadHandle = CreateFile(MyDevPathName,
GENERIC_READ,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
//写方式打开设备
hWriteHandle = CreateFile(MyDevPathName,
GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED,
NULL);
break;
}
}
//如果打开失败,则查找下一个设备
else continue;
}
//调用SetupDiDestroyDeviceInfoList函数销毁设备信息集合
SetupDiDestroyDeviceInfoList(hDevInfoSet);
//如果设备已经找到,那么应该使能各操作按钮,并同时禁止打开设备按钮
if (MyDevFound)
{
return 0;
}
else
{
//AddToInfOut("设备未找到");
return 1;
}
}
点击编译,会发现编译器报错,如下图:
上面错误,非常明显,可以看出是类型问题,问题的原因是,在VS 中,默认创建的控制台工程使用的Unicode 编码方式,
而HID库和相关头文件的编码方式使用的是,多字节方式,所以造成冲突,因此,需要修改工程的字符集,如下图:
点击编译,成功如下图
2、上面在自定义库文件中,使用#pragma comment(lib, "hid.lib") 这种方式导入lib
这种通过代码的形式导入库非常方便。
其实还有另一种方式(记录一下,防止忘记)
另外一种方式就是通过工程配置的方式,具体操作如下图:
(1) 右键项目,点击 - 项目属性 - 连接器 - 输入 - 附加库依赖项 - 输入相关hid库,如下图:
;hid.lib;setupapi.lib;
(2) 在常规 - 附加库目录 中输入hid.lib 库的目录,如下图:(我的hid.lib放在当前工程目录)
这种方式有一个好处就是不用编写代码,但是库的路径是绝对路径,当你的工程移动到别的目录,则需要重新配置。
上面讲述了,win32 的自定义hid库的创建。
三、Windows x64平台 自定义HID 库
编译64位的库,通过百度发现,只需配置一下编译器的就可以了,修改项目配置如下:
右键 - 项目属性 - 配置管理器 - 活动解决方案平台 - 新建 - x64
设置完毕,点击编译,会发现项目报错,如下如:
1>UsbHidTest.obj : error LNK2019: 无法解析的外部符号 HidD_GetAttributes,该符号在函数 FindUsbHidDevice 中被引用
1>UsbHidTest.obj : error LNK2019: 无法解析的外部符号 HidD_GetHidGuid,该符号在函数 FindUsbHidDevice 中被引用
LINK2019 这个错误,我翻边网络,都是说,这两个函数在编译时,没有被连接上,或则 缺少相关的库文件,而不是
但是,上面的操作明明包含HID 相关的库了啊,无论我使用方式1 (代码包含 )还是方式2(配置工程)或则 两个都使用,还是会报错,明明在32位都可以通过的,换了x64 就死活编译不过。
这个问题,我纠结了两天,网上也没有关于HID x64自定义动态库这方面的资料,只能自己摸索了。
但是根据上面问题的提示,以及我将两个函数注释后,编译时可以通过的,于是乎,可以将问题定位到了hid.lib 这个库中,
通过dumpbin /headers "C:\Users\69009\Desktop\lib - 副本 (2)\lib\hid.lib" 命令去查看这个lib的一些信息,如下图
通过在命令行中参看信息可以发现原理这个lib的x86 32位平台的,而且百度发现,原理x86的库是不能参与x64编译的。
更要命的是,我一直以为x86的lib 是可以参数x64 程序的编译的。(太丢脸了)
于是,只能去度娘中,找x64 平台的hid.lib,翻边网络又是一阵迷茫,没有有关x64 的hid.lib有的却是amd 64的lib,
amd64的应该不能用于x64的吧,又这样子疑问了一个早上。
后来,和公司的大神中午一起吃饭聊天,聊到CPU的由来,结果发现,原来x64 cpu就是amd 64 的孪生兄弟,cpu的指令集都差不多,缺乏硬件常识。
这样子,amd 64 的库就可以使用与x64 平台的编译,于是将工程的32位的hid.lib换成amd 64的hid.lib, 点击编译,发现成功了,经测试可以使用。终于解决问题了,太好了。
如下图,
经一事长一智,这个解决问题,收获挺多的,写下这篇文章,记录这个,也方便其他人,不用绕弯子。