在C#中使用Delphi的结构数组和字符串

4

我一直在尝试以下方式调用 Delphi 中创建的方法:

 function _Func1(arrParams: array of TParams): Integer;stdcall;    

 type 
   TParams = record
   Type: int;
   Name: string;
   Amount : Real;
 end;

我的代码是:

[DllImport("some.dll", EntryPoint = "_Func1", CallingConvention = CallingConvention.StdCall)]
public static extern int Func(
  [MarshalAs(UnmanagedType.LPArray, ArraySubType=UnmanagedType.Struct)] TParams[] arrParams)

结构体如下:

[StructLayoutAttribute(LayoutKind.Sequential, CharSet = CharSet.Ansi)]
public struct TParams
{
  public int Type;
  [MarshalAs(UnmanagedType.AnsiBStr)]
  public string Name;
  public double Amount;
}

当我调用这个方法时,出现了错误: 无法封送类型为'TParams'的字段'Name':无效的托管/非托管类型组合(字符串字段必须与LPStr、LPWStr、BStr或ByValTStr配对)。
然而,这些组合都不起作用,因为Delphi的字符串是带有长度前缀的,并且它肯定是Ansi的(我已经尝试过其他字符串参数)。有人知道如何解决这个问题吗?
2个回答

7
这里有两个主要问题,一个是使用开放数组,另一个是使用 Delphi string开放数组 Delphi 的开放数组通过传递指向数组第一个元素的指针以及一个额外的参数来实现,该参数指定了最后一个项目的索引,即 Delphi 术语中的high。更多信息请参见this answerDelphi 字符串 C# marshaller 无法与 Delphi 字符串进行交互。Delphi 字符串是私有类型,仅在 Delphi 模块内部使用。相反,您应该使用以空字符结尾的字符串PAnsiChar
将所有内容结合起来,你可以像这样编写它:
Delphi
type 
  TParams = record
    _Type: Integer;//Type is a reserved word in Delphi
    Name: PAnsiChar;
    Amount: Double;
  end;

function Func(const arrParams: array of TParams): Integer; stdcall;

C#

[StructLayoutAttribute(LayoutKind.Sequential)]
public struct TParams
{
  public int Type;
  public string Name;
  public double Amount;
}

[DllImport("some.dll")]
public static extern int Func(TParams[] arrParams, int high);

TParams[] params = new TParams[len];
...populate params
int retval = Func(params, params.Length-1);

感谢您的回答和编辑。我会尝试这个解决方案。据我所知,我需要创建一个新的Delphi库,该库将公开使用字符串类型的函数,并将它们替换为PAnsiChar?这是正确的吗? - xurc
是的,那是正确的。魔鬼在于细节,但从高层次来看,你所说的是正确的。如果你正在做那件事,我可能也会避免使用开放数组,并明确接收指向第一个元素和元素数量的指针。 - David Heffernan

1
为了补充David的回答,你可以将其转换为Delphi字符串,但这很丑陋。在C#中,你必须用IntPtr替换结构体中的所有字符串。
private static IntPtr AllocDelphiString(string str)
{
    byte[] unicodeData = Encoding.Unicode.GetBytes(str);
    int bufferSize = unicodeData.Length + 6;

    IntPtr hMem = Marshal.AllocHGlobal(bufferSize);

    Marshal.WriteInt32(hMem, 0, unicodeData.Length); // prepended length value

    for (int i = 0; i < unicodeData.Length; i++)
        Marshal.WriteByte(hMem, i + 4, unicodeData[i]);

    Marshal.WriteInt16(hMem, bufferSize - 2, 0); // null-terminate

    return new IntPtr(hMem.ToInt64() + 4);
}

这可以直接发送到Delphi,它将被正确地读取为字符串。

请记住,在完成后必须释放此字符串。但是,不能直接在指向字符串的指针上调用GlobalFree(),因为它不指向分配的起始位置。您需要将该指针转换为长整型,然后减去4,再将其转换回指针。这样可以补偿长度前缀。


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