windows USB 了解

GUID

GUID 是一个 128 位的数字,在全球范围内是独一无二的,常被用于标识软件组件、设备接口等,以保证在不同系统和环境中能唯一识别特定对象。

cpp 复制代码
DEFINE_GUID(GUID_DEVINTERFACE_USCUSTOMKEYS, 0x12345678, 0x1234, 0x5678, 0x12, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78);

DEFINE_GUID:这是一个宏,在 Windows 编程里用于定义 GUID。它在 <guiddef.h> 头文件中被定义,此宏会创建一个 GUID 类型的全局变量,并且对其进行初始化。

GUID_DEVINTERFACE_USCUSTOMKEYS:这是所定义的 GUID 的名称。在后续代码里,可借助这个名称来引用该 GUID。

0x12345678, 0x1234, 0x5678, 0x12, 0x12, 0x23, 0x34, 0x45, 0x56, 0x67, 0x78:这些是构成 128 位 GUID 的具体数值。GUID 通常会被拆分成几个部分,以便于表示和处理。

枚举设备

cpp 复制代码
#include <windows.h>
#include <iostream>
#include <setupapi.h>
#include <devguid.h>
#include <regstr.h>
#include <tchar.h>

#pragma comment(lib, "setupapi.lib")

// 这里使用 USB 设备类的 GUID 作为示例,你可以根据需要替换为其他设备类的 GUID
DEFINE_GUID(GUID_DEVINTERFACE_USB_DEVICE, 0xA5DCBF10L, 0x6530, 0x11D2, 0x90, 0x1F, 0x00, 0xC0, 0x4F, 0xB9, 0x51, 0xED);

int main() {
    HDEVINFO hDevInfo;
    SP_DEVICE_INTERFACE_DATA deviceInterfaceData = { 0 };
    PSP_DEVICE_INTERFACE_DETAIL_DATA deviceInterfaceDetailData = NULL;
    DWORD requiredSize = 0;
    DWORD index = 0;

    // 获取指定设备类的设备信息集
    hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_USB_DEVICE, NULL, NULL, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    if (hDevInfo == INVALID_HANDLE_VALUE) {
        std::cerr << "SetupDiGetClassDevs failed with error: " << GetLastError() << std::endl;
        return 1;
    }

    // 初始化 SP_DEVICE_INTERFACE_DATA 结构体
    deviceInterfaceData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA);

    // 枚举设备接口
    while (SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_USB_DEVICE, index, &deviceInterfaceData)) {
        // 获取所需的缓冲区大小
        SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, NULL, 0, &requiredSize, NULL);

        // 分配缓冲区
        deviceInterfaceDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA)LocalAlloc(LPTR, requiredSize);
        if (deviceInterfaceDetailData == NULL) {
            std::cerr << "LocalAlloc failed with error: " << GetLastError() << std::endl;
            SetupDiDestroyDeviceInfoList(hDevInfo);
            return 1;
        }
        deviceInterfaceDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA);

        // 获取设备接口的详细信息
        if (!SetupDiGetDeviceInterfaceDetail(hDevInfo, &deviceInterfaceData, deviceInterfaceDetailData, requiredSize, NULL, NULL)) {
            std::cerr << "SetupDiGetDeviceInterfaceDetail failed with error: " << GetLastError() << std::endl;
            LocalFree(deviceInterfaceDetailData);
            index++;
            continue;
        }

        // 打印设备接口的详细信息
        std::wcout << L"Device Path: " << deviceInterfaceDetailData->DevicePath << std::endl;

        // 释放缓冲区
        LocalFree(deviceInterfaceDetailData);
        index++;
    }

    // 检查枚举是否因错误而终止
    if (GetLastError() != ERROR_NO_MORE_ITEMS) {
        std::cerr << "SetupDiEnumDeviceInterfaces failed with error: " << GetLastError() << std::endl;
    }

    // 销毁设备信息集
    SetupDiDestroyDeviceInfoList(hDevInfo);

    return 0;
}    

SetupDiGetClassDevs

cpp 复制代码
HDEVINFO SetupDiGetClassDevs(
  const GUID          *ClassGuid,
  PCTSTR              Enumerator,
  HWND                hwndParent,
  DWORD               Flags
);

功能:此函数会创建一个包含指定设备类的设备信息集,该集合可用来枚举和操作设备。

参数:

ClassGuid:指向设备类的 GUID(全局唯一标识符)的指针,指定要枚举的设备类。若为 NULL,则会返回所有设备类的设备。

Enumerator:指定设备枚举器名称的字符串,如 USB 或 PCI。若为 NULL,则不限制枚举器。

