Search code examples
c#delphipinvoke

Marshalling plain structures: does C# copy them onto heap?


I have a native DLL, written in Delphi, actively using callback mechanism: a callback function is "registered" and later called from inside of the DLL:

function RegisterCallback(CallbackProc: TCallbackProc): Integer; stdcall;

Most of the callback functions are passing plain structures by reference, like the following:

TCallbackProc = procedure(Struct: PStructType); stdcall;

where PStructType is declared as

TStructType = packed record 
  Parameter1: array[0..9] of AnsiChar;
  Parameter2: array[0..19] of AnsiChar;
  Parameter3: array[0..29] of AnsiChar;
end;
PStructType = ^TStructType;

This DLL is consumed by a .NET application, written in C#. The C# code is written very negligently and the application as a whole behaves unreliably, showing hard-to-identify exceptions, raised in different places from run to run.

I do not have reasons to suspect the DLL, because it had already proved itself as a quite robust piece of software, used in many other applications. What I am currently concerned about is the way how those structures are used in C#.

Let's assume, that the record from above is re-declared in C# as follows:

[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Ansi, Pack = 1)]
public struct TStructType
{
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 10)]
    public string Parameter1;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 20)]
    public string Parameter2;
    [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 30)]
    public string Parameter3;
}

and the callback is declared as

[UnmanagedFunctionPointer(CallingConvention.StdCall)]
public delegate void CallbackProc(ref TStructType Struct);

Now something interesting begins. Let's assume, that in the DLL, the registered callbacks are called in this way:

var
  Struct: TStructType;
begin
  // Struct is initialized and filled with values
  CallbackProc(@Struct);
end;

But what I see in the C# application, and what I do not like at all, that the marshaled structure is saved aside as a pointer for future use:

private void CallbackProc(ref TStructType Struct)
{
    SomeObjectList.Add(Struct); // !!! WTF?
}

As I understand, Struct variable is that created on Delphi's stack deep inside in the DLL, and storing pointer to it on heap in client application - is a sheer adventure.

I am not a big fan/expert of C#, so please excuse my naive question, does the marshaller do something behind the scene, like copy structures onto heap or something like that, or the fact that the application sometimes works is a matter of pure chance?

Thank you in advance.


Solution

  • A C# struct is a value type. Which means that

    SomeObjectList.Add(Struct)
    

    will make a copy of the struct. So, nothing to get concerned about.

    In fact, in CallbackProc you are not operating on the object that was allocated in your Delphi code. That's because the p/invoke marshaller had to take the raw pointer that it received and convert that into a TStructType object. And a TStructType contains C# strings which are most definitely not blittable with those Delphi character arrays. So the marshaller has already added a layer in between your C# code and the Delphi code.

    Since the function receives the structure by ref what happens is as follows:

    1. Before calling CallbackProc the marshaller de-serializes the raw unmanaged pointer to a TStructType object.
    2. CallbackProc is then passed that TStructType object by reference.
    3. When CallbackProc returns, the p/invoke marshaller serializes the TStructType object back to the original raw unmanaged pointer.

    One of the consequences of this is that changes you make to the TStructType object are not visible to the Delphi code until the callback procedure returns. Contrast that to what happens when you call a Delphi procedure passing a variable as a var parameter. In that case any changes in the procedure are visible immediately outside that procedure.