locked
PInvoke works 64-bit, fails 32-bit (AccessViolationException) RRS feed

  • Question

  • Hi folks,

    I've written a fast CRC32 implementation in "C" that I have built as both 32-bit and 64-bit DLL's which I use from a single "Any CPU" targetted library assembly.  The reason for doing so is it's about 50% fastrer than the fastest C# version I could find or build myself.

    The loading assembly re-exposes the API's exported by the DLL, and these dynamically use the correct DLL (32 or 64-bit) depending on whether the managed assembly is running in 32-bit or 64-bit mode.  Here are the PInvoke decl's:

    [DllImport("NativeUtil64.dll", EntryPoint = "CrcComputeHash")]
    
    static extern uint CrcComputeHash64(byte[] buffer, uint length);
    
    [DllImport("NativeUtil32.dll", EntryPoint = "CrcComputeHash")]
    
    static extern uint CrcComputeHash32(byte[] buffer, uint length);
    

     

    I then expose a wrapper "CrcComputeHash" which simply calles the 64-bit or 32-bit depending on the length of IntPtr.

    The 64-bit version always works.  No problem.

    When I either run on a 32-bit OS (or force target CPU to be x86), the 32-bit version is invoked, and as soon as the "C" side of things attempts to read the first byte of the data array, I get an AccessViolationException thrown back through PInvoke:

    "Attempted to read or write protected memory. This is often an indication that other memory is corrupt."

    I am not using "unsafe", and I have as you can see not used any special marshalling keyword on the byte array.  Perhaps I should?  And if so, why is it not necessary for 64-bit?

    This code works for weeks in 64-bit land, being run thousands upon thousands of times on many different 64-bit machines.  But when I finally forced testing on a 32-bit OS this failed immediately, and continues to fail every single call.  Both platforms are Windows 7 Enterprise.

    Setting aside the fact that the behavior is different, is there something here that I'm missing that is causing the byte array to be marshalled incorrectly?  The array is not modified or changed; only read using pointer math on the "C" side, it starts at the first byte, CRC's it against the previous CRC value, and then increments the pointer to the next location, and continues to do so until length is met.

    Here's the "C" code in case anybody cares:

    extern "C" DWORD __declspec (dllexport)CrcComputePartialHash(char *buffer, unsigned __int64 length, unsigned int oldcrc)
    {
      register DWORD oldcrc32;
      register int c;
      
      oldcrc32 = oldcrc;
    
      for ( ; length; --length, ++buffer)
      {
        c = *buffer;
        oldcrc32 = UPDC32(c, oldcrc32);
      }
    
      return oldcrc32;
    }

    If anybody has any thoughts I'd appreciate them.  At first I thought my 32-bit version of the DLL had problems, but I wrote a test 32-bit C exe and it uses it fine.  So it has to be something about my invocation, yet on the 64-bit side, the same exact invocation mechanism works.

    - Rhino

    Wednesday, May 26, 2010 5:51 PM

Answers

All replies

  • I forgot to add, this is a secondary API in addition to one to CRC an entire file, and that one is used primarily.  Obviously, the above API would suffer heavily from PInvoke overhead, and any gains from the more efficient CRC32 would be offset at least partially.

    For now, to work around this defect, I'm using my native CRC32 for the case where I have to hash a byte array, and still using the native version when I have to hash an entire file.  It's an acceptable workaround, but I really want to know why I'm seeing different behavior under 32-bit and 64-bit.

    Thanks,

    - Rhino

    Wednesday, May 26, 2010 5:59 PM
  • What's the unmanaged signature for CrcComputeHash?

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    Wednesday, May 26, 2010 6:36 PM
  • OK I figured out what I was doing wrong and fixed it, but I want to make sure I understand why I was seeing what I was seeing.

    Sorry, here is the entrypoint I actually call, which immmediately invokes the "partial" function providing the starter CRC value which I showed in my original post:

    extern "C" DWORD __declspec (dllexport)CrcComputeHash(char *buffer, unsigned __int64 length)

    The same function signature is used for both 32-bit and 64-bit.

    On the C# side it's declared with:

    [DllImport("NativeUtil32.dll", EntryPoint = "CrcComputeHash")]

    static extern uint CrcComputeHash32(byte[] buffer, uint length);   <-- the uint is wrong!

    Note the bold.  And there is the problem lol.  I have __int64 on the receiving end, and uint on the calling end.  When I changed it to ulong, it now works for both 32-bit and 64-bit.

    What I think was happening is since I mistakenly declared the length a managed uint, only 32-bits were marshalled for the length, leaving cruft in the __int64 length parameter making it artifically large, which ultimately resulted in the loop walking off the end of the array.  So it makes perfect sense why it was blowing up...at least for 32-bit.

    At first I couldn't understand why it wasn't blowing up in 64-bit as well, because a managed uint is 32-bits in either case.  Why would it not always blow up?  Wasn't I always going to pass only half of the value, casuing a corrupt "length" parameter and the same bug?

    Is PInvoke perhaps marshalling 64-bits of a data for a 32-bit managed int in 64-bit processes because it knows native int's are 64-bit for 64-bit processes?  I guess that makes sense, but is suprising to me...

    Or is there some other explanation that eludes me...

    Thanks - Rhino

    Wednesday, May 26, 2010 7:45 PM
  • The calling convention for x64 is different than for x86 (cdecl, by default).

    An unbalanced cdecl call will unbalance the stack, whereas (in this case) the x64 calling convention passes the 32-bit int in a 64-bit register.

           -Steve


    Programming blog: http://nitoprograms.blogspot.com/
      Including my TCP/IP .NET Sockets FAQ
      and How to Implement IDisposable and Finalizers: 3 Easy Rules
    Microsoft Certified Professional Developer

    How to get to Heaven according to the Bible
    • Marked as answer by RhinoNY Thursday, May 27, 2010 3:47 PM
    Wednesday, May 26, 2010 8:15 PM
  • Thanks Stephen,

    That makes perfect sense.  I did not know thet cdecl and stdcall were 32-bit only, and that x64 / Itanium compilers had their own calling conventions which quietly ignore cdecl (and stdcall for that matter it seems). 

    Learn something new every day...  ;-)

    Thanks again...marked as answer!

    - Rhino

    Thursday, May 27, 2010 3:46 PM