hwndParent:与设备安装程序相关的父窗口句柄,通常设为 NULL。

Flags:控制函数行为的标志位,常用标志有:

DIGCF_PRESENT:仅返回当前存在的设备。

DIGCF_DEVICEINTERFACE:返回支持指定设备接口类的设备。

返回值:若函数调用成功,会返回设备信息集的句柄;若失败,则返回 INVALID_HANDLE_VALUE。

SetupDiEnumDeviceInterfaces

cpp 复制代码
BOOL SetupDiEnumDeviceInterfaces(
  HDEVINFO                 DeviceInfoSet,
  PSP_DEVINFO_DATA         DeviceInfoData,
  const GUID               *InterfaceClassGuid,
  DWORD                    MemberIndex,
  PSP_DEVICE_INTERFACE_DATA DeviceInterfaceData
);

功能:对设备信息集中的设备接口进行枚举。

参数:

DeviceInfoSet:由 SetupDiGetClassDevs 函数返回的设备信息集的句柄。

DeviceInfoData:指向 SP_DEVINFO_DATA 结构体的指针,用于指定特定设备。若为 NULL,则枚举所有设备。

InterfaceClassGuid:指向设备接口类的 GUID 的指针,指定要枚举的接口类。

MemberIndex:要枚举的设备接口的索引,从 0 开始。

DeviceInterfaceData:指向 SP_DEVICE_INTERFACE_DATA 结构体的指针,用于接收枚举到的设备接口信息。

返回值:若函数调用成功,返回 TRUE;若失败或没有更多设备接口可供枚举,返回 FALSE,此时可通过 GetLastError 函数获取错误代码。若错误代码为 ERROR_NO_MORE_ITEMS,则表示枚举结束。

SetupDiGetInterfaceDeviceDetail

cpp 复制代码
BOOL SetupDiGetInterfaceDeviceDetail(
  HDEVINFO                         DeviceInfoSet,
  PSP_DEVICE_INTERFACE_DATA        DeviceInterfaceData,
  PSP_DEVICE_INTERFACE_DETAIL_DATA DeviceInterfaceDetailData,
  DWORD                            DeviceInterfaceDetailDataSize,
  PDWORD                           RequiredSize,
  PSP_DEVINFO_DATA                 DeviceInfoData
);

功能:获取指定设备接口的详细信息,像设备路径之类的。

参数:

DeviceInfoSet:由 SetupDiGetClassDevs 函数返回的设备信息集的句柄。

DeviceInterfaceData:指向 SP_DEVICE_INTERFACE_DATA 结构体的指针,包含要获取详细信息的设备接口信息。

DeviceInterfaceDetailData:指向 SP_DEVICE_INTERFACE_DETAIL_DATA 结构体的指针,用于接收设备接口的详细信息。

DeviceInterfaceDetailDataSize:DeviceInterfaceDetailData 结构体的大小。

RequiredSize:指向 DWORD 类型变量的指针,用于接收所需的缓冲区大小。

DeviceInfoData:指向 SP_DEVINFO_DATA 结构体的指针,用于接收设备的详细信息。若为 NULL,则不接收该信息。

返回值:若函数调用成功,返回 TRUE;若失败,返回 FALSE,可通过 GetLastError 函数获取错误代码。若所需的缓冲区大小超过 DeviceInterfaceDetailDataSize,则返回 FALSE,且 RequiredSize 会包含所需的缓冲区大小。

SetupDiDestroyDeviceInfoList

cpp 复制代码
BOOL SetupDiDestroyDeviceInfoList(
  HDEVINFO DeviceInfoSet
);

功能:销毁由 SetupDiGetClassDevs 函数创建的设备信息集,释放相关资源。

参数:

DeviceInfoSet:要销毁的设备信息集的句柄。

返回值:若函数调用成功,返回 TRUE;若失败,返回 FALSE,可通过 GetLastError 函数获取错误代码。

总结

这些函数通常按以下顺序使用:

调用 SetupDiGetClassDevs 函数创建设备信息集。

利用 SetupDiEnumDeviceInterfaces 函数枚举设备信息集中的设备接口。

针对每个枚举到的设备接口,调用 SetupDiGetInterfaceDeviceDetail 函数获取详细信息。

最后调用 SetupDiDestroyDeviceInfoList 函数销毁设备信息集,释放资源。

总结

这些函数通常按以下顺序使用:

调用 SetupDiGetClassDevs 函数创建设备信息集。

利用 SetupDiEnumDeviceInterfaces 函数枚举设备信息集中的设备接口。

针对每个枚举到的设备接口,调用 SetupDiGetInterfaceDeviceDetail 函数获取详细信息。

