C#教程

关注公众号 jb51net

关闭
首页 > 软件编程 > C#教程 > C# USBHID设备通信

C#中的USBHID设备通信全解

作者:weixin_36019375

USBHID是USB中的一种设备类别,广泛应用于键盘、鼠标、游戏手柄等输入设备,本文就来详细的介绍一下C#中的USBHID设备通信,具有一定的参考价值,感兴趣的可以了解一下

简介:USBHID(USB Human Interface Device)规范定义了如何支持键盘、鼠标等交互设备。在C#中,实现与USBHID设备的通信需要调用Windows API和P/Invoke技术。本文详细介绍了如何在C#编程环境下枚举、打开、读写以及错误处理USBHID设备,并提供了一个完整的示例代码。

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

USBHID(Human Interface Device)是USB(Universal Serial Bus)中的一种设备类别,广泛应用于键盘、鼠标、游戏手柄等输入设备。相较于传统的串口通信,USBHID具有即插即用、传输速率高、错误率低等优点,成为许多硬件设备通信的首选方式。

在C#中实现USBHID通信,需要深入了解USBHID的工作原理和数据交互流程。C#通过P/Invoke(Platform Invocation Services)技术调用底层的Windows API,实现了与USBHID设备的数据交互。本章将详细介绍USBHID在C#环境中的实现方法,为后续章节的学习打下坚实的基础。

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

2.1 P/Invoke技术简介

2.1.1 P/Invoke技术的定义

P/Invoke是Platform Invocation Services的缩写,是一种在.NET环境中调用非托管代码的方法,尤其是用于调用Windows API函数。其允许程序员在C#或其他.NET语言中直接使用C语言声明的函数。这种技术使得.NET程序能够利用丰富而成熟的Windows API,同时也使得维护旧的Windows应用程序变得更加容易。

2.1.2 P/Invoke技术的工作原理

P/Invoke的工作原理主要依赖于两个关键概念:托管代码(Managed Code)和非托管代码(Unmanaged Code)。托管代码运行在CLR(Common Language Runtime)的保护和管理下,而非托管代码则直接在操作系统级别执行,不受CLR的管理。P/Invoke通过提供一个桥梁,使得托管代码可以调用非托管代码,同时CLR能够管理相应的内存、类型转换、异常等。

2.2 Windows API在C#中的调用

2.2.1 Windows API的搜索与选择

在进行P/Invoke调用前,首先需要对所需的Windows API函数进行搜索和选择。这通常需要了解函数的作用、参数以及返回类型。开发者可以参考MSDN(Microsoft Developer Network)文档来查找正确的API。一旦确定了要使用的API,就需要了解它的准确名称、所在的DLL以及参数类型和顺序。

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

在C#中使用P/Invoke声明Windows API函数通常需要使用 DllImport 属性。这个属性告诉CLR在哪个DLL文件中寻找非托管函数。下面是一个简单的例子,展示了如何在C#中声明和使用 MessageBox API函数:

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);
    }
}

在这个例子中, MessageBox 函数在 user32.dll 中声明,并且通过 DllImport 属性导入。函数的返回类型是 int ,并且有四个参数:窗口句柄 hWnd 、文本字符串 text 、标题字符串 caption 和消息框类型 type

2.3 P/Invoke技术的深入分析

2.3.1 数据类型转换与映射

P/Invoke在不同环境间进行函数调用时,需要考虑数据类型的一致性。C#中的数据类型与C语言中的数据类型并不完全相同。例如,C#中的 int 类型是32位的,而C语言中的 int 可能是16位的。因此,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提供了 arshal.GetLastWin32Error() 方法来获取上一次调用非托管函数时所发生的错误代码。另外,为了防止资源泄露,建议使用 try-finally 语句来确保即使在发生异常的情况下,也能够正确释放资源。

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);
    }
}

以上示例中, CreateFile CloseHandle 都是Windows API函数,通过 try-finally 确保了文件句柄在使用完毕后能够安全关闭,即使在读取文件时发生异常。

以上章节内容涵盖了P/Invoke技术的基础知识、在C#中调用Windows API的步骤和技巧,并对数据类型映射、异常处理与资源管理进行了深入分析。通过理解这些知识,开发者可以更加有效地使用P/Invoke技术,从而在.NET环境中充分利用Windows强大的API资源。

3. 枚举USBHID设备的步骤

3.1 USBHID设备枚举概念

3.1.1 枚举设备的重要性

