Passing array of struct from c# to delphi

c#, delphi, pinvoke

Solution

Seems like I found the issue:

The code runs fine if .NET 4.0 or higher is used. If you use .NET 3.5 or lower the len-parameter has to be passed by value.

See MSDN-documentation SizeParamIndex v3.5:

The parameter containing the size must be an integer that is passed by value.

See MSDN-documentation SizeParamIndex v4.0:

When arrays are passed as C-style arrays, the marshaler cannot determine the size of the array. Therefore, to pass an managed array to an unmanaged function or method, you must provide two arguments:

The array, defined by reference or value.

The array size, defined by reference or value.

Code working with .NET 3.5:

C#

[DllExport]
public static int func(
    [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
    Sample[] samples,
    int len,
    ref int outLen
)
{
    // len holds the length of the array on input
    // outLen is assigned the number of items that have been assigned values 
    // use the return value to indicates success and the required array size (>=0) or failure (<0)
    int requiredSize = 20;
    if (requiredSize < len)
    {
        len = requiredSize;
    }
    for (outLen = 0; outLen < len; outLen++)
    {
        samples[outLen].Name = "foo: " + outLen.ToString();
    }
    return requiredSize;
}

Delphi7

function func(samples: PSample; len: Integer; var outLen: Integer): Integer; stdcall;
  external 'ArrayTest.dll';

procedure Test2;
var
  samples: array of TSample;
  i, len: Integer;
begin
  len := 0;
  // query the required array size
  i := func(PSample(samples), len, len);
  if i>0 then
  begin
    len := i;
    SetLength(samples, len);
    if func(PSample(samples), len, len)>=0 then
      for i := 0 to len-1 do
        Writeln(samples[i].Name);
  end;
end;

Conclusion:

The code posted in my question and posted by David Heffernan here only works with .NET >= 4.0! If you have to use .NET <= 3.5 you must pass the arraysize by value and not by reference!

Problem

I am using Robert Giesecke Unmanaged Exports 1.2.6 in VS2010 and my goal is to pass an array of structs from c# (.NET 3.5) to delphi (D7). I have to admit, that I'm not that familiar with delphi. I've already read this post, but the suggested answer didn't work for me: When calling `func` in delphi the CPU-debugging-window opens and if I continue the app exits without exception and without the desired result. Here is the code I tried: C# platform x86 ``` using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Runtime.InteropServices; using RGiesecke.DllExport; namespace ArrayTest { public class Class1 { public struct Sample { [MarshalAs(UnmanagedType.BStr)] public string Name; } [DllExport] public static int func( [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Sample[] samples, ref int len ) { // len holds the length of the array on input // len is assigned the number of items that have been assigned values // use the return value to indicate success or failure for (int i = 0; i < len; i++) samples[i].Name = "foo: " + i.ToString(); return 0; } } } ``` Delphi7 ``` program DelphiApp; {$APPTYPE CONSOLE} uses SysUtils, ActiveX; type TSample = record Name: WideString; end; PSample = ^TSample; function func(samples: PSample; var len: Integer): Integer; stdcall; external 'ArrayTest.dll'; procedure Test2; var samples: array of TSample; i, len: Integer; begin len := 10; SetLength(samples, len); if func(PSample(samples), len)=0 then for i := 0 to len-1 do Writeln(samples[i].Name); end; begin Test2(); end. ``` As mentioned earlier, the debugger opens the CPU-Window and if I continue the app exits without exception or error message. If I run it without dbugger, Windows tells me the app isn't working any more and the app closes. What am I missing? Update Modified code: ``` [DllExport] public static int func( [Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] Sample[] samples, ref int len ) { Console.WriteLine("return 0"); return 0; } procedure Test2; var samples: array of TSample; i, len: Integer; begin len := 10; SetLength(samples, len); if func(PSample(samples), len)=0 then for i := 0 to len-1 do Writeln('D7: ', i); end; ``` Even if I don't access the array on either side, the behaviour still is the same. Console-output: `return 0`

Original source