RasmusW RasmusW - 1 month ago 15
C Question

C# native callback with pointer to struct parameter

I'm writing a C# wrapper for a native library. It contains this callback function:

typedef application_event_result(*application_event_ptr)(application_request* request);


The parameter is defined as such:

typedef struct {
uint32_t query;
const char* client;
bool isAuthenticated;
bool isGuest;
} application_request;


I have defined the C# callback delegate like this:

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
public delegate application_event_result application_event([MarshalAs(UnmanagedType.Struct)]
ref application_request request);


The structure in C#:

[StructLayout(LayoutKind.Sequential)]
public struct application_request
{
public UInt32 query;

[MarshalAs(UnmanagedType.LPStr)]
public string client;

[MarshalAs(UnmanagedType.I1)]
public bool isAuthenticated;

[MarshalAs(UnmanagedType.I1)]
public bool isGuest;
}


This all seems to work. The callback in C# is triggered, and the members of the struct have the expected values.

But upon return to native code, a heap corruption exception is triggered (0xc0000374).

Obviously, I would like to avoid that.

If I change the C# callback signature to use IntPtr instead of the "ref application_request" parameter, and then Marshal it manually using the following code it works.

var request = Marshal.PtrToStructure<application_request>(requestptr);


But I would like to have the signature be as precise as possible and not have to use the Marshaler myself.

Is there a away for me to change the callback delegate's signature so .net can convert the struct automatically?

Answer

Your problem is the char* member of the struct. The C# marshaler assumes that it is responsible for deallocating that memory. It does so by calling CoTaskMemFree. I think it is pretty clear that the memory is not meant to be destroyed by the C# code at all.

Marshal that member as IntPtr instead:

[StructLayout(LayoutKind.Sequential)]
public struct application_request
{
    public UInt32 query;

    public IntPtr client;

    [MarshalAs(UnmanagedType.I1)]
    public bool isAuthenticated;

    [MarshalAs(UnmanagedType.I1)]
    public bool isGuest; 
}

Inside your callback method you can read the value of the string by calling Marshal.PtrToStringAnsi.

You wrap this up by making the members private and exposing them via properties. That will allow you to encapsulate the conversion from pointer to string.