枚举是发现和识别USB设备的过程,尤其对于USBHID设备,这是实现数据通信的基础。在USB通信协议中,枚举过程使主机能够识别连接到USB端口上的设备类型,并获取其配置信息。没有正确的枚举过程,USBHID设备无法被识别和使用。

枚举过程涉及到几个关键步骤,包括检测设备连接事件、获取设备描述符、配置设备以及最终与设备建立通信。通过这些步骤,软件能够获得必要的信息,如设备的供应商ID、产品ID、设备类等,这为后续的操作提供了必要的前提条件。

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

在枚举过程中,需要使用一系列的Windows API函数来完成任务。这些API包括但不限于 SetupDiGetClassDevs SetupDiEnumDeviceInterfaces CreateFile GetLastError 等。通过这些API,可以在程序中模拟枚举过程,发现和打开USBHID设备。

关键技术点之一是如何准确地使用Windows提供的设备接口。这通常通过设备接口的GUID来完成。另一个关键点是错误处理,因为在枚举过程中可能会遇到各种异常情况,如设备尚未连接或配置不正确等,这时需要通过适当的错误处理来确保程序的健壮性。

3.2 枚举USBHID设备的操作步骤

3.2.1 使用Windows API枚举设备

枚举USBHID设备的第一步是使用 SetupDiGetClassDevs 函数获取设备信息集。代码示例如下:

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 枚举设备时的常见问题及解决方案

在枚举USBHID设备的过程中,可能会遇到设备未正确识别或无法打开等问题。这些问题通常由以下原因导致:

针对这些问题,可以采取以下解决方案:

此外,代码中要适当地处理Windows API返回的错误码,通过调用 GetLastError 函数获取错误详情,并进行相应的错误处理逻辑。

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

在上述代码段中,如果 SetupDiGetClassDevs 函数返回了 IntPtr.Zero ,表示枚举失败。通过调用 Marshal.GetLastWin32Error ,程序可以获取错误码,并抛出异常,从而让用户或开发者了解具体的问题所在。

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

在本章中,我们将深入探讨如何在C#中打开和关闭USBHID设备,同时涉及与之相关的最佳实践和异常处理策略。

4.1 打开USBHID设备的步骤

4.1.1 设备句柄的获取

打开USBHID设备的第一步是获取设备的句柄,句柄是操作系统用来标识和管理资源的一种方式。在C#中,我们通常使用 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 打开设备时的异常处理

当尝试打开一个USBHID设备时,可能会遇到各种异常,例如权限不足或设备不存在。异常处理是确保程序稳定运行的关键。

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

4.2 关闭USBHID设备的策略

4.2.1 设备关闭的条件和时机

关闭USBHID设备的时机取决于应用程序的设计。一般来说,在程序结束前应关闭所有打开的设备,释放资源。

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

4.2.2 关闭设备前的清理工作

关闭设备前的清理工作是确保设备状态正确和数据完整性的重要步骤。在关闭句柄之前,应确保所有数据传输已成功完成。

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

在这个阶段,也可以记录一些关键信息,比如最后关闭设备时的时间戳,用于调试或日志分析。

本章节介绍了打开和关闭USBHID设备的方法,包括设备句柄的获取和异常处理策略。在下一章节中,我们将继续探讨发送和接收数据的机制。

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

数据通信是USBHID设备功能实现的核心。为确保数据有效传输,了解数据包结构、通信方式和相关的错误处理策略是必不可少的。

5.1 数据通信的基本概念

5.1.1 数据包的结构和格式

在USBHID设备通信中,数据包是指数据传输的基本单元。每一个数据包由报告描述符定义,该描述符定义了数据字段的大小、类型和用途。通常,数据包分为输入报告、输出报告和特征报告三类。

输入报告用于从设备读取数据,如按键状态或传感器数据。输出报告用于向设备发送数据,比如发送控制信号。特征报告则用于修改设备的配置或状态,通常涉及到设备的特征设置。

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

USBHID设备支持同步和异步两种数据传输方式。

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

5.2.1 利用Windows API实现数据通信

在C#中,使用Windows API可以实现USBHID设备的数据发送和接收。这通常涉及到 ReadFile WriteFile 函数,它们分别用于读取和写入数据。以下是一个使用这些函数的代码示例:

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() 获取。

常见的错误包括设备未连接、权限不足等。针对这些错误,开发者需要设计相应的异常处理逻辑,确保程序的健壮性。

在后续的章节中,我们将更深入地探讨错误处理以及示例代码的使用。接下来,我们进入第六章,详细讨论USBHID设备的错误处理策略。

到此这篇关于C#中的USBHID设备通信全解的文章就介绍到这了,更多相关C# USBHID设备通信内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文