平台:Windows7 64bit ultimate
环境:Qt5.1,libusb-win32-bin-1.2.6.0[libusb-win32 - Browse Files at SourceForge.net]
1、《USB与QT编程》—— http://91r.net/ask/16450160.html
这个资料里给出了Win平台下两种USB的解决方案,我是不太会处理Win的东西,尤其还是与设备驱动有关。尤其觉得Unix/Linux下的东西好用一些,即使是移植过来到Win32平台上的。
2、《libusb-win32》—— http://sourceforge.net/apps/trac/libusb-win32/wiki
这是libusb-win32官方wiki页,主要的说明均在这里。
3、《Qt下的libusb-win32的使用方法》—— http://www.cnblogs.com/lknlfy/p/3189949.html
这个博客里主要给出了一些libusb的基本使用:安装(驱动安装),库链接等,然后是一个简单的程序应用,很适合入门。
4、《QT - USB-driver - libusb [转]》—— http://blog.sina.com.cn/s/blog_40b3edd70100n3pi.html
这个博客里主要是对库函数与详细讲解与应用,深入理解的不错文章。
5、《QT5文件管理for windows》—— http://hi.baidu.com/wuliuhong/item/84fe3ec9ee60d07ccfd4f8e3
这个博客是Qt5处理Window消息的一个实例,可作参考。
6、《2.31.2 使用NSIS(Nullsoft Install System)》—— http://book.51cto.com/art/201003/188297.htm
还有一些资料,零碎的查阅过,也无法一一列出来了。连同其上列出来,一同感谢一下,谢谢能有那么多资料可以参考,为我解决我自己的应用提供了很大的帮助。
下面来写正文了,目前我也还不是特别理解libusb的细节,能写出来的只是一种解决方案,在我实践中出现的问题的一种解决。
下载libusb-win32-bin-1.2.6.0.zip,解压后进入其下的bin文件夹下,运行inf-wizard.exe程序。该程序主要是用来安装libusb-win32的驱动,以及一些相关库(*.dll,*.a)。官方说安装时建议先将多余的USB设备都移除,这样也避免了安装错设备的驱动。不过可以打开inf-wizard.exe程序,进入到设备列表界面,然后再插上想要安装驱动的USB设备,等待设备列表更新,就能看出自己的设备是哪一个了,如下图。列表若是无法自动刷新,可先上一步再下一步回到设备列表界面。如此,对刚开始折腾USB,不太明了的人,也不失为一个不错的方法,省得折腾了。呵呵~
记住一,最后要点击“Install Now...”进行驱动安装,依赖库回同时安装到系统中去,尤其是libusb0.dll动态库。
记住二,Vendor ID和Product ID, 这两个ID用来识别你的设备,程序中要用到的。
过程中会提示保存驱动程序,记住保存的位置,因为之后若要别的计算机上使用同一个设备,或是你为这个设备写好的程序,那么这个驱动是需要的。否则就必需再重新在其它计算机上用libusb-win32的inf-wizard.exe安装驱动。尤其是若涉及到要发布出去给他人使用的情况,这样子就更不适合了。发布的方法之后会讲到。
由于libusb-win32是第三方的东西,所以需要配置到Qt中去,才能够使用。
将解压的libusb-win32下的include中的libusb头文件lusb0_usb.h文件拷贝到Qt的include文件夹下。
我的Qt安装的是默认路径,C盘根目录下。Qt5.1自带QtCreator和mingW,mingW的include路径为:C:\Qt\Qt5.1.0\Tools\mingw48_32\i686-w64-mingw32\include。要确认你的环境上的这个路径哦!!
将解压的libusb-win32下的lib中的库文件拷贝到Qt工程下(自己写的Qt程序的工程目录),注意根据编译器的不同选择样应的库。我用的是Qt5.1自带的mingW-gcc编译器,所以拷贝的是lib/gcc下的libusb.a。
然后在Qt工程文件*.pro文件中添加如下内容:(参考自资料3)
LIBS += “D:/QtProject/USBConfig/libusb.a” —— 当然,路径是要和你的工程同步
这里没能找到怎么将libusb.a加载到Qt的环境里,因为没有找到应该放置的位置,如有知道的,解决了的,请一定告知我哦!谢谢了~
接口函数就不介绍了,详细的资料很多,用法也很多,一看就清楚的。这里就写一些我自己写的代码,我也不太会C++,所以很多东西处理的不好,勿见怪了,呵呵~
1、构造函数:
UsbListener::UsbListener(QObject *parent) :
QThread(parent)
{
usb_init(); /* initialize the library */
}
2、查找设备函数:——这个函数用来查找自己的设备,之后在监听系统是否有设备已插入/拔出计算机时有用
struct usb_device * UsbListener::findUSBDev(const unsigned short idVendor,
const unsigned short idProduct)
{
struct usb_bus *bus;
struct usb_device *dev;
usb_find_busses(); /* find all busses */
usb_find_devices(); /* find all connected devices */
for(bus = usb_get_busses(); bus; bus = bus->next)
{
for(dev = bus->devices; dev; dev = dev->next)
{
if((dev->descriptor.idVendor == idVendor)
&& (dev->descriptor.idProduct == idProduct))
{
return dev;
}
}
}
return NULL;
}
3、打开函数:
bool UsbListener::openUSB(struct usb_device *dev)
{
devOpenFlg = false;
devHandle = usb_open(dev);
if(!devHandle)
{
qDebug() << "error opening device: ";
qDebug() << usb_strerror();
return false;
}
else
{
qDebug() << "success: device " << MY_VID << " : "<< MY_PID << " opened";
devOpenFlg = true;
}
if (usb_set_configuration(devHandle, MY_CONFIG) < 0)
{
qDebug() << "error setting config #" << MY_CONFIG << " : " << usb_strerror();
usb_close(devHandle);
return false;
}
else
{
qDebug() << "success: set configuration #" << MY_CONFIG;
}
if (usb_claim_interface(devHandle, 0) < 0)
{
qDebug() << "error claiming interface #" << MY_INTF;
qDebug() << usb_strerror();
usb_close(devHandle);
return false;
}
else
{
qDebug() << "success: claim_interface #" << MY_INTF;
}
return true;
}
4、关闭函数:——注意要停止线程,否则一但关闭了USB设备,而线程在继续读取数据,这样是错误的。
void UsbListener::closeUSB()
{
if(devHandle)
{
devOpenFlg = false;
UsbListener::quit();
int ret = usb_close(devHandle); // Exit Thread
qDebug() << "Close USB Device [" << ret << "]";
}
}
发送数据函数:——此处用的是usb_bulk_write()方式向串口发送数据,其后的读函数同样也是用usb_bulk_read()方式接收数据。
bool UsbListener::sendData(QByteArray &data)
{
if(true == devOpenFlg)
{
int ret = 0;
char *tmp = data.data();
ret = usb_bulk_write(devHandle, EP_OUT, tmp, data.length(), 5000);
if (ret < 0)
{
qDebug() << "error writing:";
qDebug() << usb_strerror();
return false;
}
else
{
qDebug() << "success: bulk write " << ret << " bytes";
return true;
}
}
return false;
}
5、接收函数:——这里主要是用线程来监听接收数据,注意要将managerData()函数换为你自己的数据处理函数。
void UsbListener::run()
{
char tmp[BUF_SIZE];
int ret;
if(false == devOpenFlg)
{
return;
}
// Running a sync read test
while(1)
{
ret = usb_bulk_read(devHandle, EP_IN, tmp, sizeof(tmp), 5000);
if(ret > 0)
{
qDebug() << "success: bulk read " << ret << " bytes";
QString strTmp;
QString strOut;
for(int i = 0; i < ret; i++)
{
strTmp.sprintf("%02X ", (unsigned char)tmp[i]);
strOut.append(strTmp);
}
qDebug() << strOut;
managerData(tmp, ret); // Receive Data Proccess Function
}
}
}
要实现此功能,首先要做的是让Qt能够监听到Windows传来的消息,可以参考资料5的内容。具体的Windows的东西,我也不是特别清楚,所以就不细说了。还是那句话,我只是在说一种解决方案。
在主窗口下重载函数:nativeEvent(
)
mainwindow.h
protected:
bool nativeEvent(const QByteArray &eventType, void *message, long *result);
mainwindow.cpp
bool MainWindow::nativeEvent(const QByteArray &eventType, void *message, long *result)
{
MSG *msg = reinterpret_cast(message);
int msgType = msg->message;
if(msgType == WM_DEVICECHANGE)
{
switch(msg->wParam)
{
case DBT_DEVICEARRIVAL:
/* break; */
case DBT_DEVICEREMOVECOMPLETE:
/* break; */
case DBT_DEVNODES_CHANGED:
{
dev = usbListener->findUSBDev(USB_VID, USB_PID);
if(NULL != dev)
{
lStatusInfo->setText(tr("设备已插入!"));
}
else
{
ui->stackedWidget->setCurrentIndex(0);
lStatusInfo->setText(tr("设备已移除!"));
}
}
break;
default:
break;
}
}
return false;
}
这里有几个点需要注意:
1、查到过很多资料,都是讲的用winEvent()函数来处理,但Qt5.1已经没有此函数了
,参见c++ - Get HWND on windows with Qt5 (from WId) - Stack Overflow中的第5条。另外,此处我用的是QMainWindow基类,其它情况还是多留心处理。
2、在我测试的过程中,libusb-win32设备安装后,会在设备管理器中多一个libusb-win32 devices设备,如下图。
而在插入/拔出该设备时,收到WM_DEVICECHANGE(0x219)消息时,wParam参数内容只有DBT_DEVNODES_CHANGED(0x7),而测试其它的设备,如U盘,USB转串口线,USB烧录器,都能正常收到DBT_DEVICEARRIVAL和DBT_DEVICEREMOVECOMPLETE两个设备接入和移除的消息。这个应该是驱动的问题,因为用的是libusb-win32的驱动,所以不有原原本本的用Windows的监听流程(到底是不是如此,我也不清楚,呵呵~)。故此处在收到DBT_DEVNODES_CHANGED消息后,通过libusb对总线上的设备查询来实现该功能,这就是为什么要用到findUSBDev()的原因了。
3、在findUSBDev()函数中,需要在每次查找设备时,都调用usb_find_busses()和usb_find_devices()来更新总线上的设备信息。这样才能正常的查询到设备是连接在计算机上,还是未连接。否则设备的状态会出现错误。
这里的一个最大的问题就是驱动的问题,因为换一台没有安装过libusb驱动的计算机,接上设备后肯定无法使用我们辛辛苦苦编写的程序。所以我经过了一些资料查询与整理,还是解决了这个问题。主要思路如下:
静态编译Qt程序,减少一些对库依赖的问题;
用NSIS进行Qt程序和驱动打包;
发布给用户后,用户连接设备安装驱动时,要求其到指定的安装目录下的Driver目录下找驱动。若是同一款产品,换一台连接这台计算机,驱动会自动安装好。
1、静态编译
这个部分就不说了,网上有不少这个内容的帖子。配置这个环境还是挺麻烦的,我用台式机i5+4G内存,都编译了3个多小时才编译完静态库,不过还是有好处的,呵呵~
参考:Qt5.1.0 MinGW480 release静态版编译结果及过程分享 - QTCN开发网 - Powered by phpwind
我基本一次就能编译成功,没有遇上什么问题,所以也没办法提供太多的帮助了。
2、NSIS打包
参考资料6里有这个方面的使用说明。
我用的是NSIS3.0。因为NSIS是用脚本配置的方式来处理的,所以还是不太方便使用。不过也不难,对看参考资料,还有NSIS的帮助说明,基本没有大问题。使用上就不细说了,大致职下:
A. 文件准备:
USBConfig.exe —— 换成你自己写的程序
libusb0.dll —— 这个未包含在静态编译库中,属于第三方的东西,还是要拿过来,不然无法使用
License.txt —— 这个是必需要的文件,去找一个吧!我也不知道里边要什么样的内容
win.bmp —— 这个也是必需要的文件,SNIN安装目录下\Contrib\Graphics\Header目录里的win.bmp不错,关键是尺寸(150x57)合适。
Driver —— 这个目录当然就是安装libusb-win32驱动时保存下来的驱动内容,我曾经是想挑出几个必需的文件就行了,后来发现还是都保留的好。因为里面有好几种平台都支持的驱动,若只保留了自己的平台驱动(如win7 64bit),换一个平台后,原来libusb-win32是支持的,但是驱动文件夹下没有,这样就不好了。而且都保留,打包后的程序也没有多大。
HeaderBitmap.nsi —— 这个文件是从NSIS安装目录下拷贝出来的模板,路经为..\NSIS\Examples\Modern UI\下,当然要和你自己的环境相对应。
还有一些其它文件,例如帮助文档,我这没有,所就没有用了。对于驱动的安装,然后帮助文档还是不错的选择。
B. 修改HeaderBitmap.nis模板内容:
这个是关键的一步,控制着打包的内容,生成安装程序后的安装以及卸载过程等内容。这里用到的并不全面,全面的东西可以参考NSIS的帮助文档。
HeaderBitmap.nis可直接用记事本之类的工具进行编辑,但对中文的支持好像会有问题,我尝试过,但是中文要么是乱码,要么就不显示,还是用英文吧!也懒得去看英文帮助了,呵呵~ 若是你找到了这个问题的解决办法,别忘了告诉我哦!
;NSIS Modern User Interface
;Header Bitmap Example Script
;Written by Joost Verburg
;--------------------------------
;Include Modern UI
!include "MUI2.nsh"
;--------------------------------
;General
;Name and file
Name "USBConfig" ---- 程序名
OutFile "Setup.exe" ---- 生成的目标文件名
;Default installation folder
InstallDir "$PROGRAMFILE\USBConfig"
---- 安装的目标文件夹名称,这里要将前缀改成$ProgramFiles,不然会装到其它路经,不用担心64bit平台上目标目录的问题
---- $PROGRAMFILE是NSIS的环境变量,会自动映射到C:\Program Files或64平台的C:\Program Files (x86)目录下
;Get installation folder from registry if available
InstallDirRegKey HKCU "Software\USBConfig" "" ---- 这里是安装注册表的目录,修改对应名称,若是不想要安装注册表内容,则注释掉这行,还以后对的应该删除行
;Request application privileges for Windows Vista
RequestExecutionLevel user
;--------------------------------
;Interface Configuration
!define MUI_HEADERIMAGE
!define MUI_HEADERIMAGE_BITMAP "win.bmp" ; optional ---- 这里就用到了图片文件,是在生成的Setup.exe文件里的图标
!define MUI_ABORTWARNING
;--------------------------------
;Pages
!insertmacro MUI_PAGE_LICENSE "License.txt" ---- 这里就用到了License文件,我试过不用,但好像不行
!insertmacro MUI_PAGE_COMPONENTS
!insertmacro MUI_PAGE_DIRECTORY
!insertmacro MUI_PAGE_INSTFILES
!insertmacro MUI_UNPAGE_CONFIRM
!insertmacro MUI_UNPAGE_INSTFILES
;--------------------------------
;Languages
!insertmacro MUI_LANGUAGE "SimpChinese" ---- 语言版本,没太研究,好像是控制安装程序安装过程中的显示语言为中文
;--------------------------------
;Installer Sections
Section "Application and Help(required)" SecDummy ---- 安装的第一部分内容,用Section来划分,这里的内容是程序的内容
SetOutPath "$INSTDIR"
;ADD YOUR OWN FILES HERE...
File "USBConfig.exe" ---- 添加要打包进去的文件,这里没有打包win.bmp,若是打包进去,安装后在安装目录下也会有win.bmp文件,没有需要
File "License.txt"
File "libusb0.dll"
File /r "Driver" ---- 添加文件夹的方法,需要参数/r
;Store installation folder
WriteRegStr HKCU "Software\USBConfig" "" $INSTDIR ---- 这里是控制安装注册表,不需要则注释掉
;Create uninstaller
WriteUninstaller "$INSTDIR\Uninstall.exe" ---- 这里是生成卸载程序
SectionEnd
;--------------------------------
;Descriptions
;Language strings
LangString DESC_SecDummy ${LANG_ENGLISH} "Application and Help(required)" ---- 这里是语言配置,没有太研究
;Assign language strings to sections
!insertmacro MUI_FUNCTION_DESCRIPTION_BEGIN
!insertmacro MUI_DESCRIPTION_TEXT ${SecDummy} $(DESC_SecDummy)
!insertmacro MUI_FUNCTION_DESCRIPTION_END
;--------------------------------
; Optional section (can be disabled by the user)
Section "StartMenu ShortCuts" ---- 安装的第二部分,同样用Section来划分,这里的内容是添加“开始菜单”的菜单项
CreateDirectory "$SMPROGRAMS\USBConfig" ---- 开始菜单中安装的目录
CreateShortCut "$SMPROGRAMS\USBConfig\USBConfig.lnk" "$INSTDIR\USBConfig.exe" "" "$INSTDIR\USBConfig.exe" 0 ---- 目录中的快捷选项
CreateShortCut "$SMPROGRAMS\USBConfig\Uninstall.lnk" "$INSTDIR\Uninstall.exe" "" "$INSTDIR\Uninstall.exe" 0
SectionEnd
;--------------------------------
; Optional section (can be disabled by the user)
Section "Desktop ShortCuts" ---- 安装的第三部分,同样用Section来划分,这里的内容是添加“桌面”的快捷方式
CreateShortCut "$DESKTOP\USBConfig\USBConfig.lnk" "$INSTDIR\USBConfig.exe" "" "$INSTDIR\USBConfig.exe" 0
---- DESKTOP为NSIS的环境变量,会映射到系统桌面
SectionEnd
;--------------------------------
;Uninstaller Section
Section "Uninstall" ---- 这里是控制卸载程序工作的内容
;ADD YOUR OWN FILES HERE...
Delete "$INSTDIR\Uninstall.exe" ---- 卸载时应该删除的文件
Delete "$INSTDIR\USBConfig.exe"
Delete "$INSTDIR\License.txt"
Delete "$INSTDIR\libusb0.dll"
RMDir /r "$INSTDIR\Driver" ---- 卸载时应该删除的目录, /r参数是强制删除,不管其目录下是否有文件存在
RMDir "$INSTDIR" Delete "$SMPROGRAMS\USBConfig\USBConfig.lnk" ---- 卸载时删除开始菜单中的快捷选项
Delete "$SMPROGRAMS\USBConfig\Uninstall.lnk"
Delele "$DESKTOP\USBConfig.lnk" ---- 卸载时删除桌面快捷方式
RMDir "$SMPROGRAMS\USBConfig" ---- 卸载时删除开始菜单中的目录
DeleteRegKey /ifempty HKCU "Software\USBConfig" ---- 卸载时删除安装时安装的注册表内容,不需要时注释掉
SectionEnd
参考上面的注释说明,修改完成后即可使用NSIS软件进行打包。
打开NSIS,在主界面上选择"Compile NSI scripts",打开makeNSISW工具->选择"Open"->选择修改过的HeaderBitmap.nsi文件->makeNSISW工具自动进行打包,并根据配置文件的内容,在同文件夹下生成对应的安装程序,我的配置是生成Setup.exe,故生成的也就是该文件。
顺便提一句,若是配置文件错了,makeNSISW工具还是能检查出来,可以看工具的打印输出来修改调试脚本。参见如下三张图:
C、剩下的就是找另外一台没有libusb-win32环境的计算机去测试了,应该没问题的。
剩下的,就是祝你好运咿呀!哈哈~
P.S. 水平有限,高手就勿见怪了。