当前位置:

首页 > 编程开发 > C#中的USBHID设备通信全解

C#中的USBHID设备通信全解

在C#中与USBHID设备(比如键盘、鼠标、游戏手柄)打交道,本质上是一场与Windows底层API的对话。这个过程绕不开一个关键技术:P/Invoke。它就像一座桥梁,让托管在.NET环境下的C#代码能够调用那些用C语言写成的、直接与操作系统交互的非托管函数。本文将带你走通从枚举设备、建立连接到读

本文目录

    在C#中与USBHID设备(比如键盘、鼠标、游戏手柄)打交道,本质上是一场与Windows底层API的对话。这个过程绕不开一个关键技术:P/Invoke。它就像一座桥梁,让托管在.NET环境下的C#代码能够调用那些用C语言写成的、直接与操作系统交互的非托管函数。本文将带你走通从枚举设备、建立连接到读写数据的完整路径,并提供可直接使用的代码示例。

    C#中的USBHID设备通信全解

    1. USBHID概述和C#中的实现

    USBHID,即人机接口设备,是USB标准中专门为交互设备定义的一类。相比老式的串口通信,它凭借即插即用、高传输速率和低错误率,成为了许多硬件交互的首选方案。

    要在C#世界里驾驭USBHID设备,核心在于理解其工作原理和数据流,并熟练运用P/Invoke技术来调用Windows API。这为后续所有操作——发现设备、打开连接、收发数据——奠定了基础。

    2. P/Invoke技术与Windows API调用

    2.1 P/Invoke技术简介

    2.1.1 P/Invoke技术的定义

    简单来说,P/Invoke是.NET框架提供的一种机制,允许托管代码(如C#)安全地调用非托管代码库(如Windows系统DLL中的C函数)。这使得开发者能够利用大量成熟且功能强大的Windows API,同时也为旧有Win32应用程序的迁移和维护提供了便利。

    2.1.2 P/Invoke技术的工作原理

    其核心在于桥接两个世界:受公共语言运行时(CLR)管理的托管代码,和直接在操作系统层面运行的非托管代码。P/Invoke负责处理两者之间的差异,包括内存管理、数据类型转换和异常传递,让调用过程尽可能平滑。

    2.2 Windows API在C#中的调用

    2.2.1 Windows API的搜索与选择

    动手之前,先得找到对的“工具”。你需要明确要完成什么功能,然后去查阅官方文档(如MSDN)来确定具体的API函数、它所在的DLL库、参数类型及顺序。这一步的准确性直接决定了后续代码能否成功运行。

    2.2.2 在C#中声明和使用Windows API函数

    找到API后,下一步就是在C#中声明它。这主要依靠 DllImport 属性。以下是一个调用系统消息框的经典示例:

    using System;
    using System.Runtime.InteropServices;
    
    class Program
    {
        // 使用DllImport属性指定DLL名和入口点
        [DllImport("user32.dll", SetLastError = true)]
        static extern int MessageBox(int hWnd, String text, String caption, uint type);
    
        static void Main()
        {
            // 调用MessageBox API
            MessageBox(0, "Hello World!", "My Message Box", 0);
        }
    }
    

    这段代码声明了位于 user32.dll 中的 MessageBox 函数,并指定了其参数和返回类型。这样,在C#中就可以像调用普通方法一样使用它了。

    2.3 P/Invoke技术的深入分析

    2.3.1 数据类型转换与映射

    C#和C/C++的数据类型并非总是一一对应。因此,在跨平台调用时,数据类型的正确映射至关重要。P/Invoke通过 StructLayout、MarshalAs 等属性来确保结构体布局和数据类型能够被非托管代码正确识别。

    [DllImport("kernel32.dll", SetLastError = true)]
    static extern IntPtr VirtualAllocEx(IntPtr hProcess, IntPtr lpAddress, uint dwSize, AllocationType flAllocationType, MemoryProtection flProtect);
    
    [StructLayout(LayoutKind.Sequential)]
    struct MEMORY_BASIC_INFORMATION
    {
        public IntPtr BaseAddress;
        public IntPtr AllocationBase;
        public uint AllocationProtect;
        public IntPtr RegionSize;
        public StateEnum State;
        public AllocationType Protect;
        public TypeEnum Type;
    }
    
    // 使用时
    MEMORY_BASIC_INFORMATION mbi = new MEMORY_BASIC_INFORMATION();
    // ...调用VirtualAllocEx获取信息...
    VirtualQueryEx(hProcess, IntPtr.Zero, out mbi, (uint)Marshal.SizeOf(typeof(MEMORY_BASIC_INFORMATION)));
    

    2.3.2 异常处理与资源管理

    调用非托管代码时,错误可能发生在“墙的另一边”。P/Invoke提供了 Marshal.GetLastWin32Error() 方法来捕获这些错误。同时,必须谨慎管理非托管资源(如文件句柄、内存指针),通常使用 try-finally 或 using 模式来确保资源被正确释放,避免内存泄漏。

    IntPtr handle = IntPtr.Zero;
    try
    {
        handle = CreateFile("somefile.txt", GENERIC_READ, FILE_SHARE_READ, IntPtr.Zero, OPEN_EXISTING, 0, IntPtr.Zero);
        if (handle == INVALID_HANDLE_VALUE)
        {
            int errorCode = Marshal.GetLastWin32Error();
            throw new IOException("Unable to open file", errorCode);
        }
        // ...使用文件...
    }
    finally
    {
        if (handle != IntPtr.Zero && handle != INVALID_HANDLE_VALUE)
        {
            CloseHandle(handle);
        }
    }
    

    以上示例确保了无论文件操作是否成功,句柄最终都会被安全关闭。

    3. 枚举USBHID设备的步骤

    3.1 USBHID设备枚举概念

    3.1.1 枚举设备的重要性

    枚举,简单说就是“发现并认识”设备的过程。对于USBHID设备,这是通信的第一步。主机通过枚举获取设备的供应商ID、产品ID、设备类等关键信息,没有这一步,后续的打开和读写都无从谈起。

    3.1.2 枚举过程中的关键技术点

    枚举过程依赖于一系列Windows API,如 SetupDiGetClassDevs、SetupDiEnumDeviceInterfaces 等。核心在于使用正确的设备接口GUID(全局唯一标识符)来定位HID设备,并妥善处理可能出现的错误,例如设备未连接或驱动异常。

    3.2 枚举USBHID设备的操作步骤

    3.2.1 使用Windows API枚举设备

    枚举通常从获取设备信息集开始。以下代码展示了如何遍历系统中所有已连接的HID设备:

    using System;
    using System.Runtime.InteropServices;
    
    class UsbHidEnumeration
    {
        const int DIGCF_PRESENT = 0x2;
        const int DIGCF_DEVICEINTERFACE = 0x10;
    
        [DllImport("setupapi.dll", SetLastError = true)]
        static extern IntPtr SetupDiGetClassDevs(ref Guid classGuid, IntPtr enumerator, IntPtr hwndParent, int flags);
    
        [DllImport("setupapi.dll", SetLastError = true)]
        static extern bool SetupDiEnumDeviceInterfaces(IntPtr deviceInfoSet, IntPtr deviceInfoData, ref Guid interfaceClassGuid, int memberIndex, ref SP_DEVICE_INTERFACE_DATA deviceInterfaceData);
    
        static Guid HidGuid = new Guid(0x4d1e55b2, 0xf16f, 0x11cf, 0x88, 0xcb, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30);
    
        public static void EnumerateHidDevices()
        {
            IntPtr deviceInfoSet = SetupDiGetClassDevs(ref HidGuid, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);
    
            SP_DEVICE_INTERFACE_DATA deviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
            deviceInterfaceData.cbSize = Marshal.SizeOf(deviceInterfaceData);
    
            for (int memberIndex = 0; SetupDiEnumDeviceInterfaces(deviceInfoSet, IntPtr.Zero, ref HidGuid, memberIndex, ref deviceInterfaceData); memberIndex++)
            {
                // 处理每一个找到的设备接口
            }
        }
    
        [StructLayout(LayoutKind.Sequential)]
        struct SP_DEVICE_INTERFACE_DATA
        {
            public int cbSize;
            public Guid InterfaceClassGuid;
            public int Flags;
            public IntPtr Reserved;
        }
    }
    

    代码首先通过 SetupDiGetClassDevs 获取HID设备列表,然后循环调用 SetupDiEnumDeviceInterfaces 遍历每个设备接口。

    3.2.2 枚举设备时的常见问题及解决方案

    枚举失败通常有几个原因:驱动问题、权限不足或物理连接故障。对应的解决思路也很直接:

    • 驱动问题:确保设备驱动程序已正确安装且为最新版本。
    • 权限问题:尝试以管理员身份运行程序。
    • 连接问题:检查USB线缆和端口,尝试重新插拔或更换端口。

    在代码层面,务必检查API调用的返回值,并通过 GetLastError 获取详细错误码,这是调试的关键。

    if (deviceInfoSet == IntPtr.Zero)
    {
        int errorCode = Marshal.GetLastWin32Error();
        throw new Exception("无法获取设备信息集。错误代码: " + errorCode);
    }
    

    4. 打开和关闭USBHID设备的方法

    成功枚举到设备后,下一步就是与之建立连接(打开)并在完成后妥善断开(关闭)。

    4.1 打开USBHID设备的步骤

    4.1.1 设备句柄的获取

    打开设备的本质是获取一个代表该设备的句柄。在Windows中,这通常通过 CreateFile 函数完成,尽管它的名字看起来像是用于文件操作。

    using System;
    using System.Runtime.InteropServices;
    
    class UsbHidDevice
    {
        [DllImport("kernel32.dll", SetLastError = true)]
        public static extern IntPtr CreateFile(
            string lpFileName,
            uint dwDesiredAccess,
            uint dwShareMode,
            IntPtr lpSecurityAttributes,
            uint dwCreationDisposition,
            uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);
        // USB设备的路径和访问权限等参数
        private static readonly string DevicePath = "\\.\HIDI001";
        private static readonly uint GenericRead = 0x80000000;
    
        public static IntPtr OpenDevice()
        {
            IntPtr deviceHandle = CreateFile(
                DevicePath,
                GenericRead,
                0,
                IntPtr.Zero,
                0x3, // OPEN_EXISTING
                0,
                IntPtr.Zero);
            if(deviceHandle == IntPtr.Zero || deviceHandle == new IntPtr(-1))
            {
                throw new System.ComponentModel.Win32Exception();
            }
    
            return deviceHandle;
        }
    }
    

    4.1.2 打开设备时的异常处理

    打开操作可能因设备被占用、路径错误或权限问题而失败。使用 try-catch 块来捕获并处理这些异常是保证程序健壮性的必要措施。

    try
    {
        IntPtr deviceHandle = UsbHidDevice.OpenDevice();
        // 使用设备句柄进行后续操作
    }
    catch (System.ComponentModel.Win32Exception ex)
    {
        // 处理异常情况,例如用户没有足够的权限或设备不存在
        Console.WriteLine($"Error opening device: {ex.Message}");
    }
    

    4.2 关闭USBHID设备的策略

    4.2.1 设备关闭的条件和时机

    一个良好的习惯是:在程序结束或不再需要设备时,立即关闭其句柄以释放系统资源。通常放在 finally 块或 Dispose 方法中执行。

    // 在程序结束前关闭设备句柄
    if(deviceHandle != IntPtr.Zero)
    {
        CloseHandle(deviceHandle);
    }
    

    4.2.2 关闭设备前的清理工作

    关闭前,应确保所有进行中的数据操作(如读写)已经完成或已被妥善取消。这可以避免数据丢失或设备状态异常。记录关闭日志也有助于后续的问题追踪。

    // 示例代码,确保设备已断开所有连接
    if( // 设备正在传输数据的条件判断)
    {
        // 等待数据传输完成或取消传输
    }
    

    5. 发送和接收数据的机制

    与设备建立连接后,核心任务就是数据交换。理解USBHID的数据包结构和通信模式是关键。

    5.1 数据通信的基本概念

    5.1.1 数据包的结构和格式

    USBHID设备通过“报告”来传输数据,报告的结构由设备固件中的报告描述符定义。主要分为三类:

    • 输入报告:从设备流向主机,例如键盘按下的键值、鼠标移动的坐标。
    • 输出报告:从主机发送到设备,例如设置键盘背光、控制游戏手柄震动。
    • 特征报告:用于双向配置,通常用来获取或设置设备的特定属性。

    5.1.2 数据传输的同步和异步方式

    数据传输有两种模式:

    • 同步传输:调用会阻塞当前线程,直到数据传输完成。简单直接,但可能影响程序响应。
    • 异步传输:调用后立即返回,数据传输在后台进行。效率更高,但编程模型相对复杂。

    5.2 实现数据发送和接收的策略

    5.2.1 利用Windows API实现数据通信

    在C#中,读写数据主要依靠 ReadFile 和 WriteFile 这两个API。以下是一个简化的类结构展示:

    public class HidDevice
    {
        public const int HidImports.HidGuid = 0x4d1e55b2, 0xafa6, 0x4fad, 0x9c, 0x85, 0x19, 0xb8, 0x80, 0xee, 0xca, 0x8b;
    
        [DllImport("hid.dll")]
        private static extern bool HidD_GetAttributes(IntPtr HidDeviceObject, out HidAttributes Attributes);
    
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern IntPtr CreateFile(string lpFileName, uint dwDesiredAccess, uint dwShareMode,
            IntPtr lpSecurityAttributes, uint dwCreationDisposition, uint dwFlagsAndAttributes,
            IntPtr hTemplateFile);
    
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool ReadFile(IntPtr hFile, [Out] byte[] lpBuffer, uint nNumberOfBytesToRead,
            out uint lpNumberOfBytesRead, IntPtr lpOverlapped);
    
        [DllImport("kernel32.dll", SetLastError = true)]
        private static extern bool WriteFile(IntPtr hFile, byte[] lpBuffer, uint nNumberOfBytesToWrite,
            out uint lpNumberOfBytesWritten, IntPtr lpOverlapped);
        ...
    }
    

    5.2.2 数据传输中的错误检测与处理

    ReadFile 和 WriteFile 会返回布尔值指示成功与否。失败时,应立即调用 Marshal.GetLastWin32Error() 获取错误码。常见的错误包括设备意外移除、缓冲区不足或访问被拒绝。健全的错误处理逻辑是确保应用程序稳定性的最后一道防线。

    本站声明:本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系bd@zhengruan.com
    作者最新文章
    编程开发
    相关文章 更多
    联想推出150W充电头:3个USB-C接口、配三脚插脚,339元
    联想推出150W充电头:3个USB-C接口、配三脚插脚,339元

    联想推出150W充电头,配备3个USB-C接口,总输出功率150W,单口最高140W。采用三脚插头接地更安心,支持PD3.2、PPS、QC3.0等主流协议,兼容联想及摩托罗拉私有快充。定价339元,附赠7A/240W充电线。

    绿联推出 45W“小冰片”单 USB-C 接口轻薄充电头:12.5mm 厚度,109 元
    绿联推出 45W“小冰片”单 USB-C 接口轻薄充电头:12.5mm 厚度,109 元

    绿联推出“指尖湃45W‘小冰片’”轻薄充电头,单USB-C接口,厚度仅12.5mm,银灰配色,可折叠插脚,最大输出45W,售价109元。极致轻薄便携,适合为笔记本、平板或手机应急快充。

    GPD G2 显卡扩展坞预售:MCIO + USB4 双口,内置 800W 电源售价 1800 元
    GPD G2 显卡扩展坞预售:MCIO + USB4 双口,内置 800W 电源售价 1800 元

    GPDG2显卡扩展坞现已开启预售,售价1800元,7月30日付尾款。其配备MCIO8i与USB42.0双接口,可提供高达256Gbps的全对称双向带宽。内置800W金牌全模组电源,支持ATX3.1标准,当连接RTX4090显卡时性能损耗仅约2%。该产品专门为掌机或迷你主机用户设计。

    飞傲 FG3 头戴式游戏耳机明日开售:50mm+16mm 双动圈、USB/3.5mm 双输入接口,399 元
    飞傲 FG3 头戴式游戏耳机明日开售:50mm+16mm 双动圈、USB/3.5mm 双输入接口,399 元

    飞傲FG3游戏耳机售价399元,明日上午10点开售。采用50mm+16mm同轴双动圈,支持USB/3.5mm双输入。内置DAC耳放,支持虚拟7.1音效及可拆卸降噪麦克风,多平台兼容。

    小米新款金沙江磁吸充电宝10000mAh获认证:新增USB-C自带线
    小米新款金沙江磁吸充电宝10000mAh获认证:新增USB-C自带线

    8月19日消息,就在昨天,小米一款型号为“WPB1025”的移动电源新品顺利通过了国家CCC质量认证,据推测,这极有可能是小米新款金沙江磁吸充电宝10000mAh版。据了解,小米目前售卖的金沙江磁吸充电宝10000mAh版本于今年5月上市,那时就已按照2026年新国标进行设计,并且顺利通过了各项安全

    制造商倍魔推出 100W USB-C“AI 语音数据线”,号称可以声控空调
    制造商倍魔推出 100W USB-C“AI 语音数据线”,号称可以声控空调

    倍魔发布100WUSB-CAI语音数据线,长约1.5米,支持100W快充与USB2.0传输,内置麦克风,可通过唤醒词声控空调等家电,价格约22-29元,兼具充电、数据传输与语音控制功能。

    Club3D 推出全球首款无源 9 米长 USB4 线:20Gbps 带宽、支持 100W PD
    Club3D 推出全球首款无源 9 米长 USB4 线:20Gbps 带宽、支持 100W PD

    Club3D发布全球首款9米无源USB4线缆,无需芯片或供电,实现20Gbps带宽、100WPD充电,支持4K@240Hz或8K@60Hz视频,适用于会议室、VR及扩展坞等长距离高带宽场景。

    Club3D推出全球首款无源9米长USB4线:20Gbps带宽
    Club3D推出全球首款无源9米长USB4线:20Gbps带宽

    Club3D推出全球首款9米无源USB4线缆,无需外部供电即可实现20Gbps带宽,支持100W快充、4K@240Hz及8K@60Hz视频传输,满足长距离高带宽需求。

    Win11怎么解决USB传输速度慢
    Win11怎么解决USB传输速度慢

    在 Win11 里遇到 USB 传文件速度突然掉下来,别急着把问题全算到 U 盘身上。很多时候,症结反而出在系统侧,最常见的就是两条:一条是设备策略还停留在偏保守的模式,另一条是 USB 控制器被节电策略限制了性能。先把这两处排查并处理好,再拿同一批文件做一次回测,通常比上来就重装驱动更能直接看出效

    我是 USB-C 极简主义者
    我是 USB-C 极简主义者

    去欧洲旅行7周,仅带一块通用电源砖,通过USB-C接口统一为手机、笔记本、手表、牙刷等所有设备充电。一个充电器、一根线、一个标准即可满足全部需求,彻底告别专有充电口,轻装出行。

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    PDF教程
    PDF教程

    正软商城PDF教程频道提供PDF编辑、转换、合并、拆分、压缩及格式处理方法,同时介绍常用PDF软件和工具的使用技巧。

    Mac软件 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    Aperty macOS版
    Aperty macOS版
    macOS

    Aperty 是 Skylum 推出的 AI 人像修图软件,支持自然肤质修饰、瑕疵处理、面部与身体调整、预设及批量同步。

    WINDOWS 更多
    讯飞智作 网页版
    讯飞智作 网页版
    Windows/ macOS/ Linux

    讯飞智作是一款基于语音合成技术、虚拟人驱动技术与数字人形象声音构建能力的 AI 数字人音视频创作平台。

    火山引擎 网页版
    火山引擎 网页版
    Windows/ macOS/ Linux

    火山引擎是字节跳动旗下企业级云与AI服务平台。聚焦大模型核心能力,依托字节自研豆包多模态大模型,凭借火山方舟一站式平台,提供模型精调、推理部署、智能应用搭建等全链路服务,结合高性能AI算力,助力各行业快速落地大模型数字化与智能化应用。

    PixTV 网页版
    PixTV 网页版
    Windows

    PixTV 是一款在浏览器中使用的 AI 视觉内容创作平台,提供图片、视频、音频、文字生成、AI 分镜、局部重绘、提示词反推和无限画布等工具,适合短剧、漫剧、广告及社交媒体内容制作。