最后调用 SetupDiDestroyDeviceInfoList 函数销毁设备信息集,释放资源。

注意

需要注意环境的编码格式,根据实际使用选择

开打管道

cpp 复制代码
m_hReadPipe = CreateFile((LPCSTR)m_ReadPipe, GENERIC_READ | GENERIC_WRITE, FILE_SHARE_READ | FILE_SHARE_WRITE,
        NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, 0);

CreateFile 是 Windows API 中的一个函数,用于创建或打开文件、目录、物理磁盘、卷、控制台缓冲区、通信资源、邮件槽、管道等对象。此函数会返回一个句柄,后续可使用该句柄对这些对象进行操作。

  1. (LPCSTR)m_ReadPipe

    含义:这是要打开的对象的名称,在该代码里为管道的名称。m_ReadPipe 应当是一个字符串类型的变量,代表管道的名称。(LPCSTR) 是一个强制类型转换,将 m_ReadPipe 转换为 LPCSTR 类型(即指向以 null 结尾的多字节字符串的指针)。

    注意:在 Unicode 字符集环境下,建议使用 CreateFileW 函数并传入 LPCWSTR 类型的参数,以避免字符编码问题。

  2. GENERIC_READ | GENERIC_WRITE

    含义:这是访问权限标志,用于指定对打开对象的访问方式。

    GENERIC_READ:表示对对象具有读权限,也就是可以从对象中读取数据。

    GENERIC_WRITE:表示对对象具有写权限,即能够向对象中写入数据。

    GENERIC_READ | GENERIC_WRITE:表示同时具有读和写的权限。

  3. FILE_SHARE_READ | FILE_SHARE_WRITE

    含义:这是共享模式标志,用于指定其他进程对该对象的访问权限。

    FILE_SHARE_READ:允许其他进程以读权限打开该对象。

    FILE_SHARE_WRITE:允许其他进程以写权限打开该对象。

    FILE_SHARE_READ | FILE_SHARE_WRITE:表示其他进程可以同时以读和写的权限打开该对象。

  4. NULL

    含义:这是指向 SECURITY_ATTRIBUTES 结构体的指针,用于指定对象的安全属性。若为 NULL,则表示使用默认的安全属性,并且该对象的句柄不能被子进程继承。

  5. OPEN_EXISTING

    含义:这是创建方式标志,用于指定如果对象不存在时的处理方式。OPEN_EXISTING 表示仅当对象已经存在时才打开它,如果对象不存在,函数将失败并返回 INVALID_HANDLE_VALUE。

  6. FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED

    含义:这是文件属性和标志的组合。

    FILE_ATTRIBUTE_NORMAL:表示文件没有特殊的属性,是最常见的文件属性。

    FILE_FLAG_OVERLAPPED:表示以异步(重叠)方式打开文件。在异步模式下,对文件的读写操作会立即返回,而不会等待操作完成,可通过 OVERLAPPED 结构体来处理操作结果。

  7. 0

    含义:这是模板文件的句柄,用于指定一个模板文件的句柄,新创建的文件将继承该模板文件的属性。若为 0,则表示不使用模板文件。

返回值

若函数调用成功,会返回一个有效的句柄,该句柄可用于后续对管道的读写操作。

若失败,会返回 INVALID_HANDLE_VALUE,可通过 GetLastError 函数获取具体的错误代码。

不要忘记还有 CloseHandle(m_hReadPipe);

CloseHandle 是 Windows API 中的一个函数,它的主要作用是关闭对象的句柄。在 Windows 操作系统里,许多系统资源(像文件、管道、线程、进程等)都会用句柄来标识,当不再需要使用这些资源时,就需要调用 CloseHandle 函数来关闭对应的句柄,从而释放系统资源。

异步读取

cpp 复制代码
#include <windows.h>
#include <iostream>
#include <string>

const char* WRITE_PIPE_NAME = "\\\\.\\pipe\\Pipe0";
const char* READ_PIPE_NAME = "\\\\.\\pipe\\Pipe1";
const int BUFFER_SIZE = 1024;
const int WRITE_INTERVAL_MS = 3000;
const int MAIN_LOOP_INTERVAL_MS = 100; // 主循环间隔设为 100 毫秒

