locked
Asynchronous WriteFile() on 64-bit OS RRS feed

  • Question

  • I am porting an application to 64-bit OS that has run fine on 32-bit OS.  I am currently having a problem with the WriteFile() and associated asynchronous support function calls for accessing a USB HID device.

    [StructLayout(LayoutKind.Sequential)] 
    public struct OVERLAPPED 
        public UIntPtr Internal; 
        public UIntPtr InternalHigh; 
        public uint    Offset; 
        public uint    OffsetHigh; 
        public IntPtr  EventHandle; 


    The file creation call:

    hDevice = APIs.CreateFile(m_DevInterfaceDetailData.DevicePath,     //lpFileName 
                              APIs.GENERIC_READ | APIs.GENERIC_WRITE,  //dwDesiredAccess 
                              0,                                       //dwShareMode 
                              0,                                       //lpSecurityAttributes 
                              APIs.OPEN_EXISTING,                      //dwCreationDisposition 
                              APIs.FILE_FLAG_OVERLAPPED,               //dwFlagsAndAttributes 
                              0);                                      //hTemplateFile 
     


    Here's the abridged writing code:

    public static ushort PutNChr(byte DeviceId, string OutStr) 
        // Milliseconds to wait for write to complete
        const int WRITE_TIMEOUT = 1000; 
     
        APIs.OVERLAPPED  HIDOverlapped = new APIs.OVERLAPPED(); 
        int       dwResult;  
        WindowsErrors ErrorCode; 
        uint       NumBytesWritten; 
        int        bResult; 
        byte[]     LocalBuff = new byte[128];     // Assume buffer is > report size 
        UInt16 i; 
        char[] OutStrAr = OutStr.ToCharArray(); 
        int    OutStrIdx;
     
        // Copy string data to LocalBuff[]
        ....
     
        HIDOverlapped.Offset = 0; 
        HIDOverlapped.OffsetHigh = 0; 
        HIDOverlapped.EventHandle = HIDOUTReportEvent;   // ***This is a manual reset event***
        HIDOverlapped.Internal = UIntPtr.Zero; 
        HIDOverlapped.InternalHigh = UIntPtr.Zero; 
     
        GCHandle PinnedBuffer = GCHandle.Alloc(LocalBuff, GCHandleType.Pinned); 
     
        if (0 == APIs.ResetEvent(HIDOverlapped.EventHandle)) // Make sure event is reset
        {
            // Error handling
        } 
     
        bResult = APIs.WriteFile(hDevice,
                                 PinnedBuffer.AddrOfPinnedObject(), 
                                 64,                                // HID report length is 64 bytes
                                 out NumBytesWritten,  
                                 ref HIDOverlapped); 
                         
        if (bResult == 0) 
        { 
            Console.WriteLine("bResult: FALSE"); 
            ErrorCode = (WindowsErrors)Marshal.GetLastWin32Error(); 
     
            if (ErrorCode == WindowsErrors.ERROR_IO_PENDING) // Overlapped I/O in progress 
            { 
                Console.WriteLine("ERROR_IO_PENDING"); 
            } 
            else 
            {
                // Error handling...
                APIs.CancelIo(hDevice); 
            } //else 
                             
        } //if 
        else 
        { 
            Console.WriteLine("bResult: TRUE"); 
        } 
     
        PinnedBuffer.Free();

        dwResult = APIs.WaitForSingleObject(HIDOUTReportEvent, WRITE_TIMEOUT);
        Console.WriteLine("dwResult: " + dwResult.ToString());

        if (dwResult == APIs.WAIT_TIMEOUT) 
        { 
            Console.WriteLine("Write timeout"); 
            return NumBytesSent; 
        } //if  
        else if (dwResult == APIs.WAIT_ABANDONED) 
        { 
            Console.WriteLine("Write abandoned"); 
            return NumBytesSent; 
        } //if  
     
        NumBytesWritten = 0;
        if (0 != APIs.GetOverlappedResult(hDevice,
                                          ref HIDOverlapped,
                                          out NumBytesWritten,
                                          1))
        { 
            Console.WriteLine("Write Done"); 
        } 
        else 
        { 
            // Error handling
        } 
        Console.WriteLine("NumBytesWritten: " + NumBytesWritten.ToString());
     
        return NumBytesSent;  
    //PutNChr() 

    When run on 32-bit I get the following output:

    bResult: FALSE
    ERROR_IO_PENDING
    dwResult: 0
    Write Done
    NumBytesWritten: 64


    On 64-bit platform:

    bResult: FALSE
    ERROR_IO_PENDING
    dwResult: 0
    Write Done
    NumBytesWritten: 0


    I have verified that the actual data *IS* correctly transferred on the USB bus, yet NumBytesWritten reports 0.  Help please!

    I have also found that if the bWait parameter of GetOverlappedResult is set to 0 then the function call fails and GetLastWin32Error() returns 996: "Overlapped I/O event is not in a signaled state" even though the WaitForSingleObject() has correctly returned indicating that the I/O event is signaled.

    Friday, February 6, 2009 10:53 PM

