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 函数获取错误代码。

使用场景

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

相关推荐
FPGA小徐10 分钟前
电子工程师面试真题:LM7805 扩流 + 过流保护电路原理分析
单片机·嵌入式硬件
霖霖71431 分钟前
【ESP32笔记】RGB灯WS2812实验
笔记·单片机·mcu·物联网
西城微科方案开发1 小时前
激光测距仪模组选择——M01还是M03?
单片机·嵌入式硬件
zjxtxdy1 小时前
STM32hal库开发-中断与串口和通信协议
stm32·单片机·嵌入式硬件
霖霖7141 小时前
【ESP32笔记】开发环境的搭建
单片机·mcu·物联网
李少兄1 小时前
Windows 11 环境下 BAT 脚本无法执行的排查与修复指南
windows
wuyk5551 小时前
27.LeetCode 2265. 统计值等于子树平均值的节点数 —— 后序遍历实战
stm32·单片机·嵌入式硬件·算法
糖糖单片机设计1 小时前
基于 STM32 的配网变压器涌流抑制器设计与实现
人工智能·stm32·嵌入式硬件·51单片机·语音识别
解道Jdon1 小时前
FigCraft把Figma用Rust从零重写了一遍
ide·windows·git·svn·eclipse·github·visual studio
沐欣工作室_lvyiyi2 小时前
基于单片机的智能窗帘控制设计(论文+源码)
单片机·嵌入式硬件·单片机设计·江苏理工学院·伊犁师范大学·延安大学·兰州城市学院