带有USB设备的异步I/O[英] Asynchronous I/O with USB device

问题描述

在下面的代码中,有一个 BeginRead 调用,我想以异步方式使用它.(警告,代码是使用在互联网上发现的各种代码示例拼凑而成的!)使用此示例,我希望 BeginRead 立即返回,然后在数据最终显示时稍后触发回调.

相反,我看到的是它在 BeginRead 调用中"卡住"了.我可以中断,程序计数器在 BeginRead 行,但如果我继续,它不会去任何地方.CreateFile 调用和 FileStream 构造函数调用都设置为异步的,所以我看不出它不会继续的任何原因.
如果我添加另一个部分来创建另一个文件句柄和文件流,然后先将一些内容写入我的设备,然后调用 BeginRead,BeginRead 将立即成功完成,并且回调被命中,并且接收到来自设备的数据.

我尝试仅使用 .net 调用并避免使用 CreateFile,但发现 .net async 很难处理文件,但不适用于 USB 设备路径.只是声称(错误地)设备路径中有无效字符.

除了调用原始 Win32 API 的托管代码的混淆因素外,还有另一个潜在的混淆因素.我正在尝试在 64 位系统上编译和运行.我已经发现我必须将结构字段从 int 更改为 Int32.可能还有更多类似的东西.

有什么想法吗?

