DllExport:将数组数组从 Delphi 传递到 C#

DllExport: Passing an array of arrays from Delphi to C#

我正在使用 RGiesecke 的 "Unmanaged Exports" 包从 C# 创建一个可以从 Delphi 应用程序调用的 dll。

具体来说,我希望传递结构数组的数组。

我在 C# 中所做的工作是

public struct MyVector
{
  public float X;
  public float Y;
}

[DllExport]
public static void DoStuff([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] 
  MyVector[] vectors, int count)
{
  // Do stuff
}

然后可以从 Delphi 调用它,执行如下操作:

unit MyUnit
interface
type
  TVector = array[X..Y] of single;
  TVectorCollection = array of TVector;
  procedure TDoExternalStuff(const vectors : TVectorCollection; count : integer; stdcall;
  procedure DoSomeWork;

implementation

procedure DoSomeWork;
var
  vectors : array of TVector;
  fDoExternalStuff : TDoExternalStuff;
  Handle: THandle;
begin
  // omitted: create and fill vectors
  Handle := LoadLibrary('MyExport.dll');
  @fDoExternalStuff := GetProcAddress(Handle, 'DoStuff');
  fDoExternalStuff(vectors, Length(vectors)); 
end;

end.

然而,我真正需要做的是传递一组 TVector 数组。包含 TVector 数组的结构数组也可以。但是写

[DllExport]
public static void DoStuff([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] 
  MyVector[][] vectors, int count)
{
  // Do stuff
}

不适用于 Delphi

...
TVectorCollection = array of array of TVector;
...

procedure DoSomeWork;
var
  vectors : array of array of TVector;
  fDoExternalStuff : TDoExternalStuff;
  Handle: THandle;
begin
  // omitted: create and fill vectors
  Handle := LoadLibrary('MyExport.dll');
  @fDoExternalStuff := GetProcAddress(Handle, 'DoStuff');
  fDoExternalStuff(vectors, Length(vectors)); //external error 
end;

如果确实如此,我也会感到有点惊讶,因为我没有在任何地方指定锯齿状数组的各个元素的长度。

有没有办法设置我的 DllExport 函数以编组此类元素?

Is there a way for me to setup my DllExport function to be able to marshal this type of element?

不,p/invoke 编组永远不会下降到子数组中。您将必须手动编组。

我个人会传递一个指向子数组第一个元素的指针数组,以及一个子数组长度数组。

在 C# 方面,它将如下所示:

[DllImport("kernel32.dll", EntryPoint = "CopyMemory", SetLastError = false)]
public static extern void CopyMemory(IntPtr dest, IntPtr src, uint count);

[DllExport]
public static void DoStuff( 
    [In] 
    int arrayCount, 
    [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] 
    IntPtr[] arrays, 
    [In, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 0)] 
    int[] subArrayCount, 
)
{
    MyVector[][] input = new MyVector[arrayCount];
    for (int i = 0; i < arrayCount; i++)
    {
        input[i] = new MyVector[subArrayCount[i]];
        GCHandle gch = GCHandle.Alloc(input[i], GCHandleType.Pinned);
        try
        {
            CopyMemory(
                gch.AddrOfPinnedObject(), 
                arrays[i], 
                (uint)(subArrayCount[i]*Marshal.SizeOf(typeof(MyVector))
            );
        }
        finally
        {
            gch.Free();
        }
    }
}

有点乱,因为我们不能使用 Marshal.Copy 因为它不知道你的结构。 []没有简单的内置方法可以从 IntPtr 复制到 IntPtr](https://github.com/dotnet/corefx/issues/493)。因此 CopyMemory 的 p/invoke。无论如何,有很多方法可以给这个蒙皮,这只是我的选择。请注意,我依赖于你的类型是 blittable 的。如果您更改了类型以使其不可 blittable,那么您需要使用 Marshal.PtrToStructure.

在 Delphi 方面,您可以作弊并利用动态数组的动态数组实际上是指向子数组指针数组的指针这一事实。它看起来像这样:

type  
  TVectorDoubleArray = array of array of TVector;
  TIntegerArray = array of Integer;

procedure DoStuff(
  arrays: TVectorDoubleArray; 
  arrayCount: Integer;
  subArrayCount: TIntegerArray
); stdcall; external dllname;

....

procedure CallDoStuff(const arrays: TVectorDoubleArray);
var
  i: Integer;
  subArrayCount: TIntegerArray;
begin
  SetLength(subArrayCount, Length(arrays));
  for i := 0 to high(subArrayCount) do
    subArrayCount[i] := Length(arrays[i]);
  DoStuff(Length(Arrays), arrays, subArrayCount);
end;