libusb 当前版本(1.0.26)libusb.h 头文件提供的接口似乎没有办法获取 Windows 平台相关的设备实例路径,其形如:
\\?\usb#vid_04ca&pid_7070#5&20d34a76&0&6#{a5dcbf10-6530-11d2-901f-00c04fb951ed}
只是提供了 libusb_get_port_numbers 之类的接口来获取拓扑结构。
我们可以通过 libusb 源码中平台相关的接口来获取DevicePath,但是使用非公有接口意味着替换版本的时候要注意源码相关的修改。目前找了两种方式:
1.通过 winusb_device_priv 结构体中的 path 获取
通过 libusbi.h 头文件中的 usbi_get_device_priv 函数获取 libusb_device 对应的平台相关的数据结构,对应到 windows 平台就是 winusb_device_priv,其包含的 path 字段就是存放DevicePath。
#include "libusb.h"
#include "libusbi.h"
void enum_device()
{
libusb_init(NULL);
libusb_device **device_list;
int count = libusb_get_device_list(NULL, &device_list);
for(int i = 0 ; i < count; i++)
{
libusb_device_descriptor desc;
int ret = libusb_get_device_descriptor(device_list[i], &desc);
if (ret != LIBUSB_SUCCESS) {
continue;
}
winusb_device_priv *priv = (winusb_device_priv*)usbi_get_device_priv(device_list[i]);
if (priv) {
fprintf(stderr, "Device %d path: %s.\n", i, priv->path);
}
}
libusb_free_device_list(device_list, 1);
libusb_exit(NULL);
}
从 usbi_get_device_priv 函数推测,公有结构和平台相关结构在内存上是紧挨着的:
#define PTR_ALIGN(v) (((v) + (sizeof(void *) - 1)) & ~(sizeof(void *) - 1))
static inline void *usbi_get_device_priv(struct libusb_device *dev)
{
return (unsigned char *)dev + PTR_ALIGN(sizeof(*dev));
}
获取 DevicePath 的过程在 windows_winusb.c 的 winusb_get_device_list 和 get_interface_details 函数,使用的 SetupAPI 系列的接口来获取的。有一点需要注意,获取到的 path 内部转换成了大写,见 normalize_path 函数实现。
不修改库源码,而是直接使用私有接口需要用到的源码文件:
其中 config.h 文件来自于源码的 msvc 文件夹。
可能会遇到类型转换报错,自己做下显式转换即可。
2.通过 libusb_device 结构体中的 session_data 和 Windows 的 DevInst 比较
session_data 在 windows 平台上目前就是存储的 SP_DEVINFO_DATA 结构的 DevInst 值。相较于第一种方式,我们需要再次用 SetupAPI 的接口枚举一遍设备信息,然后判断 session_data 和 DevInst 是否相等,相等时就可以用 Win32 接口获取 DevicePath 了。
libusb_device 结构体定义在 libusbi.h 头文件。
#include <stdio.h>
#include <Windows.h>
#include <SetupAPI.h>
#pragma comment(lib, "SetupAPI.lib")
#include <devguid.h>
// 具体的设备 GUID 需要 initguid, 如 usbiodef
#include <initguid.h>
// USB 设备
// GUID_DEVINTERFACE_USB_DEVICE
#include <usbiodef.h>
void enum_device()
{
// HDEVINFO 标识设备信息集
HDEVINFO info_set;
// SetupDiGetClassDevs 返回包含本地计算机请求的设备信息元素的设备信息集的句柄
// 接口文档:https://learn.microsoft.com/zh-cn/windows/win32/api/setupapi/nf-setupapi-setupdigetclassdevsa
// 若要返回支持任何类的设备接口的设备,设置 DIGCF_DEVICEINTERFACE 和 DIGCF_ALLCLASSES 标志,然后将 ClassGuid 设置为 NULL
//info_set = SetupDiGetClassDevsA(NULL, NULL, NULL, DIGCF_ALLCLASSES | DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
// 若要仅返回支持指定类的设备接口的设备,设置 DIGCF_DEVICEINTERFACE 标志并使用 ClassGuid 参数提供设备接口类的类 GUID
// 实际使用对应设备的 GUID
GUID device_guid{GUID_DEVINTERFACE_USB_DEVICE};
info_set = SetupDiGetClassDevsA(&device_guid, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
// 如果操作失败,返回 INVALID_HANDLE_VALUE
if (info_set == INVALID_HANDLE_VALUE) {
fprintf(stderr, "SetupDiGetClassDevs: [err code] %d.\n", GetLastError());
return;
}
// SP_DEVINFO_DATA 标识设备信息集中的设备
SP_DEVINFO_DATA info_data = { 0 };
info_data.cbSize = sizeof(info_data);
// SetupDiEnumDeviceInfo 枚举设备信息
for (int index = 0; SetupDiEnumDeviceInfo(info_set, index, &info_data); index++)
{
fprintf(stderr, "Device %d %d:\n", index, info_data.DevInst);
// 获取设备实例路径
// SP_DEVICE_INTERFACE_DATA 设备信息集中的设备接口
SP_DEVICE_INTERFACE_DATA interface_data = { 0 };
interface_data.cbSize = sizeof(interface_data);
// SetupDiEnumDeviceInterfaces 枚举包含在设备信息集中的设备接口
BOOL ret = SetupDiEnumDeviceInterfaces(info_set, NULL, (LPGUID)&device_guid, index, &interface_data);
if (!ret) continue;
ULONG required_len = 0;
// SetupDiGetDeviceInterfaceDetail 返回有关设备接口的详细信息
// 第一次调用是获取长度,这里是返回false
SetupDiGetDeviceInterfaceDetailA(info_set, &interface_data, NULL, 0, &required_len, NULL);
if (required_len <= 0) continue;
ULONG predicted_len = required_len;
// SP_INTERFACE_DEVICE_DETAIL_DATA 包含设备接口的路径
SP_INTERFACE_DEVICE_DETAIL_DATA_A detail_data = { 0 };
detail_data.cbSize = sizeof(SP_INTERFACE_DEVICE_DETAIL_DATA_A);
// 检索即插即用设备信息
if (SetupDiGetDeviceInterfaceDetailA(info_set,
&interface_data,
&detail_data,
predicted_len,
&required_len,
&info_data)) {
fprintf(stderr, "Device Instance Path: %s.\n", detail_data.DevicePath);
}
}
// SetupDiDestroyDeviceInfoList 删除设备信息集并释放所有关联的内存
SetupDiDestroyDeviceInfoList(info_set);
}