Click here to Skip to main content
15,893,663 members
Please Sign up or sign in to vote.
3.00/5 (2 votes)
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?

C#
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);
    
        // From setupapi.h
        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);
    
        // Magic numbers found lying around on the internet...
        // Supposedly they come from a windows header file somewhere.
        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
            {
                //m_fsDeviceRead.EndRead(ar);
    
                if ((ar.IsCompleted))
                {
                    // Transmit the data back up to the Async layer.
                }
            }
            catch (IOException ex)
            {
                // Just ignore the error if the device has been disconnected.
                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);
            //ArgumentNullException. NullReferenceException uses COR_E_NULLREFERENCE
            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);
    
            //Wininet
            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);
    
            // The cbSize element of the MyDeviceInterfaceData structure must be set to
            // the structure's size in bytes. 
            // The size is 28 bytes for 32-bit code and 32 bytes for 64-bit code.
            SP_DEVICE_INTERFACE_DATA DeviceInterfaceData = new SP_DEVICE_INTERFACE_DATA();
            DeviceInterfaceData.cbSize = Marshal.SizeOf(DeviceInterfaceData);
    
            // Loop through the set of devices found by the OS, creating a list of our devices.
            List<String> listHidDevices = new List<String>();
            Int32 i32MemberIndex = 0;
            while (true)
            {
                // Begin with 0 and increment through the device information set until
                // no more devices are available.
                Boolean bSuccess = SetupDiEnumDeviceInterfaces(hDeviceInfoSet,
                    IntPtr.Zero, ref guidHID, i32MemberIndex,
                    ref DeviceInterfaceData);
                if ( ! bSuccess)
                {
                    // If it fails, that means we've reached the end of the list.
                    break;
                }
    
                // A device is present.
                // Find out how big of a buffer is needed.
                Int32 i32BufferSize = 0;
                bSuccess = SetupDiGetDeviceInterfaceDetail(hDeviceInfoSet,
                    ref DeviceInterfaceData, IntPtr.Zero, 0,
                    ref i32BufferSize, IntPtr.Zero);
                String sErrorMessage;
                if (bSuccess)
                {
                    // This success is unexpected! We wanted to get an error, with the attendant
                    // information of how big to make the buffer for a successful call.
                    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);
                }
    
                // Allocate memory for the SP_DEVICE_INTERFACE_DETAIL_DATA structure using the returned buffer size.
                IntPtr pDetailDataBuffer = Marshal.AllocHGlobal(i32BufferSize);
    
                // Store cbSize in the first bytes of the array. The number of bytes varies with 32- and 64-bit systems.
                Marshal.WriteInt32(pDetailDataBuffer, (IntPtr.Size == 4) ? (4 + Marshal.SystemDefaultCharSize) : 8);
    
                // Call SetupDiGetDeviceInterfaceDetail again.
                // This time, pass a pointer to DetailDataBuffer
                // and the returned required buffer size.
                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);
                }
    
                // Skip over cbsize (4 bytes) to get the address of the devicePathName.
                IntPtr pDevicePathName = new IntPtr(pDetailDataBuffer.ToInt32() + 4);
    
                // Get the String containing the devicePathName.
                String sDevicePathName = Marshal.PtrToStringAuto(pDevicePathName).ToLower();
    
                // See if the device path includes our VID and 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("Found device: " + sDevicePathName);
                }
    
                if (pDetailDataBuffer != IntPtr.Zero)
                {
                    // Free the memory allocated previously by AllocHGlobal.
                    Marshal.FreeHGlobal(pDetailDataBuffer);
                }
    
                i32MemberIndex++;
            }
    
            if (hDeviceInfoSet != IntPtr.Zero)
            {
                SetupDiDestroyDeviceInfoList(hDeviceInfoSet);
            }
    
            if (listHidDevices.Count == 0)
            {
                // No devices found.
                return;
            }
    
            SafeFileHandle hHidRead = CreateFile(listHidDevices[0],
                GENERIC_READ | GENERIC_WRITE,
                FILE_SHARE_READ | FILE_SHARE_WRITE,
                IntPtr.Zero, // securityAttributes
                OPEN_EXISTING, // creationDisposition
                FILE_FLAG_OVERLAPPED, //| DUPLEX, // flags
                0); // template
            if (hHidRead.IsInvalid)
            {
                string errorMessage = new Win32Exception(Marshal.GetLastWin32Error()).Message;
                Console.WriteLine(errorMessage);
                // Can't do it, have to bail.
                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);
    
            // Leave an async read dangling.
            BeginAsyncRead(ref fsDeviceRead, i16InputReportLen);
    
            hHidRead.Close();
        }
    
        public struct SyncObj_t
        {
            public FileStream fs;
            public Byte[] buf;
        };

        /// <summary>
        /// Kicks off an asynchronous read which completes when data is read or when the device
        /// is disconnected. Uses a callback.
        /// </summary>
        static public void BeginAsyncRead(ref FileStream fs, int iBufLen)
        {
            SyncObj_t syncObj = new SyncObj_t();
            syncObj.fs = fs;
            syncObj.buf = new Byte[iBufLen];
            // Put the buff we used to receive the stuff as the async state then we can get at it when the read completes
    
            fs.BeginRead(syncObj.buf, 0, iBufLen, new AsyncCallback(ReadCompleted), syncObj);
        }

        /// <summary>
        /// Callback for above. Care with this as it will be called on the background thread from the async read
        /// </summary>
        /// <param name="iResult">Async result parameter</param>
        static protected void ReadCompleted(IAsyncResult iResult)
        {
            // Retrieve the stream and read buffer.
            SyncObj_t syncObj = (SyncObj_t)iResult.AsyncState;
            try
            {
                // call end read : this throws any exceptions that happened during the read
                syncObj.fs.EndRead(iResult);
                try
                {
                    //InputReport oInRep = CreateInputReport();	// Create the input report for the device
                    //oInRep.SetData(arrBuff);	// and set the data portion - this processes the data received into a more easily understood format depending upon the report type
                    // Pass the new input report on to the higher level handler.
                    //HandleDataReceived(oInRep);
                }
                finally
                {
                    // when all that is done, kick off another read for the next report
                    BeginAsyncRead(ref syncObj.fs, syncObj.buf.Length);
                }
            }
            catch (IOException ex)	// if we got an IO exception, the device was removed
            {
                Console.WriteLine(ex.ToString());
                /*
                HandleDeviceRemoved();
                if (OnDeviceRemoved != null)
                {
                    OnDeviceRemoved(this, new EventArgs());
                }
                Dispose();
                */
            }
        }
    }
}



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
Posted
Updated 30-Jan-12 12:35pm
v2
Comments
Sergey Alexandrovich Kryukov 30-Jan-12 16:19pm    
This is a code dump. Many said it's rude, but I personally think it is not, but just useless. Where is you question? What do you want to achieve and why do you want it? What help do you expect?
--SA
Herman<T>.Instance 31-Jan-12 4:47am    
he missed a small detail in the async call.

1 solution

C#
static public void BeginAsyncRead

should be
C#
static public IAsyncResult BeginAsyncRead
 
Share this answer
 

This content, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)



CodeProject, 20 Bay Street, 11th Floor Toronto, Ontario, Canada M5J 2N8 +1 (416) 849-8900