Answers

  • Okay, I reworked this as recommended and replaced the kernel event (CreateEvent) with a managed ManualResetEvent:

    public static ushort PutNChr(byte DeviceId, string OutStr)     
    {     
        // Milliseconds to wait for write to complete    
        const int WRITE_TIMEOUT = 1000;     
         
        APIs.OVERLAPPED  HIDOverlapped = new APIs.OVERLAPPED();     
        int       dwResult;      
        WindowsErrors ErrorCode;     
        uint       NumBytesWritten = 0;     
        int        bResult;     
        byte[]     LocalBuff = new byte[128];     // Assume buffer is > report size     
        UInt16 i;     
        char[] OutStrAr = OutStr.ToCharArray();     
        int    OutStrIdx;   
         
        GCHandle PinnedBuffer = GCHandle.Alloc(LocalBuff, GCHandleType.Pinned);   
        GCHandle PinnedHIDOverlapped = GCHandle.Alloc(HIDOverlapped, GCHandleType.Pinned);     
         
        // Copy string data to LocalBuff[]   
        ....   
        
         
        HIDOverlapped.Offset = 0;     
        HIDOverlapped.OffsetHigh = 0;     
        HIDOverlapped.EventHandle = HIDOUTReportEvent.SafeWaitHandle.DangerousGetHandle();   // ***This is a manual reset event***   
        HIDOverlapped.Internal = UIntPtr.Zero;     
        HIDOverlapped.InternalHigh = UIntPtr.Zero;     
         
        HIDOUTReportEvent.Reset(); // Make sure event is reset   
        bResult = APIs.WriteFile(hDevice,   
                                 PinnedBuffer.AddrOfPinnedObject(),     
                                 64,                                // HID report length is 64 bytes   
                                 out NumBytesWritten,      
                                 PinnedHIDOverlapped.AddrOfPinnedObject());   
                             
        if (bResult == 0)     
        {     
            Console.WriteLine("bResult: FALSE");     
            ErrorCode = (WindowsErrors)Marshal.GetLastWin32Error();     
         
            if (ErrorCode == WindowsErrors.ERROR_IO_PENDING) // Overlapped I/O in progress     
            {     
                Console.WriteLine("ERROR_IO_PENDING");     
            }     
            else     
            {   
                // Error handling...    
        
                APIs.CancelIo(hDevice);     
            } //else     
                                 
        } //if     
        else     
        {     
            Console.WriteLine("bResult: TRUE");     
        }     
        
        if (!m_Slots[DeviceId].Slot.HIDOUTReportEvent.WaitOne(WRITE_TIMEOUT, false)) 
        { 
          Console.Write("Write timeout or abandoned\n"); 
          break
        } 
     
        if (0 != APIs.GetOverlappedResult(hDevice,   
                                          PinnedHIDOverlapped.AddrOfPinnedObject(),   
                                          out NumBytesWritten,   
                                          1))    
        
        {     
            Console.WriteLine("Write Done");     
        }     
        else     
        {     
            // Error handling    
        }    
        PinnedHIDOverlapped.Free();    
        PinnedBuffer.Free();   
     
        Console.WriteLine("NumBytesWritten: " + NumBytesWritten.ToString());   
        return NumBytesSent;      
    //PutNChr()    

    Now it is back to working on x86 mode:

    bResult: FALSE
    ERROR_IO_PENDING
    Write Done
    NumBytesWritten: 64

    but on x64 mode gives:

    bResult: FALSE
    ERROR_IO_PENDING
    Write Done
    NumBytesWritten: 0

    Again, I verified that the data was transferred on the USB bus, yet GetOverlappedResult reports 0 bytes transferred.

    More help please!
    Thursday, February 12, 2009 2:06 AM
  • Thanks AlexBB.  Checking out WriteFileEx() gave me some clues as to the problem.

    Using WriteFileEx() requires setting up a callback function called on completion of the write.  The callback function is passed the number of bytes written.  In my write function I make the call to WriteFileEx() and then call my ManualResetEvent's WaitOne().  The callback needs to set the event and pass NumberBytesWritten back to the waiting thread.

    I read that in the context of calling WriteFileEx() the hEvent member of  the OVERLAPPED structure is not used.  I figured this would be a convenient way to pass data back and forth to the callback function.  I need the callback function to know the index of my ManualResetEvent (I have an array of them in order to service multiple devices simultaneously) and I need the callback to send back NumberBytesWritten.

    I have found that any changes to HIDOverlapped after creating a GCHandle to it was not reflected in the memory space pointed to by PinnedHIDOverlapped.AddrOfPinnedObject().  However, everything worked if I referenced the overlapped structure through the pinned object:

    For example,

    NumBytesWritten = (uint)(APIs.OVERLAPPED)(PinnedHIDOverlapped.Target).hEvent;

    After discovering that (sorry if my understanding of pinning is not very good), I found all I needed to do to get my previous code working was to pin HIDOverlapped after I was done filling in the structure, not before.

        HIDOverlapped.Offset = 0;      
        HIDOverlapped.OffsetHigh = 0;      
        HIDOverlapped.EventHandle = HIDOUTReportEvent.SafeWaitHandle.DangerousGetHandle();   // ***This is a manual reset event***    
        HIDOverlapped.Internal = UIntPtr.Zero;      
        HIDOverlapped.InternalHigh = UIntPtr.Zero;      
          
        GCHandle PinnedHIDOverlapped = GCHandle.Alloc(HIDOverlapped, GCHandleType.Pinned);      
     
        HIDOUTReportEvent.Reset(); // Make sure event is reset    
        bResult = APIs.WriteFile(hDevice,    
                                 PinnedBuffer.AddrOfPinnedObject(),      
                                 64,                                // HID report length is 64 bytes    
                                 out NumBytesWritten,       
                                 PinnedHIDOverlapped.AddrOfPinnedObject());    
     

    Thanks for everyone's help on this!
    • Marked as answer by kfank Saturday, February 14, 2009 12:45 AM
    Saturday, February 14, 2009 12:45 AM

All replies

  • Try targeting x86 if you're targeting Any CPU or x64.
    Friday, February 6, 2009 11:21 PM
  • The OVERLAPPED buffer is not copied by asynchronous I/O. It has to remain valid throughout the lifetime of the I/O operation.

    So, you need to pin HIDOverlapped, and pass its address to WriteFile/GetOverlappedResult.

    There are several other mistakes in the code, too: the data buffer has to remain pinned until after GetOverlappedResult. Also, doing unmanaged waits is very bad - it cripples the CLR (disables garbage collection, prevents STA pumping, etc). If you don't want to use the provided FileStream (which I believe can be constructed from a SafeFileHandle referring to a device), then at least use ManualResetEvent. You can get the underlying handle by HIDOUTReportEvent.SafeWaitHandle.DangerousGetHandle() to stick in the overlapped structure, and then wait for it using HIDOUTReportEvent.WaitOne().

    I see no 64-bit-specific problems in your code (though I'd have to see the P/Invoke declarations for these APIs before I could say for sure). I think your 32-bit code was just lucky.

           -Steve

    Saturday, February 7, 2009 3:19 PM
  • Targeting to x86 really just sidesteps the issue and forces 32-bit execution.  I would want to compile as "Any CPU" so that the underlying assembly/dll is compatible with 64-bit builds too.

    Steve,

    I am already pinning the data buffer as shown in the code above (see PinnedBuffer).  I have changed the P/Invoke declarations to this:

    [StructLayout(LayoutKind.Sequential)]   
    public struct OVERLAPPED   
    {   
        public UIntPtr Internal;   
        public UIntPtr InternalHigh;   
        public uint    Offset;   
        public uint    OffsetHigh;   
        public IntPtr  EventHandle;   
    }   

    [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
    public static extern IntPtr CreateEvent(
      IntPtr
    lpEventAttributes,  // LPSECURITY_ATTRIBUTES
      int    bManualReset,       // BOOL
      int    bInitialState,      // BOOL
      string lpName);            // LPCTSTR
      

    [DllImport("kernel32", SetLastError = true, ExactSpelling = true)]   
    internal static extern Int32 WaitForSingleObject(IntPtr handle,        // HANDLE   
                                                     Int32  milliseconds); // DWORD   
       
    [DllImport("kernel32.dll", SetLastError = true)]   
    public static extern unsafe int WriteFile(   
      IntPtr   Handle,                // HANDLE   
      IntPtr   lpBuffer,              // LPCVOID   
      uint     NumberOfBytesToWrite,  // DWORD   
      out uint NumberOfBytesWritten,  // LPDWORD   
      IntPtr   OverlappedData);       // LPOVERLAPPED   
       
    [DllImport("kernel32.dll", SetLastError = true)]   
    public static extern int GetOverlappedResult(   
      IntPtr    hFile,                       // HANDLE   
      IntPtr    OverlappedData,              // LPOVERLAPPED   
      out uint  lpNumberOfBytesTransferred,  // LPDWORD   
      int       bWait);                      // BOOL   
     

    The event used in HIDOverlapped is defined as:

    HIDOutReportEvent = APIs.CreateEvent(IntPtr.Zero, 0, 0, "");  
     

    I modified the original code to pin the HIDOverlapped structure as follows:

    public static ushort PutNChr(byte DeviceId, string OutStr)    
    {    
        // Milliseconds to wait for write to complete   
        const int WRITE_TIMEOUT = 1000;    
        
        APIs.OVERLAPPED  HIDOverlapped = new APIs.OVERLAPPED();    
        int       dwResult;     
        WindowsErrors ErrorCode;    
        uint       NumBytesWritten;    
        int        bResult;    
        byte[]     LocalBuff = new byte[128];     // Assume buffer is > report size    
        UInt16 i;    
        char[] OutStrAr = OutStr.ToCharArray();    
        int    OutStrIdx;  
        
        // Copy string data to LocalBuff[]  
        ....  
       
        
        HIDOverlapped.Offset = 0;    
        HIDOverlapped.OffsetHigh = 0;    
        HIDOverlapped.EventHandle = HIDOUTReportEvent;   // ***This is a manual reset event***  
       
        HIDOverlapped.Internal = UIntPtr.Zero;    
        HIDOverlapped.InternalHigh = UIntPtr.Zero;    
        
        GCHandle PinnedBuffer = GCHandle.Alloc(LocalBuff, GCHandleType.Pinned);  
        GCHandle PinnedHIDOverlapped = GCHandle.Alloc(HIDOverlapped, GCHandle.Pinned);    
        
        if (0 == APIs.ResetEvent(HIDOverlapped.EventHandle)) // Make sure event is reset  
       
        {  
            // Error handling  
       
        }    
        
        bResult = APIs.WriteFile(hDevice,  
       
                                 PinnedBuffer.AddrOfPinnedObject(),    
                                 64,                                // HID report length is 64 bytes  
       
                                 out NumBytesWritten,     
                                 PinnedHIDOverlapped.AddrOfPinnedObject());  
                            
        if (bResult == 0)    
        {    
            Console.WriteLine("bResult: FALSE");    
            ErrorCode = (WindowsErrors)Marshal.GetLastWin32Error();    
        
            if (ErrorCode == WindowsErrors.ERROR_IO_PENDING) // Overlapped I/O in progress    
            {    
                Console.WriteLine("ERROR_IO_PENDING");    
            }    
            else    
            {  
                // Error handling...   
       
                APIs.CancelIo(hDevice);    
            } //else    
                                
        } //if    
        else    
        {    
            Console.WriteLine("bResult: TRUE");    
        }    
       
        PinnedBuffer.Free();  
     
       
        dwResult = APIs.WaitForSingleObject(HIDOUTReportEvent, WRITE_TIMEOUT);    
        Console.WriteLine("dwResult: " + dwResult.ToString());   
     
       
        if (dwResult == APIs.WAIT_TIMEOUT)    
        {    
            Console.WriteLine("Write timeout");    
            return NumBytesSent;    
        } //if     
        else if (dwResult == APIs.WAIT_ABANDONED)    
        {    
            Console.WriteLine("Write abandoned");    
            return NumBytesSent;    
        } //if     
        
        NumBytesWritten = 0;  
        if (0 != APIs.GetOverlappedResult(hDevice,  
                                          PinnedHIDOverlapped.AddrOfPinnedObject(),  
                                          out NumBytesWritten,  
                                          1))   
       
        {    
            Console.WriteLine("Write Done");    
        }    
        else    
        {    
            // Error handling   
        }   
        PinnedHIDOverlapped.Free();   
        Console.WriteLine("NumBytesWritten: " + NumBytesWritten.ToString());  
        
        return NumBytesSent;     
    //PutNChr()   
     


    To ease debugging the new code I compiled as x86.  Now when I run this code on 64-bit platform I get:

    bResult: FALSE
    ERROR_IO_PENDING
    dwResult: 258
    Write timeout

    I verified that the data actually was written to the USB bus, so something is still wrong.

    Monday, February 9, 2009 10:31 PM
  • You need to keep the data buffer pinned until the I/O operation completes, and replace the unmanaged wait with a managed wait.

           -Steve
    Tuesday, February 10, 2009 4:49 AM
  • Okay, I reworked this as recommended and replaced the kernel event (CreateEvent) with a managed ManualResetEvent:

    public static ushort PutNChr(byte DeviceId, string OutStr)     
    {     
        // Milliseconds to wait for write to complete    
        const int WRITE_TIMEOUT = 1000;     
         
        APIs.OVERLAPPED  HIDOverlapped = new APIs.OVERLAPPED();     
        int       dwResult;      
        WindowsErrors ErrorCode;     
        uint       NumBytesWritten = 0;     
        int        bResult;     
        byte[]     LocalBuff = new byte[128];     // Assume buffer is > report size     
        UInt16 i;     
        char[] OutStrAr = OutStr.ToCharArray();     
        int    OutStrIdx;   
         
        GCHandle PinnedBuffer = GCHandle.Alloc(LocalBuff, GCHandleType.Pinned);   
        GCHandle PinnedHIDOverlapped = GCHandle.Alloc(HIDOverlapped, GCHandleType.Pinned);     
         
        // Copy string data to LocalBuff[]   
        ....   
        
         
        HIDOverlapped.Offset = 0;     
        HIDOverlapped.OffsetHigh = 0;     
        HIDOverlapped.EventHandle = HIDOUTReportEvent.SafeWaitHandle.DangerousGetHandle();   // ***This is a manual reset event***   
        HIDOverlapped.Internal = UIntPtr.Zero;     
        HIDOverlapped.InternalHigh = UIntPtr.Zero;     
         
        HIDOUTReportEvent.Reset(); // Make sure event is reset   
        bResult = APIs.WriteFile(hDevice,   
                                 PinnedBuffer.AddrOfPinnedObject(),     
                                 64,                                // HID report length is 64 bytes   
                                 out NumBytesWritten,      
                                 PinnedHIDOverlapped.AddrOfPinnedObject());   
                             
        if (bResult == 0)     
        {     
            Console.WriteLine("bResult: FALSE");     
            ErrorCode = (WindowsErrors)Marshal.GetLastWin32Error();     
         
            if (ErrorCode == WindowsErrors.ERROR_IO_PENDING) // Overlapped I/O in progress     
            {     
                Console.WriteLine("ERROR_IO_PENDING");     
            }     
            else     
            {   
                // Error handling...    
        
                APIs.CancelIo(hDevice);     
            } //else     
                                 
        } //if     
        else     
        {     
            Console.WriteLine("bResult: TRUE");     
        }     
        
        if (!m_Slots[DeviceId].Slot.HIDOUTReportEvent.WaitOne(WRITE_TIMEOUT, false)) 
        { 
          Console.Write("Write timeout or abandoned\n"); 
          break
        } 
     
        if (0 != APIs.GetOverlappedResult(hDevice,   
                                          PinnedHIDOverlapped.AddrOfPinnedObject(),   
                                          out NumBytesWritten,   
                                          1))    
        
        {     
            Console.WriteLine("Write Done");     
        }     
        else     
        {     
            // Error handling    
        }    
        PinnedHIDOverlapped.Free();    
        PinnedBuffer.Free();   
     
        Console.WriteLine("NumBytesWritten: " + NumBytesWritten.ToString());   
        return NumBytesSent;      
    //PutNChr()    

    Now it is back to working on x86 mode:

    bResult: FALSE
    ERROR_IO_PENDING
    Write Done
    NumBytesWritten: 64

    but on x64 mode gives:

    bResult: FALSE
    ERROR_IO_PENDING
    Write Done
    NumBytesWritten: 0

    Again, I verified that the data was transferred on the USB bus, yet GetOverlappedResult reports 0 bytes transferred.

    More help please!
    Thursday, February 12, 2009 2:06 AM
  • Hmmm. I'm out of ideas on why this would still not be working.

    Have you tried the same from unmanaged code, just as a test?

          -Steve
    Thursday, February 12, 2009 10:01 PM
  • Sorry for being such a newb at this stuff.  In order to test with unmanaged code I thought I would recompile a 32-bit project I have.  I have not figured out how to specify in VS2005 that I am building a 64-bit DLL.  It comes up as Win32.  The build platform is Vista64.
    Friday, February 13, 2009 1:13 AM
  •  WriteFileEx.

    Also UAC for x64 seem to be more rigid than for x86. make sure all permist are there, etc.
    AlexB
    Friday, February 13, 2009 1:17 AM
  • For unmanaged x64 code, I think you have to go to the solution's Configuration, and in the platform drop-down box select "New...". That's how it is on VS2008, anyway (it's been more than a year since I've used VS2005). :)

           -Steve
    Friday, February 13, 2009 12:13 PM
  • Thanks AlexBB.  Checking out WriteFileEx() gave me some clues as to the problem.

    Using WriteFileEx() requires setting up a callback function called on completion of the write.  The callback function is passed the number of bytes written.  In my write function I make the call to WriteFileEx() and then call my ManualResetEvent's WaitOne().  The callback needs to set the event and pass NumberBytesWritten back to the waiting thread.

    I read that in the context of calling WriteFileEx() the hEvent member of  the OVERLAPPED structure is not used.  I figured this would be a convenient way to pass data back and forth to the callback function.  I need the callback function to know the index of my ManualResetEvent (I have an array of them in order to service multiple devices simultaneously) and I need the callback to send back NumberBytesWritten.

    I have found that any changes to HIDOverlapped after creating a GCHandle to it was not reflected in the memory space pointed to by PinnedHIDOverlapped.AddrOfPinnedObject().  However, everything worked if I referenced the overlapped structure through the pinned object:

    For example,

    NumBytesWritten = (uint)(APIs.OVERLAPPED)(PinnedHIDOverlapped.Target).hEvent;

    After discovering that (sorry if my understanding of pinning is not very good), I found all I needed to do to get my previous code working was to pin HIDOverlapped after I was done filling in the structure, not before.

        HIDOverlapped.Offset = 0;      
        HIDOverlapped.OffsetHigh = 0;      
        HIDOverlapped.EventHandle = HIDOUTReportEvent.SafeWaitHandle.DangerousGetHandle();   // ***This is a manual reset event***    
        HIDOverlapped.Internal = UIntPtr.Zero;      
        HIDOverlapped.InternalHigh = UIntPtr.Zero;      
          
        GCHandle PinnedHIDOverlapped = GCHandle.Alloc(HIDOverlapped, GCHandleType.Pinned);      
     
        HIDOUTReportEvent.Reset(); // Make sure event is reset    
        bResult = APIs.WriteFile(hDevice,    
                                 PinnedBuffer.AddrOfPinnedObject(),      
                                 64,                                // HID report length is 64 bytes    
                                 out NumBytesWritten,       
                                 PinnedHIDOverlapped.AddrOfPinnedObject());    
     

    Thanks for everyone's help on this!
    • Marked as answer by kfank Saturday, February 14, 2009 12:45 AM
    Saturday, February 14, 2009 12:45 AM