4

I am using RGiesecke's "Unmanaged Exports" package to create a dll from C# that can be called from a Delphi application.

Specifically, I am looking to pass an array of arrays of a struct.

What I have made work in C# is

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
}

Which can then be called from Delphi, doing something like this:

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.

However, what I really need to do is to pass an array of array of TVector. An array of structs that hold an array of TVector would also do. But writing

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

Does not work with 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;

And I would also be a bit surprised if it did, since I am not specifying the length of the individual elements of the jagged array anywhere.

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

1
  • Although it works to declare import the function using the dynamic array TVectorCollection, be careful about this. On the C# side this is treated as a raw pointer. So it's fine as you have it, but if you were to go in the opposite direction this would break down. Commented Oct 2, 2015 at 13:53

1 Answer 1

4

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

No, p/invoke marshalling never descends into sub-arrays. You will have to marshal this manually.

Personally I'd pass an array of pointers to the first elements of the sub-arrays, and an array of the lengths of the sub-arrays.

On the C# side it will look like this:

[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();
        }
    }
}

It's a little messy since we can't use Marshal.Copy because it doesn't know about your struct. And there is []no simple built in way to copy from IntPtr to IntPtr](https://github.com/dotnet/corefx/issues/493). Hence the p/invoke of CopyMemory. Anyway, there's many ways to skin this one, this is just my choice. Do note that I am relying on your type being blittable. If you changed the type so that it was not blittable then you'd need to use Marshal.PtrToStructure.

On the Delphi side you can cheat a little and take advantage of the fact that a dynamic array of dynamic arrays is actually a pointer to an array of pointers to the sub-arrays. It will look like this:

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;
Sign up to request clarification or add additional context in comments.

13 Comments

Thank you! It seems like a crude solution. But if it is only possible to make a crude solution, then so be it. But how would you pass both two lists in one method call? Would you put it in a struct, and if so, how would you marshall that one?
Either in a struct, or as two arguments. You use two arguments at the moment. Replace int count with int[] subArrayCount. Let me know if you have trouble doing this and I'll help.
@yms It's not a multi-dimensional array. It's a ragged array.
David, I am not quite sure i follow how you would do it by changing int count to int[] subArrayCount. Writing public static void DoStuff([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] MyVector[][] vectors, int[] subArrayCount) does not seem to help -- I still get an external error when calling from Delphi where I change the signature of the calling method from delphi to fDoExternalStuff(vectors, subArrayCount); I am probably misunderstanding something?
It's going to be like this: public static void DoStuff([In] IntPtr[] vectors, [In] int[] subArrayCount) Each element of vectors is a pointer to the first element of the subarray. Use Marshal.Copy to copy into a .net array.
|

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.