如何从C#中调用Delphi DLL中的函数

4

我在 Delphi 代码中定义了以下函数:

procedure TestFLASHWNew(
    name: array of string; 
    ID: array of Integer;
    var d1:double
); stdcall;

我该如何定义并从C#中调用它?


1
我认为你需要更改那些数组声明的方式。如果我没有错的话,像那样的动态数组实际上只与 Delphi 兼容。不过,我可能是错的。 - Lasse V. Karlsen
3
你能访问Delphi源代码吗?我问这个问题是因为在不进行大量黑客攻击的情况下,C#无法调用该签名。string不是Interop类型,它是原生的Delphi类型。而且Pascal开放数组也行不通。但双参数没问题!! - David Heffernan
我有Delphi源代码。 - mans
这两个数组长度相同吗?每个名称和ID是否都是成对的? - David Heffernan
是的,它们是。数组大小不是固定的,但我始终知道它们的大小(在C#和Delphi代码中都是如此)。 - mans
2个回答

5

这是一个有些混乱的P/Invoke,因为你不能(就我有限的知识而言)使用任何内置的易于编组技术。相反,您需要像这样使用Marshal.StructureToPtr

C#

[StructLayout(LayoutKind.Sequential)]
public struct MyItem
{
    [MarshalAs(UnmanagedType.LPWStr)]
    public string Name;
    public int ID;
}

[DllImport(@"mydll.dll")]
private static extern void TestFLASHWNewWrapper(IntPtr Items, int Count, ref double d1);

static void Main(string[] args)
{
    MyItem[] items = new MyItem[3];
    items[0].Name = "JFK";
    items[0].ID = 35;
    items[1].Name = "LBJ";
    items[1].ID = 36;
    items[2].Name = "Tricky Dicky";
    items[2].ID = 37;

    IntPtr itemsPtr = Marshal.AllocHGlobal(Marshal.SizeOf(typeof(MyItem))*items.Length);
    try
    {
        Int32 addr = itemsPtr.ToInt32();
        for (int i=0; i<items.Length; i++)
        {
            Marshal.StructureToPtr(items[i], new IntPtr(addr), false);
            addr += Marshal.SizeOf(typeof(MyItem));
        }

        double d1 = 666.0;
        TestFLASHWNewWrapper(itemsPtr, items.Length, ref d1);
        Console.WriteLine(d1);
    }
    finally
    {
        Marshal.FreeHGlobal(itemsPtr);
    }
}

Delphi

TItem = record
  Name: PChar;
  ID: Integer;
end;
PItem = ^TItem;

procedure TestFLASHWNewWrapper(Items: PItem; Count: Integer; var d1: Double); stdcall;
var
  i: Integer;
  name: array of string;
  ID: array of Integer;
begin
  SetLength(name, Count);
  SetLength(ID, Count);
  for i := 0 to Count-1 do begin
    name[i] := Items.Name;
    ID[i] := Items.ID
    inc(Items);
  end;
  TestFLASHWNew(name, ID, d1);
end;

我已经使用一个包装函数来调用你的TestFLASHWNew函数,但你肯定想重新设计它。

我假设你正在使用带有Unicode字符串的Delphi。如果不是,请将[MarshalAs(UnmanagedType.LPWStr)]更改为[MarshalAs(UnmanagedType.LPStr)]


谢谢。我明天会检查并回报! - mans
粗糙的剪切和粘贴。现在已经存在。 - David Heffernan
谢谢。如何传递一个二维数组? - mans
我无法在评论中回答这个问题。这值得一个新的问题。话虽如此,我认为我会将数组压平为1维,并将尺寸作为参数传递。 - David Heffernan

2
Delphi函数有两个问题需要被非Delphi代码调用:
  • 它使用了专有的Delphi字符串实现。
  • 它使用了开放数组,同样是专有实现。
了解开放数组的实现方式以及如何设置堆栈来传递它们可以允许(已经记录)在另一端使用一些“黑客”来从堆栈中读取这些参数。对于字符串来说,情况会更加困难,因为它们的处理更加复杂。
如果您无法更改函数,您可以做的是定义一些简单的包装器,以PChars代替字符串并显式传递数组大小,从而可以从C#(或任何其他语言)调用该函数。

谢谢。您能否提供一些使用传递数组大小和使用pchar的示例代码或引导我到一些示例代码?例如:我如何创建pchar数组以及如何将它们转换为字符串(在Delphi端)。我有DLL的源代码,但由于它非常大,我无法更改它。我可以在Delphi中编写一个新函数,将输入参数从适合在C#和Delphi之间传递的格式转换并导出新函数。 - mans
在Delphi中将PChars转换为字符串非常容易,只需使用类型转换或赋值即可,编译器会处理它。要传递变量大小的数组,您需要传递一个内存缓冲区,其大小为sizeof(Pchar)*元素数量,并且还需要元素数量。在“托管”语言中执行此操作可能比在“非托管”语言中更困难。 - user160694
另一个选择可能是通过COM包装Delphi DLL,然后使用COM数据类型在调用之间传递数据,即使用SAFEARRAYS或Variants。请参见http://msdn.microsoft.com/en-us/library/aa719104(VS.71).aspx和http://msdn.microsoft.com/en-us/library/ms221145.aspx。 - user160694

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接