用于传递给 C DLL 的现代 Delphi 到 C 字符串

Modern Delphi to C strings for passing to C DLLs

我有一些旧的 C DLL 作为我的 Delphi 应用程序和 MATLAB 编译的 DLL 之间的中介。这些最初是用 Delphi 7 开发的,并且运行良好。然而,现在我先升级到 XE7,现在又升级到 10.2.3,但我遇到了问题。基本上,我必须将指向记录的指针传递给 DLL;该结构包括指向老式 C 风格字符串的字段(即字符数组,其中每个字符都是一个字节,并以 0 字节结尾)。

所以考虑这个记录:

MyRec = record
  id: Integer;
  name: PByte;
end;

我写了这样一个函数:

function MakeCString(S: String): TBytes;
var
  I: Integer;
  len: Integer;
begin
  len := Length(S);
  SetLength(Result, len + 1);
  if len > 0 then
    begin
      for I := 0 to (len - 1) do
        begin
          Result[i] := Byte(S[I + 1]);
        end;
    end;
  Result[len] := 0;
end;

那么,我的意图是这样使用它:

var r: MyRec;
r.id := 27;
r.name := MakeCString('Jimbo');
callMatlabWrapperDll(@r);

我知道这很难看并且容易失忆——如果它能可靠地工作的话!它似乎有点工作,但现在在另一台 Windows 10 机器上(可能),在我的开发机器上(也是 Windows 10),我得到错误。

我已经在这个问题上苦苦思索了很久。我需要的是一种创建 C 风格字符串的方法,将其地址传递给 DLL,然后进行清理。 (注意:这都是Win32,Win64上运行)。

谢谢。

有两点需要注意:

  1. 从 Delphi 2009 年开始,string 是 Unicode(即每个字符两个字节)。

  2. Delphi strings 本质上是 C-style 字符串加上一个额外的 header.

第一点意味着您必须将字符串从 Unicode 转换为 ANSI。第二点意味着您不需要编写一个从 Delphi 字符串创建 C-style 字符串的函数——如果您只是 Delphi 字符串已经是 C-style忽略它的 header。只需将 Delphi 字符串转换为 PChar 即可为您提供指向 C-style 字符串的指针,因为 header 与字符串指针的值有负偏移。

如果 MyDelphiString 是您的 Delphi string,可以通过简单的转换将其转换为旧的 AnsiStringAnsiString(MyDelphiString)。这将在堆上创建一个新字符串,其内容相同但现在采用旧版 ANSI 格式(每个字符 1 个字节)。当然你会失去像⌬∮☃电脑这样的“特殊”字符。

让我们将它保存到一个局部变量中,这样我们就知道它的生命周期(至少)只要这个变量在范围内(感谢 Remy Lebeau):

var
  MyAnsiString: AnsiString;
begin
  MyAnsiString := AnsiString(MyDelphiString)

并且要获得指向 C-style 字符串的指针,该字符串是该字符串 object 的子集,只需写入 PAnsiChar(MyAnsiString)。如果您坚持在记录中使用 PByte 类型,您也可以明确说明这种重新解释:

r.Name = PByte(PAnsiChar(MyAnsiString))

但是如果记录成员的类型是 PAnsiChar 而不是 PByte 会更好。

您的 MakeCString() 执行错误。它没有将 Unicode 字符转换为 ANSI,它只是将字符原样从 16 位截断为 8 位,这不是一回事。

就此而言,您实际上根本不需要 MakeCString()。您可以改用 TEncoding.GetBytes()

此外,您将“转换”的结果直接分配给记录的 PByte 字段。您应该首先将其分配给本地 TBytes 变量,然后获取指向其数据的指针。

试试这个:

type
  MyRec = record
    id: Integer;
    name: PByte;
  end;

procedure callMatlab;
var
  r: MyRec;
  name: TBytes;
begin
  name := TEncoding.Default.GetBytes('Jimbo'#0);
  r.id := 27;
  r.name := PByte(name);
  callMatlabWrapperDll(@r);
end;

或者,您可以(并且应该)使用 PAnsiChar,这是 C 风格的 API 通常用于 8 位字符串的方式,例如:

type
  MyRec = record
    id: Integer;
    name: PAnsiChar;
  end;

procedure callMatlab;
var
  r: MyRec;
  name: AnsiString;
begin
  name := AnsiString('Jimbo');
  r.id := 27;
  r.name := PAnsiChar(name);
  callMatlabWrapperDll(@r);
end;

这会更符合您的 Delphi 7 代码,当时 String 仍然是 AnsiString