Window x64 与 x86 USB 动态库调试记录

10 篇文章 0 订阅
8 篇文章 1 订阅

一、前言,最近又遇到一个奇葩的需求,同一个客户,老外的要求真的非常多,上一次要求的提示公司产品的基于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, 点击编译,发现成功了,经测试可以使用。抓狂哭终于解决问题了,太好了。

如下图,


经一事长一智,这个解决问题,收获挺多的,写下这篇文章,记录这个,也方便其他人,不用绕弯子。大笑

评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值