用于传递给 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上运行)。
谢谢。
有两点需要注意:
从 Delphi 2009 年开始,string
是 Unicode(即每个字符两个字节)。
Delphi strings 本质上是 C-style 字符串加上一个额外的 header.
第一点意味着您必须将字符串从 Unicode 转换为 ANSI。第二点意味着您不需要编写一个从 Delphi 字符串创建 C-style 字符串的函数——如果您只是 Delphi 字符串已经是 C-style忽略它的 header。只需将 Delphi 字符串转换为 PChar
即可为您提供指向 C-style 字符串的指针,因为 header 与字符串指针的值有负偏移。
如果 MyDelphiString
是您的 Delphi string
,可以通过简单的转换将其转换为旧的 AnsiString
:AnsiString(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
。
我有一些旧的 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上运行)。
谢谢。
有两点需要注意:
从 Delphi 2009 年开始,
string
是 Unicode(即每个字符两个字节)。Delphi strings 本质上是 C-style 字符串加上一个额外的 header.
第一点意味着您必须将字符串从 Unicode 转换为 ANSI。第二点意味着您不需要编写一个从 Delphi 字符串创建 C-style 字符串的函数——如果您只是 Delphi 字符串已经是 C-style忽略它的 header。只需将 Delphi 字符串转换为 PChar
即可为您提供指向 C-style 字符串的指针,因为 header 与字符串指针的值有负偏移。
如果 MyDelphiString
是您的 Delphi string
,可以通过简单的转换将其转换为旧的 AnsiString
:AnsiString(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
。