void WriteAsync(HANDLE hPipe, int writeCount, HANDLE hWriteEvent) {
    OVERLAPPED overlapped = { 0 };
    overlapped.hEvent = hWriteEvent;

    std::string message = "Data write number " + std::to_string(writeCount);
    DWORD bytesWritten;
    if (!WriteFile(hPipe, message.c_str(), static_cast<DWORD>(message.length()), NULL, &overlapped)) {
        if (GetLastError() != ERROR_IO_PENDING) {
            std::cerr << "WriteFile failed: " << GetLastError() << std::endl;
            return;
        }
    }

    DWORD waitResult = WaitForSingleObject(hWriteEvent, INFINITE);
    if (waitResult == WAIT_OBJECT_0) {
        if (!GetOverlappedResult(hPipe, &overlapped, &bytesWritten, FALSE)) {
            std::cerr << "GetOverlappedResult failed: " << GetLastError() << std::endl;
        } else {
            std::cout << "Asynchronous write completed. Bytes written: " << bytesWritten << std::endl;
        }
        ResetEvent(hWriteEvent);
    } else {
        std::cerr << "WaitForSingleObject failed: " << waitResult << std::endl;
    }
}

void ReadAsync(HANDLE hPipe, HANDLE hReadEvent) {
    OVERLAPPED overlapped = { 0 };
    overlapped.hEvent = hReadEvent;

    char buffer[BUFFER_SIZE];
    DWORD bytesRead;
    if (!ReadFile(hPipe, buffer, BUFFER_SIZE, NULL, &overlapped)) {
        if (GetLastError() != ERROR_IO_PENDING) {
            std::cerr << "ReadFile failed: " << GetLastError() << std::endl;
            return;
        }
    }

    DWORD waitResult = WaitForSingleObject(hReadEvent, INFINITE);
    if (waitResult == WAIT_OBJECT_0) {
        if (!GetOverlappedResult(hPipe, &overlapped, &bytesRead, FALSE)) {
            std::cerr << "GetOverlappedResult failed: " << GetLastError() << std::endl;
        } else if (bytesRead > 0) {
            buffer[bytesRead] = '\0';
            std::cout << "Asynchronous read completed. Bytes read: " << bytesRead << std::endl;
            std::cout << "Read data: " << buffer << std::endl;
        }
        ResetEvent(hReadEvent);
    } else {
        std::cerr << "WaitForSingleObject failed: " << waitResult << std::endl;
    }
}

int main() {
    HANDLE hWritePipe = CreateFile(
        WRITE_PIPE_NAME,
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL
    );

    if (hWritePipe == INVALID_HANDLE_VALUE) {
        std::cerr << "CreateFile for write pipe failed: " << GetLastError() << std::endl;
        return 1;
    }

    HANDLE hReadPipe = CreateFile(
        READ_PIPE_NAME,
        GENERIC_READ,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL
    );

    if (hReadPipe == INVALID_HANDLE_VALUE) {
        std::cerr << "CreateFile for read pipe failed: " << GetLastError() << std::endl;
        CloseHandle(hWritePipe);
        return 1;
    }

    HANDLE hWriteEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hWriteEvent == NULL) {
        std::cerr << "CreateEvent for write failed: " << GetLastError() << std::endl;
        CloseHandle(hWritePipe);
        CloseHandle(hReadPipe);
        return 1;
    }

    HANDLE hReadEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
    if (hReadEvent == NULL) {
        std::cerr << "CreateEvent for read failed: " << GetLastError() << std::endl;
        CloseHandle(hWriteEvent);
        CloseHandle(hWritePipe);
        CloseHandle(hReadPipe);
        return 1;
    }

    int writeCount = 1;
    DWORD lastWriteTime = GetTickCount();

    while (true) {
        DWORD currentTime = GetTickCount();
        if (currentTime - lastWriteTime >= WRITE_INTERVAL_MS) {
            WriteAsync(hWritePipe, writeCount++, hWriteEvent);
            lastWriteTime = currentTime;
        }

        ReadAsync(hReadPipe, hReadEvent);
        Sleep(MAIN_LOOP_INTERVAL_MS);
    }

    CloseHandle(hWriteEvent);
    CloseHandle(hReadEvent);
    CloseHandle(hWritePipe);
    CloseHandle(hReadPipe);
    return 0;
}    

Pipe0 每 3 秒写入一次数据,同时在主循环中以更短的时间间隔尝试从 Pipe1 读取数据,且写入和读取操作分别使用独立的事件对象。

CreateEvent

创建或打开一个命名或未命名的事件对象,事件对象是一种同步对象,用于线程间或进程间的同步,可用来通知其他线程某个操作已经完成。

常用于异步操作中,当异步操作完成时,将事件对象设置为有信号状态,通知其他线程操作已完成。

cpp 复制代码
HANDLE CreateEvent(
  LPSECURITY_ATTRIBUTES lpEventAttributes,
  BOOL                  bManualReset,
  BOOL                  bInitialState,
  LPCTSTR               lpName
);

