一、概述
最近在项目中使用md5和sh1 算法,发现在原来的工程上,加上这两个算法的源代码竟然编译不能通过,出现100 多个bug。后来上来发现错误都是源于两个算法使用的一个头文件“StdInt.h”,后来找了一下vc的include 目录,竟然找不到
。后来,百度一下,才知道,StdInt.h 是c99中引进的一个标准C库的文件,而VC6.0这个编译器是98年出的,所以不支持。(就相差一年
),公司原来用的动态库都是基于VC6.0建立的,太Out了
,原来都没有想过会这么不兼容,于是乎就有了这边文章,顺带也把之前HID库封装一下,hid 库也是移植也是比较麻烦的,当初刚到公司时,一个项目需要封装库,足足搞了两天,才发现就卡在一个小问题,因此决定,使用常用的编译器VS2010把常用的动态库重新封装一遍。
二、VS2010 动态库创建
1、 动态库:包含.dll、.lib、.h三个文件,动态链接库是一个可执行的模块,不能单独运行,需要应用程序调用才能使用,通常封装了函数的集合,仅在程序运行的时候才去查。.lib文件提供调用函数的位置信息,.dll是原函数代码,使用的时候必须包含以上三个文件。
2、新建工程Win32 控制台工程,如图:
3、点击下一步,设置工程属性,选择DLL 和 空项目,点击确认。
3、Win32 动态库原理
(1)动态链接库:dll不必被包含在最终的EXE中,静态调用时仅把函数名或者变量名或者类名链接到EXE文件中,而这些东西的实体都只有在运行时才从动态库中导入到可执行文件中,动态调用的时候EXE文件执行时可以直接动态地引用和卸载DLL文件。
(2)回顾一下VC++支持的DLL
DLL的编制与具体的编程语言及编译器无关,动态链接库随处可见,VC++支持三种DLL:非MFC动态库、MFC规则DLL和MFC扩展DLL。DLL导出函数(或变量、类)可供应用程序调用;DLL内部函数只能在DLL程序内使用,应用程序无法调用它们
(3)导出函数的声明方式:
一种在函数声明类型和函数名之间加上“_declspec(dllexport)”。
另外一种采用模块定义(.def)文件声明,需要在库工程中添加模块文件,格式如下:
LIBRARY 库工程名称
EXPORTS 导出函数名
4、具体步骤
4.1 新建UsbHidTest.cpp 和 UsbHidTest.h
4.2 使用“_declspec(dllexport)” 方式导出函数
(1) 在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 BOOL UsbCall FindUsbHidDevice();
#endif
(2)在UsbHidTest.cpp 中添加如下代码:
#include <Windows.h>
#define USBDLL //一定要先定义这个宏,在添加包含UsbHidTest.h 文件
#include "UsbHidTest.h"
//以下函数函数实现代码
UsbPort BOOL UsbCall FindUsbHidDevice()
{
return TRUE;
}
(3)编译上面代码,则可以在工程目录下录下看到,相应的xx.lib 和 xx.dll 文件,如图:
目
(4)检查Dll 中函数是否导出,可以时用DllView.exe 来查看,如图:
4.3、使用模块定义文件 xx.def 方式导出函数
(1)以同样方式在创建UsbHidTest.cpp 和 UsbHidTest.h 文件。
(2)在工程中,创建UsbHidTest.def 模块定义文件,(注意:经验和教训,如果将别的工程中的xx.def 文件 添加到工程中,是没有效果的,也就是工程可以正常编译,但是导出的DLL 却看不到函数名,这个血的教训,,第一次使用这种方式的时候,我就吃亏了,这个问题足足纠结了一天,才发现。
)如图:
通过工程方式创建的模块定义文件 .def 编译器应该会在工程配置文件中,自动添加相关的配置信息。
(3)在UsbHidTest.h 添加如下代码:
#ifndef _USB_HID_TEST_H_
#define _USB_HID_TEST_H_
//函数调用约定
#ifdef USBDLL
#define UsbCall __stdcall
#else
#define UsbCall __cdecl
#endif
BOOL UsbCall FindUsbHidDevice();
#endif
(4)在UsbHidTest.cpp 添加如下代码:
#include <Windows.h>
#define USBDLL
#include "UsbHidTest.h"
//以下函数函数实现代码
BOOL UsbCall FindUsbHidDevice()
{
return TRUE;
}
(5)在UsbHidTest.def 文件中添加如下代码:
LIBRARY
EXPORTS
FindUsbHidDevice
(6) 编译文件,则可以在Debug目录看到相应的lib 和 dll 文件。
如图:
三、Win32 USB Hid 动态库创建
(1)将hidpi.h、hidsdi.h、hidusage.h 和 hid.lib 复制当前工程目录下如图:
(2) 将hidpi.h、hidsdi.h、hidusage.h 和 hid.lib 添加到当前工程下,如图:
(3)在UsbHidTest.cpp 中包含相关的.h 文件,
#include "dbt.h"
extern "C"
{
#include "hidsdi.h"
#include "setupapi.h"
}
(4) 在FindUsbHidDevce 方法中添加如下函数体:
BOOL UsbCall FindUsbHidDevice()
{
//定义变量用来保存VID、PID、版本号
DWORD MyVid = 0x0035, MyPid = 0xFFFF, MyPvn = 0x1234;
//定义一个GUID的结构体HidGuid来保存HID设备的接口类GUID。
GUID HidGuid;
//定义一个DEVINFO的句柄hDevInfoSet来保存获取到的设备信息集合句柄。
HDEVINFO hDevInfoSet;
//定义MemberIndex,表示当前搜索到第几个设备,0表示第一个设备。
DWORD MemberIndex;
//DevInterfaceData,用来保存设备的驱动接口信息
SP_DEVICE_INTERFACE_DATA DevInterfaceData;
//定义一个BOOL变量,保存函数调用是否返回成功
BOOL Result;
//定义一个RequiredSize的变量,用来接收需要保存详细信息的缓冲长度。
DWORD RequiredSize;
//定义一个指向设备详细信息的结构体指针。
PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData;
//定义一个用来保存打开设备的句柄。
HANDLE hDevHandle;
//定义一个HIDD_ATTRIBUTES的结构体变量,保存设备的属性。
HIDD_ATTRIBUTES DevAttributes;
//PSP_DEVICE_INTERFACE_DETAIL_DATA pDevDetailData;
//初始化设备未找到
BOOL MyDevFound=FALSE;
//获取在文本框中设置的VID、PID、PVN。
//GetMyIDs();
//初始化读、写句柄为无效句柄。
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);
//根据HidGuid来获取设备信息集合。其中Flags参数设置为
//DIGCF_DEVICEINTERFACE|DIGCF_PRESENT,前者表示使用的GUID为
//接口类GUID,后者表示只列举正在使用的设备,因为我们这里只
//查找已经连接上的设备。返回的句柄保存在hDevinfo中。注意设备
//信息集合在使用完毕后,要使用函数SetupDiDestroyDeviceInfoList
//销毁,不然会造成内存泄漏。
hDevInfoSet=SetupDiGetClassDevs(&HidGuid,
NULL,
NULL,
DIGCF_DEVICEINTERFACE|DIGCF_PRESENT);
//AddToInfOut("开始查找设备");
//然后对设备集合中每个设备进行列举,检查是否是我们要找的设备
//当找到我们指定的设备,或者设备已经查找完毕时,就退出查找。
//首先指向第一个设备,即将MemberIndex置为0。
MemberIndex=0;
while(1)
{
//调用SetupDiEnumDeviceInterfaces在设备信息集合中获取编号为
//MemberIndex的设备信息。
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);
//if(hReadHandle!=INVALID_HANDLE_VALUE)
//AddToInfOut("读访问打开设备成功");
//else
//AddToInfOut("读访问打开设备失败");
//写方式打开设备
hWriteHandle=CreateFile(MyDevPathName,
GENERIC_WRITE,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL|FILE_FLAG_OVERLAPPED,
NULL);
//if(hWriteHandle!=INVALID_HANDLE_VALUE)
//AddToInfOut("写访问打开设备成功");
//else
//AddToInfOut("写访问打开设备失败");
//手动触发事件,让读报告线程恢复运行。因为在这之前并没有调用
//读数据的函数,也就不会引起事件的产生,所以需要先手动触发一
//次事件,让读报告线程恢复运行。
//显示设备的状态。
//SetDlgItemText(IDC_DS,"设备已打开");
//找到设备,退出循环。本程序只检测一个目标设备,查找到后就退出
//查找了。如果你需要将所有的目标设备都列出来的话,可以设置一个
//数组,找到后就保存在数组中,直到所有设备都查找完毕才退出查找
break;
}
}
//如果打开失败,则查找下一个设备
else continue;
}
//调用SetupDiDestroyDeviceInfoList函数销毁设备信息集合
SetupDiDestroyDeviceInfoList(hDevInfoSet);
//如果设备已经找到,那么应该使能各操作按钮,并同时禁止打开设备按钮
if(MyDevFound)
{
return TRUE;
}
else
{
//AddToInfOut("设备未找到");
return FALSE;
}
return TRUE;
}
(5)直接编译,你会发现,出现如下图错误,显示在FindUsbHidDevice 方法中引用的方法来链接有问题,即编译器进行编译时链接不到相关的函数。
(6)解决问题的方式时,在工程属性中,打开配置属性->链接器->输入->附加依赖项中,添加相关函数依赖的库,如下图:
点击确定后,可以成功编译,如下图:
好了,Win32 Usb HID 动态库的使用就是这么多了,记录下来,防止以后自己再次使用时忘记了。