In the code below, there is a BeginRead call which I want to use in an async manner. (warning, code is cobbled together using a variety of code samples found lying around on the internet!) With this sample, I expect the BeginRead to return immediately, and then the callback to be hit later on when data finally shows up.
Instead what I'm seeing, is that it gets "stuck" inside the BeginRead call. I can break, and the program counter is on the BeginRead line, but if I continue, it won't go anywhere. Both the CreateFile call and the FileStream constructor call are set up to be asynchronous, so I can't see any reason it wouldn't continuing.
If I add another section which creates another file handle and file stream, and then write something out to my device first, THEN call BeginRead, the BeginRead will successfully complete immediately, and the callback gets hit, and the data from the device is received.
I tried using .net calls only and avoiding CreateFile, but discovered the hard way that .net async works with files, but doesn't work with a USB device path. Just claims (erroneously) that there are invalid characters in the device path.
Besides the confounding factor of managed code calling an original Win32 API, there is another potential confounder. I am trying to compile and run on a 64-bit system. I already discovered I had to change struct fields from int to Int32. There may be something more along those lines.
Any ideas?
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.IO;
using System.Runtime.InteropServices;
using Microsoft.Win32.SafeHandles;
namespace Async_Multi_USB
{
class Program
{
internal struct SP_DEVICE_INTERFACE_DATA
{
internal Int32 cbSize;
internal System.Guid InterfaceClassGuid;
internal Int32 Flags;
internal IntPtr Reserved;
}
[DllImport("setupapi.dll", SetLastError = true, CharSet = CharSet.Auto)]
internal static extern IntPtr SetupDiGetClassDevs(ref System.Guid ClassGuid, IntPtr Enumerator, IntPtr hwndParent, Int32 Flags);
internal const Int32 DIGCF_PRESENT = 2;
internal const Int32 DIGCF_DEVICEINTERFACE = 0X10;
[DllImport("hid.dll", SetLastError = true)]
public static extern void HidD_GetHidGuid(ref System.Guid HidGuid);
[DllImport("setupapi.dll", SetLastError = true)]
internal static 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 static 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 static 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);
public const Int32 DUPLEX = (0x00000003);
public const Int32 FILE_FLAG_OVERLAPPED = (0x40000000);
[DllImport("hid.dll", SetLastError = true)]
internal static extern Boolean HidD_GetPreparsedData(SafeFileHandle HidDeviceObject, ref IntPtr PreparsedData);
internal struct HIDP_CAPS
{
internal Int16 Usage;
internal Int16 UsagePage;
internal Int16 InputReportByteLength;
internal Int16 OutputReportByteLength;
internal Int16 FeatureReportByteLength;
[MarshalAs(UnmanagedType.ByValArray, SizeConst = 17)]
internal Int16[] Reserved;
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 static extern Int32 HidP_GetCaps(IntPtr PreparsedData, ref HIDP_CAPS Capabilities);
static public void OnReadComplete(IAsyncResult ar)
{
try
{
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 = unchecked((int)0x8000000A);
public const int E_HANDLE = unchecked((int)0x80070006);
public const int E_NOTIMPL = unchecked((int)0x80004001);
public const int E_NOINTERFACE = unchecked((int)0x80004002);
public const int E_POINTER = unchecked((int)0x80004003);
public const int E_ABORT = unchecked((int)0x80004004);
public const int E_FAIL = unchecked((int)0x80004005);
public const int E_OUTOFMEMORY = unchecked((int)0x8007000E);
public const int E_ACCESSDENIED = unchecked((int)0x80070005);
public const int E_UNEXPECTED = unchecked((int)0x8000FFFF);
public const int E_FLAGS = unchecked((int)0x1000);
public const int E_INVALIDARG = unchecked((int)0x80070057);
public 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);
SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);
List<String> listHidDevices = new List<String>();
Int32 i32MemberIndex = 0;
while (true)
{
Boolean 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 = "Unable to get the buffer size for USB device detail.";
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);
}
IntPtr pDetailDataBuffer = Marshal.AllocHGlobal(i32BufferSize);
Marshal.WriteInt32(pDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
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);
}
IntPtr pDevicePathName = new IntPtr(pDetailDataBuffer.ToInt32() + 4);
String sDevicePathName = Marshal.PtrToStringAuto(pDevicePathName).ToLower();
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("Found device: " + sDevicePathName);
}
if (pDetailDataBuffer != IntPtr.Zero)
{
Marshal.FreeHGlobal(pDetailDataBuffer);
}
i32MemberIndex++;
}
if (hDeviceInfoSet != IntPtr.Zero)
{
SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
}
if (listHidDevices.Count == 0)
{
return;
}
SafeFileHandle hHidRead = CreateFile(listHidDevices[0],
GENERIC_READ | GENERIC_WRITE,
FILE_SHARE_READ | FILE_SHARE_WRITE,
IntPtr.Zero,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
0);
if (hHidRead.IsInvalid)
{
string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
Console.WriteLine(errorMessage);
return;
}
IntPtr pPreparsedData = new System.IntPtr();
if ( ! HidD_GetPreparsedData(hHidRead, ref pPreparsedData))
{
SystemException se = new SystemException("Call to HidD_GetPreparsedData failed.");
throw se;
}
HIDP_CAPS Capabilities = new HIDP_CAPS();
Int32 i32Result = HidP_GetCaps(pPreparsedData, ref Capabilities);
if (i32Result == 0)
{
SystemException se = new SystemException("Call to HidD_GetCaps failed.");
throw 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 Byte[] buf;
};
static public void BeginAsyncRead(ref FileStream fs, int iBufLen)
{
SyncObj_t syncObj = new SyncObj_t();
syncObj.fs = fs;
syncObj.buf = new Byte[iBufLen];
fs.BeginRead(syncObj.buf, 0, iBufLen, new AsyncCallback(ReadCompleted), syncObj);
}
static protected void ReadCompleted(IAsyncResult iResult)
{
SyncObj_t syncObj = (SyncObj_t)iResult.AsyncState;
try
{
syncObj.fs.EndRead(iResult);
try
{
}
finally
{
BeginAsyncRead(ref syncObj.fs, syncObj.buf.Length);
}
}
catch (IOException ex)
{
Console.WriteLine(ex.ToString());
}
}
}
}
Hi SAKryukov,
Sorry about the code dump. It is a complex problem, so more code is needed to demonstrate it. I really did a lot of work to boil it down to a minimum repro case. (yes, I realize 400 lines is still a huge repro case, but the Windows APIs are often not conducive to tight code) If you plug that code into a simple Visual Studio command line project, it will compile and reproduce the problem as stated above. You would have to replace the VID and PID with that of your own USB device of course, to get down to the BeginRead attempt.
The problem is that BeginRead is blocking as if it were a synchronous attempt, but it should be an Asynchronous call.
Please look particularly at the CreateFile call, and related parameters, and the FileStream constructor, and related params, and see if you can see any thing wrong.
-Dan