I'm attempting to return a small (8 byte) struct by value from a delegate bound to a native function, but am running into the following error when targeting the .NET Framework 2.0 (the code seems to work correctly when targeting 4.0+):
An unhandled exception of type 'System.AccessViolationException' occurred in testclient.exe
Additional information: Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
I suspect I've messed up the managed type annotations such that the return value isn't being marshalled correctly, but I can't see what I'm doing wrong. Below is the code for a small native test DLL and managed client which reproduces the problem.
C/C++ Win32 (x86) test DLL
//Natural alignment, blittable, sizeof(StatusBlock) == 8
struct StatusBlock{
std::uint32_t statusA;
std::uint32_t statusB;
};
/*
* When compiled this function stores the 64bit return value in the
* eax:edx register pair as expected.
*/
static StatusBlock __cdecl SomeFunction(std::uint32_t const someVal){
return StatusBlock{ someVal, 0x1234ABCD };
}
//Exported
extern "C" PVOID __stdcall GetFunctionPointer(){
return &SomeFunction;
}
C# test client
class Program
{
//Blittable, Marshal.SizeOf(typeof(StatusBlock)) == 8
[StructLayout(LayoutKind.Sequential)]
private struct StatusBlock
{
public UInt32 statusA;
public UInt32 statusB;
}
[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
private delegate StatusBlock SomeFunction(UInt32 someVal);
[DllImport("testlib.dll",CallingConvention = CallingConvention.StdCall)]
private static extern IntPtr GetFunctionPointer();
static void Main(string[] args)
{
var fnPtr = GetFunctionPointer();
System.Diagnostics.Debug.Assert(fnPtr != IntPtr.Zero);
var someFn = (SomeFunction)Marshal.GetDelegateForFunctionPointer(fnPtr, typeof(SomeFunction));
/*
* Crashes here with a System.AccessViolationException when targeting .NET Framework 2.0.
* Works as expected when targeting .NET Framework 4.0 +
*/
var statusBlock = someFn(22);
}
}
It's worth noting that if the return type of the delegate is Uint64
the application works as expected in both the .NET 2.0 and 4.0 case. However, I shouldn't have to do this; StatusBlock
should marshal correctly.
Have I just been lucky when targeting .NET 4.0? Any insight into what I'm doing wrong would be much appreciated.