lpEventAttributes:指向 SECURITY_ATTRIBUTES 结构体的指针,该结构体决定返回的句柄是否可被子进程继承。若为 NULL,则句柄不能被继承。

bManualReset:指定事件对象的复位方式。若为 TRUE,则为手动复位事件,需要调用 ResetEvent 函数将其设置为无信号状态;若为 FALSE,则为自动复位事件,当一个等待线程被释放后,系统会自动将其设置为无信号状态。

bInitialState:指定事件对象的初始状态。若为 TRUE,则初始状态为有信号状态;若为 FALSE,则初始状态为无信号状态。

lpName:指定事件对象的名称,若为 NULL,则创建一个未命名的事件对象。

返回值

若函数成功,返回事件对象的句柄;若失败,返回 NULL,可通过 GetLastError 函数获取错误代码。

WaitForSingleObject

等待指定的对象(如事件、互斥体、信号量等)变为有信号状态,或者等待指定的时间间隔过去。

在异步操作中,用于等待异步操作完成的通知(即事件对象变为有信号状态)。

cpp 复制代码
DWORD WaitForSingleObject(
  HANDLE hHandle,
  DWORD  dwMilliseconds
);

参数

hHandle:要等待的对象的句柄。

dwMilliseconds:等待的时间间隔,以毫秒为单位。若为 INFINITE,则表示无限期等待,直到对象变为有信号状态。

返回值

返回一个指示等待结果的 DWORD 值,常见的返回值有:

WAIT_OBJECT_0:表示等待的对象变为有信号状态。

WAIT_TIMEOUT:表示等待的时间间隔已过,对象仍未变为有信号状态。

ResetEvent

cpp 复制代码
BOOL ResetEvent(
  HANDLE hEvent
);

功能

将指定的事件对象设置为无信号状态。

参数

hEvent:要设置的事件对象的句柄。

返回值

若函数成功,返回 TRUE;若失败,返回 FALSE,可通过 GetLastError 函数获取错误代码。

使用场景

对于手动复位事件,在处理完事件通知后,需要调用该函数将事件对象重置为无信号状态,以便下次使用。

GetOverlappedResult

cpp 复制代码
BOOL GetOverlappedResult(
  HANDLE       hFile,
  LPOVERLAPPED lpOverlapped,
  LPDWORD      lpNumberOfBytesTransferred,
  BOOL         bWait
);

功能

获取指定的异步(重叠)操作的结果,包括操作是否成功以及传输的字节数。

参数

hFile:进行异步操作的对象的句柄,如文件、管道等。

lpOverlapped:指向 OVERLAPPED 结构体的指针,该结构体包含了异步操作的相关信息。

lpNumberOfBytesTransferred:指向 DWORD 变量的指针,用于接收异步操作传输的字节数。

bWait:指定函数是否等待异步操作完成。若为 TRUE,则函数会等待操作完成;若为 FALSE,则若操作尚未完成,函数会立即返回 FALSE。

返回值

若函数成功,返回 TRUE;若失败,返回 FALSE,可通过 GetLastError 函数获取错误代码。

使用场景

在异步操作中,当事件对象变为有信号状态,表示异步操作可能已经完成,此时可调用该函数获取操作的具体结果。

相关推荐
Java手札35 分钟前
Windows下Golang与Nuxt项目宝塔部署指南
开发语言·windows·golang
心灵宝贝1 小时前
Postman-win64-7.2.2 安装教程(Windows 64位详细步骤)
windows·测试工具·postman
小智学长 | 嵌入式3 小时前
单片机-89C51部分:4、固件烧录
c语言·单片机·嵌入式硬件
时之彼岸Φ3 小时前
Adruino:传感器及步进电机
单片机·嵌入式硬件
网易独家音乐人Mike Zhou3 小时前
【Linux应用】交叉编译环境配置,以及最简单粗暴的环境移植(直接从目标板上复制)
linux·stm32·mcu·物联网·嵌入式·iot
少年、潜行4 小时前
【开源】基于51单片机的简易智能楼道照明设计
单片机·嵌入式硬件·51单片机
子朔不言4 小时前
MH2103 MH22D3系列的JTAG/SWD复用功能和引脚映射,IO初始化的关键点
单片机·mcu·mh2103·mh22d3·新龙微·兆讯
国科安芯4 小时前
基于先进MCU的机器人运动控制系统设计:理论、实践与前沿技术
人工智能·单片机·机器人
honey ball4 小时前
为啥低速MCU单板辐射测试会有200M-1Ghz的辐射信号
单片机·嵌入式硬件
快乐点吧4 小时前
【MongoDB】windows安装、配置、启动
数据库·windows·mongodb