使用系统;使用 System.Collections.Generic;使用 System.ComponentModel;使用 System.Diagnostics;使用 System.IO;使用 System.Runtime.InteropServices;使用 Microsoft.Win32.SafeHandles;命名空间 Async_Multi_USB{class 程序{内部 结构 SP_DEVICE_INTERFACE_DATA{internal Int32 cbSize;internal System.Guid InterfaceClassGuid;internal Int32 标志;internal IntPtr 保留;}[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]internal 静态 extern IntPtr SetupDiGetClassDevs(ref System.Guid ClassGuid, IntPtr 枚举器, IntPtr hwndParent, Int32 标志);//来自 setupapi.hinternal const Int32 DIGCF_PRESENT = 2;internal const Int32 DIGCF_DEVICEINTERFACE = 0X10;[DllImport("hid.dll", SetLastError = true)]public 静态 extern void HidD_GetHidGuid(ref System.Guid HidGuid);[DllImport("setupapi.dll", SetLastError = true)]internal 静态 extern Boolean SetupDiEnumDeviceInterfaces(IntPtr DeviceInfoSet, IntPtr DeviceInfoData, ref System.Guid InterfaceClassGuid, Int32 MemberIndex, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData);[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]internal 静态 extern Boolean SetupDiGetDeviceInterfaceDetail(IntPtr DeviceInfoSet, ref SP_DEVICE_INTERFACE_DATA DeviceInterfaceData, IntPtr DeviceInterfaceDetailData, Int32 DeviceInterfaceDetailDataSize, ref Int32 RequiredSize, IntPtr DeviceInfoData);[DllImport("setupapi.dll", SetLastError = true)]internal 静态 extern Int32 SetupDiDestroyDeviceInfoList(IntPtr DeviceInfoSet);internal const Int32 FILE_SHARE_READ = 1;internal const Int32 FILE_SHARE_WRITE = 2;internal const UInt32 GENERIC_READ = 0x80000000U;internal const UInt32 GENERIC_WRITE = 0x40000000U;internal const Int32 INVALID_HANDLE_VALUE = -1;internal const Int32 OPEN_EXISTING = 3;[DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]internal static extern SafeFileHandle CreateFile(String lpFileName, UInt32 dwDesiredAccess, Int32 dwShareMode, IntPtr lpSecurityAttributes, Int32 dwCreationDisposition, Int32 dwFlagsAndAttributes,Int32 hTemplateFile);//在互联网上发现的神奇数字......// 据说它们来自某处的windows头文件.public const Int32 DUPLEX = (0x00000003);public const Int32 FILE_FLAG_OVERLAPPED = (0x40000000);[DllImport("hid.dll", SetLastError = true)]internal 静态 extern Boolean HidD_GetPreparsedData(SafeFileHandle HidDeviceObject, ref IntPtr PreparsedData);内部 结构 HIDP_CAPS{internal Int16 用法;internal Int16 UsagePage;internal Int16 InputReportByteLength;internal Int16 OutputReportByteLength;internal Int16 FeatureReportByteLength;[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]internal Int16[] 保留;internal Int16 NumberLinkCollectionNodes;internal Int16 NumberInputButtonCaps;internal Int16 NumberInputValueCaps;internal Int16 NumberInputDataIndices;internal Int16 NumberOutputButtonCaps;internal Int16 NumberOutputValueCaps;internal Int16 NumberOutputDataIndices;internal Int16 NumberFeatureButtonCaps;internal Int16 NumberFeatureValueCaps;internal Int16 NumberFeatureDataIndices;}[DllImport("hid.dll", SetLastError = true)]internal 静态 extern Int32 HidP_GetCaps(IntPtr PreparsedData, ref HIDP_CAPS Capabilities);static public void OnReadComplete(IAsyncResult ar){试试{//m_fsDeviceRead.EndRead(ar);if ((ar.IsCompleted)){// 将数据传输回异步层.}}catch (IOException ex){// 如果设备已断开连接,则忽略该错误.Debug.WriteLine(ex.Message);}}public sealed class Hresults{public const int NOERROR = 0;public const int S_OK = 0;public const int S_FALSE = 1;public const int E_PENDING = 未选中((int)0x8000000A);public const int E_HANDLE = 未选中((int)0x80070006);public const int E_NOTIMPL = 未选中((int)0x80004001);public const int E_NOINTERFACE = 未选中((int)0x80004002);//ArgumentNullException.NullReferenceException 使用 COR_E_NULLREFERENCEpublic const int E_POINTER = 未选中((int)0x80004003);public const int E_ABORT = 未选中((int)0x80004004);public const int E_FAIL = 未选中((int)0x80004005);public const int E_OUTOFMEMORY = 未选中((int)0x8007000E);public const int E_ACCESSDENIED = 未选中((int)0x80070005);public const int E_UNEXPECTED = 未选中((int)0x8000FFFF);public const int E_FLAGS = 未选中((int)0x1000);public const int E_INVALIDARG = 未选中((int)0x80070057);//Wininetpublic const int ERROR_SUCCESS = 0;public const int ERROR_FILE_NOT_FOUND = 2;public const int ERROR_ACCESS_DENIED = 5;public const int ERROR_INSUFFICIENT_BUFFER = 122;}static void Main(string[] args){Guid guidHID = new Guid();HidD_GetHidGuid(ref guidHID);IntPtr hDeviceInfoSet = SetupDiGetClassDevs(ref guidHID, IntPtr.Zero, IntPtr.Zero, DIGCF_PRESENT | DIGCF_DEVICEINTERFACE);// MyDeviceInterfaceData结构的cbSize元素必须设置为// 结构的大小(以字节为单位).// 32位代码大小为28字节,64位代码大小为32字节.SP_DEVICE_INTERFACE_DATA DeviceInterfaceData =  SP_DEVICE_INTERFACE_DATA();DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);// 循环通过操作系统找到的设备集,创建我们的设备列表.列表<字符串>listHidDevices = new List<String>();Int32 i32MemberIndex = 0;while (true){//从0开始,通过设备信息集递增直到//没有更多设备可用.布尔 bSuccess = SetupDiEnumDeviceInterfaces(hDeviceInfoSet,IntPtr.Zero, ref guidHID, i32MemberIndex,ref DeviceInterfaceData);if (!bSuccess){// 如果失败,则意味着我们已经到了列表的末尾.break;}//设备存在.//找出需要多大的缓冲区.Int32 i32BufferSize = 0;bSuccess = SetupDiGetDeviceInterfaceDetail(hDeviceInfoSet,ref DeviceInterfaceData, IntPtr.Zero, 0,ref i32BufferSize, IntPtr.Zero);String sErrorMessage;if (bSuccess){//这个成功出乎意料!我们想得到一个错误,服务员// 成功调用缓冲区大小的信息.sErrorMessage = "无法获取 USB 设备详细信息的缓冲区大小.";Console.WriteLine(sErrorMessage);Debug.Assert(bSuccess);}sErrorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;if (Marshal.GetLastWin32Error() != Hresults.ERROR_INSUFFICIENT_BUFFER){sErrorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;Console.WriteLine(sErrorMessage);Debug.Assert(bSuccess);}// 使用返回的缓冲区大小为 SP_DEVICE_INTERFACE_DETAIL_DATA 结构分配内存.IntPtr pDetailDataBuffer = Marshal.AllocHGlobal(i32BufferSize);// 将 cbSize 存储在数组的第一个字节中.字节数因 32 位和 64 位系统而异.Marshal.WriteInt32(pDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);//再次调用SetupDiGetDeviceInterfaceDetail.//这一次,传递一个指向DetailDataBuffer的指针// 以及返回的所需缓冲区大小.bSuccess = SetupDiGetDeviceInterfaceDetail(hDeviceInfoSet,ref DeviceInterfaceData, pDetailDataBuffer,i32BufferSize, ref i32BufferSize, IntPtr.Zero);if (!bSuccess){sErrorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;Console.WriteLine(sErrorMessage);Debug.Assert(bSuccess);}//跳过cbsize(4字节)得到devicePathName的地址.IntPtr pDevicePathName = new IntPtr(pDetailDataBuffer.ToInt32() + 4);// 获取包含 devicePathName 的字符串.String sDevicePathName = Marshal.PtrToStringAuto(pDevicePathName).ToLower();//查看设备路径是否包含我们的VID和PID.UInt16 u16Vid = 0x148a;UInt16 u16Pid = 0x0004;String sSearch = String.Format("vid_{0:x4}&pid_{1:x4}", u16Vid, u16Pid);if (sDevicePathName.Contains(sSearch)){listHidDevices.Add(sDevicePathName);Console.WriteLine("找到设备:" + sDevicePathName);}if (pDetailDataBuffer != IntPtr.Zero){//释放AllocHGlobal之前分配的内存.Marshal.FreeHGlobal(pDetailDataBuffer);}i32会员索引++;}if (hDeviceInfoSet != IntPtr.Zero){SetupDiDestroyDeviceInfoList(hDeviceInfoSet);}if (listHidDevices.Count == 0){//未找到设备.返回;}SafeFileHandle hHidRead = CreateFile(listHidDevices[0],GENERIC_READ |GENERIC_WRITE,FILE_SHARE_READ |FILE_SHARE_WRITE,IntPtr.Zero, // securityAttributesOPEN_EXISTING, // creationDispositionFILE_FLAG_OVERLAPPED, //|DUPLEX,//标志0);//模板if (hHidRead.IsInvalid){string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;Console.WriteLine(errorMessage);//不行,得保释.返回;}IntPtr pPreparsedData = new System.IntPtr();if (!HidD_GetPreparsedData(hHidRead, ref pPreparsedData)){SystemException se = new SystemException("调用 HidD_GetPreparsedData 失败.");抛出 se;}HIDP_CAPS 能力 =  HIDP_CAPS();Int32 i32Result = HidP_GetCaps(pPreparsedData, ref Capabilities);if (i32Result == 0){SystemException se = new SystemException("调用 HidD_GetCaps 失败.");抛出 se;}Int16 i16OutputReportLen = Capabilities.OutputReportByteLength;Int16 i16InputReportLen = Capabilities.InputReportByteLength;const Int32 i32MaxPPCPLen = 0x1000;FileStream fsDeviceRead = new FileStream(hHidRead, FileAccess.Read,i32MaxPPCPLen, true);// 让异步读取悬空.BeginAsyncRead(ref fsDeviceRead, i16InputReportLen);hHidRead.Close();}public struct SyncObj_t{public FileStream fs;public 字节[] buf;};/// <摘要> /// 启动异步读取,在读取数据或设备时完成 /// 已断开连接.使用回调. /// </summary> static public void BeginAsyncRead(ref FileStream fs, int iBufLen){SyncObj_t syncObj = new SyncObj_t();同步对象.fs = fs;syncObj.buf = new 字节[iBufLen];//把我们用来接收东西的buff设置为异步状态,然后我们可以在读取完成时获取它fs.BeginRead(syncObj.buf, 0, iBufLen, new AsyncCallback(ReadCompleted), syncObj);}/// <摘要> /// 上述回调.请注意这一点,因为它将在异步读取的后台线程上调用 /// </summary> /// <参数 name="iResult">异步结果参数</param> static protected void ReadCompleted(IAsyncResult iResult){// 检索流并读取缓冲区.SyncObj_t syncObj = (SyncObj_t)iResult.AsyncState;试试{// call end read : 这会抛出读取过程中发生的任何异常syncObj.fs.EndRead(iResult);试试{//InputReport oInRep = CreateInputReport();//为设备创建输入报告//oInRep.SetData(arrBuff);//并设置数据部分——这会将接收到的数据处理成更容易理解的格式,具体取决于报告类型// 将新的输入报告传递给更高级别的处理程序.//HandleDataReceived(oInRep);}终于{// 完成所有操作后,开始阅读下一份报告BeginAsyncRead(ref syncObj.fs, syncObj.buf.Length);}}catch (IOException ex) // 如果我们有 IO异常,设备已被移除{Console.WriteLine(ex.ToString());/*HandleDeviceRemoved();如果(OnDeviceRemoved != null){OnDeviceRemoved(this, new EventArgs());}处置();*/}}}}



嗨 SAKryukov,

对不起代码转储.这是一个复杂的问题,因此需要更多的代码来演示它.我确实做了很多工作来将其归结为最小的复制案例.(是的,我意识到 400 行仍然是一个巨大的重现案例,但 Windows API 通常不利于紧凑的代码)如果您将该代码插入一个简单的 Visual Studio 命令行项目,它将编译并重现上述问题.当然,您必须将 VID 和 PID 替换为您自己的 USB 设备,才能开始尝试 BeginRead.

问题是 BeginRead 像同步尝试一样被阻塞,但它应该是异步调用.

请特别查看 CreateFile 调用和相关参数,以及 FileStream 构造函数和相关参数,看看是否有任何错误.

-Dan

推荐答案

static public void BeginAsyncRead

应该是
static public IAsyncResult BeginAsyncRead

本文地址:https://www.itbaoku.cn/post/1395160.html

相关标签/搜索