在 QT 中使用 libusb 检测 MAC 上的 USB 设备

最近在用 QT 做一个 MAC 上的 Kindle 批注管理软件,遇到的第一个问题就是检测 MAC 上连接的 USB 设备的状态。如果是在 Cocoa 进行开发,会有对应的系统 API 可供使用,但是由于我是在 QT 平台进行的开发,所以无形中加大了一点难度。就在这时,我发现了一个库:libusb

libusb 介绍

libusb 设计了一系列的外部API 为应用程序所调用,通过这些API应用程序可以操作硬件,从libusb的源代码可以看出,这些API 调用了内核的底层接口,和kernel driver中所用到的函数所实现的功能差不多,只是libusb更加接近USB 规范。使得libusb的使用也比开发内核驱动相对容易的多。(From: 百度百科

0x00 下载 libusb

在 libusb 项目主页(http://libusb.info)我们可以找到最新的源码,下载下来,并且解压。这里我下载的是 libusb-1.0.20.tar.bz2,把它解压出来。

0x01 安装 libusb

1
2
3
4
cd libusb-1.0.20/
./configure
make
make install

这时就已经在机器上编译安装完成了 libusb

0x02 运行示例程序

1
2
cd examples/
make

然后我们看到在 examples/ 目录下多了几个可执行程序:

  • listdevs:列出当前所有的 USB 设备

  • hotplugtest:USB 热插拔测试

  • dpfp_threaded:操作 U.are.U 4000b 指纹采集仪的 Demo

  • dpfp:初始化 U.are.U 4000b 指纹采集仪

  • sam3u_benchmark:测试 Atmel SAM3U USB 主控的同步传输的性能的 Demo

  • fxload:USB 固件操作

1
2
3
4
5
6
7
8
9
10
Usage: fxload [-v] [-V] [-t type] [-d vid:pid] [-p bus,addr] [-s loader] -i firmware

-i <path> -- Firmware to upload
-s <path> -- Second stage loader
-t <type> -- Target type: an21, fx, fx2, fx2lp, fx3
-d <vid:pid> -- Target device, as an USB VID:PID
-p <bus,addr> -- Target device, as a libusb bus number and device address path
-v -- Increase verbosity
-q -- Decrease verbosity (silent mode)
-V -- Print program version
  • xusb:USB 测试程序
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
usage: /Users/jason/Downloads/libusb-1.0.20/examples/.libs/xusb [-h] [-d] [-i] [-k] [-b file] [-l lang] [-j] [-x] [-s] [-p] [-w] [vid:pid]

-h : display usage
-d : enable debug output
-i : print topology and speed info
-j : test composite FTDI based JTAG device
-k : test Mass Storage device
-b file : dump Mass Storage data to file 'file'
-p : test Sony PS3 SixAxis controller
-s : test Microsoft Sidewinder Precision Pro (HID)
-x : test Microsoft XBox Controller Type S
-l lang : language to report errors in (ISO 639-1)
-w : force the use of device requests when querying WCID descriptors

If only the vid:pid is provided, xusb attempts to run the most appropriate test

我们以 listdevs 为例,执行测试程序:

1
./listdevs

执行结果:

1
2
3
4
05ac:8406 (bus 20, device 3) path: 7
05ac:828f (bus 20, device 20) path: 3.3
0a5c:4500 (bus 20, device 27) path: 3
05ac:8005 (bus 20, device 0)

链接库

在Windows平台、MAC 平台和Linux平台下都大量存在着库。本质上来说库是一种可执行代码的二进制形式,可以被操作系统载入内存执行。
由于Windows、MAC和Linux的平台不同(主要是编译器、汇编器和连接器的不同),因此二者库的二进制是不兼容的。

Linux下的库有两种:静态库共享库(动态库)。二者的不同点在于代码被载入的时刻不同。

静态库的代码在编译过程中已经被载入可执行程序,因此体积较大。

共享库的代码是在可执行程序运行时才载入内存的,在编译过程中仅简单的引用,因此代码体积较小。

创建静态链接库

0x00 写一个静态链接库

  • hello.h
1
2
3
4
5
6
#ifndef HELLO_H 
#define HELLO_H

void hello(const char *name);

#endif
  • hello.c
1
2
3
4
#include <stdio.h> 
void hello(const char *name) {
printf("Hello %s!\n", name);
}
  • main.c
1
2
3
4
5
6
#include "hello.h" 
int main()
{
hello("world");
return 0;
}

0x01 创建目标代码

1
gcc -c hello.c

gcc 中 -c 的编译选项的意思是使用GNU汇编器将源文件转化为目标代码之后就结束,在这种情况下,只调用了C编译器(ccl)和汇编器(as),而连接器(ld)并没有被执行,所以输出的目标文件不会包含作为程序在被装载和执行时所必须的包含信息,但它可以在以后被连接到一个程序。

我们执行 ls 命令会看到目录下多了一个 hello.o 文件,它就是 hello.c 编译的目标代码

0x02 创建静态链接库

1
ar rcs libhello.a hello.o

创建静态库用ar命令。

静态库文件名的命名规范是以lib为前缀,紧接着跟静态库名,扩展名为.a。

例如:我们将创建的静态库名为hello,则静态库文件名就是libhello.a。在创建和使用静态库时,需要注意这点。

我们执行 ls 命令,可以看到目录下多了一个静态链接库 libhello.a

0x03 链接静态链接库

静态库制作完了,如何使用它内部的函数呢?

只需要在使用到这些公用函数的源程序中包含这些公用函数的原型声明,然后在用gcc命令生成目标文件时指明静态库名,gcc将会从静态库中将公用函数连接到目标文件中。

注意,gcc会在静态库名前加上前缀lib,然后追加扩展名.a得到的静态库文件名来查找静态库文件,因此,我们在写需要连接的库时,只写名字就可以,如libhello.a的库,只写: -lhello

1
gcc -o main main.c -L. -lhello

执行程序

1
2
./main
Hello world!

创建动态链接库

0x00 创建动态链接库

1
gcc -dynamiclib -o hello.dylib hello.o

我们使用 ls 命令可以看到目录下多了 hello.dylib,它就是创建的动态链接库(.dylib是 MAC 系统下的,Windows 下是.dll, Linux 下是.so)

0x00 链接动态链接库

1
gcc -o main1 main.c -L. -lhello

执行程序

1
2
./main1
Hello world!

在 QT 中使用 libusb

0x00 将 libusb 动态链接库加入 QT 项目

我们首先在 QT5 中新建一个项目 Testlibusb,然后在项目目录下新建一个目录 lib 用来存放 libusb 的库文件。

将 libusb 对应的库文件复制到该目录下,因为我所使用的平台是 MAC OS X,所对应的库文件应当是以 .dylib 为扩展名的,我们在 libusb 源码文件夹下的 /libusb/.libs/ 目录下找到 libusb-1.0.0.dylib 然后复制到刚刚创建的目录下

并且将 libusb 的头文件 libusb.h 加入到项目中

0x01 修改 QT 项目编译选项

修改 QT 项目中的 .pro 文件,加入下面几行:

1
2
3
4
5
macx: LIBS += -L$$PWD/lib/ -lusb-1.0.0

INCLUDEPATH += $$PWD/.

DEPENDPATH += $$PWD/.

0x02 编写 libusb 测试程序

  • getusbinfo.h
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
#ifndef GETUSBINFO
#define GETUSBINFO

#include <QString>
#include <QObject>
#include <QList>
#include <QThread>

#include <libusb.h>

struct STUUSBDevices{
QString idProduct;
QString idVendor;
QString iManufacturer;
QString iSerialNumber;
};

class GetUsbInfo : QThread{
public:
GetUsbInfo(QObject *parent);
~GetUsbInfo();
int initUsbDevices();
QString getVidPid(libusb_device **devs);
void showAllUsbDevices(QList<STUUSBDevices> lst);
void setRunStatus();
void run();

bool isStop;

};

#endif // GETUSBINFO
  • getusbinfo.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
#include "getusbinfo.h"
#include <QThread>
#include <QDebug>
#include <QString>


GetUsbInfo::GetUsbInfo(QObject *parent) :
QThread(parent),isStop(false)
{
}

GetUsbInfo::~GetUsbInfo()
{
qDebug()<<"GetUsbInfo::~GetUsbInfo "<<endl;
}

int GetUsbInfo::initUsbDevices()
{
libusb_device **devs;
int r;
ssize_t cnt;

r = libusb_init(NULL);
if (r < 0)
return r;

cnt = libusb_get_device_list(NULL, &devs);
if (cnt < 0)
return (int) cnt;

getVidPid(devs);
libusb_free_device_list(devs, 1);

libusb_exit(NULL);
return 0;
}

QString GetUsbInfo::getVidPid(libusb_device **devs)
{
libusb_device *dev;
int i = 0;
QList<STUUSBDevices> lstUsb;
while ((dev = devs[i++]) != NULL) {
struct libusb_device_descriptor desc;
int r = libusb_get_device_descriptor(dev, &desc);
if (r < 0) {
qDebug()<<"failed to get device descriptor"<<stderr;
return "";
}
printf("%04x:%04x (bus %d, device %d)\n",
desc.idVendor, desc.idProduct,
libusb_get_bus_number(dev), libusb_get_device_address(dev));
STUUSBDevices stu;
stu.idProduct = QString::number(desc.idProduct);
stu.idVendor = QString::number(desc.idVendor);
stu.iManufacturer = QString::number(desc.iManufacturer);
stu.iSerialNumber = QString::number(desc.iSerialNumber);

lstUsb.append(stu);
}
showAllUsbDevices(lstUsb);
return QString(lstUsb[0].idProduct);
}

void GetUsbInfo::showAllUsbDevices(QList<STUUSBDevices> lst)
{
for(int i=0;i<lst.count();i++)
{
qDebug()<<"vid: "<<lst.at(i).idVendor<<"\n"
<<"pid:"<<lst.at(i).idProduct<<"\n"
<<"serNumber:"<<lst.at(i).iSerialNumber<<"\n"
<<"Manufacturer:"<<lst.at(i).iManufacturer<<"\n";
}
}

void GetUsbInfo::setRunStatus()
{
isStop = true;
}

void GetUsbInfo::run()
{
qDebug()<<"GetUsbInfo::run() "<<endl;
while (!isStop)
{
initUsbDevices();
sleep(10);
}
}
  • main.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
#include "mainwindow.h"
#include <QApplication>
#include "getusbinfo.h"

#include <QLibrary>

int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();

QThread t;

GetUsbInfo info(&t);

info.initUsbDevices();

return a.exec();
}

```

## 0x03 执行 libusb 测试程序

```bash
Starting /Users/jason/Project/QTDemos/build-Testlibusb-Desktop_Qt_5_5_1_clang_64bit-Debug/Testlibusb.app/Contents/MacOS/Testlibusb...
vid: "1452"
pid: "33798"
serNumber: "5"
Manufacturer: "3"

vid: "1452"
pid: "33423"
serNumber: "0"
Manufacturer: "1"

vid: "2652"
pid: "17664"
serNumber: "0"
Manufacturer: "1"

vid: "1452"
pid: "32773"
serNumber: "0"
Manufacturer: "0"

最终我们获得了当前 MAC 上的 USB 设备列表


本文的版权归作者 罗远航 所有,采用 Attribution-NonCommercial 3.0 License。任何人可以进行转载、分享,但不可在未经允许的情况下用于商业用途;转载请注明出处。感谢配合!