将一个数组的数组从Delphi传递到c#

本文关键字:数组 Delphi 一个 | 更新日期: 2023-09-27 18:15:54

我正在使用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函数能够封送这种类型的元素?

将一个数组的数组从Delphi传递到c#

是否有一种方法可以让我设置我的DllExport函数能够封送这种类型的元素?

不,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/调用。不管怎样,有很多方法去剥这个皮,这只是我的选择。请注意,我所依赖的是你这种类型的人是易被攻击的。如果你改变了类型,使它不被比特化,那么你需要使用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;