用 Delphi DLL (x64) 填充 C# String/PWideChar
Fill C# String/PWideChar by Delphi DLL (x64)
前面,这是我的第一个post/question,所以请温柔点
我遇到 Delphi (10.3) dll 的问题,它应该填充一个字符串,或者更准确地说是一个 PWideChar。
奇怪的是,它在编译 32 位时有效,但在 64 位时无效?
如标题中所述,dll 函数旨在由 c# 应用程序调用,但由于 Delphi 调试器问题,我正在使用 VCL 应用程序测试该函数。
所以这是我的 DLL 代码:
function GetName(var id: integer; var name : PWideChar) : integer; stdcall;
var
tempstr : string;
test : PwideChar;
begin
tempstr := GetNameByID(id) // this just writes the name to a local variable
name := PWideChar(tempstr); //this throws a Access violation in System.Move
result := 0;
end;
调用应用代码:
function Getname(var id: integer; var name : PWideChar) : integer; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName : string;
temp : PWideChar;
tempint : Integer;
Begin
returnedName:= 'here is som content which is definitly longer then the name to return';
temp := PWideChar(returnedName);
tempint := 0;
GetName(tempint, temp);
end;
所以这在 32 位中工作正常,但在 64 位中,DLL 中的名称分配会在分析 NativeInt 的函数 System.Move 中引发访问冲突。
到目前为止,我尝试了不同的变体,例如
StrLCopy
StringToWideChar
或将字符串移动到局部变量。看起来,DLL 无法访问主机应用程序正在创建的 WideChar 数组,但我不明白为什么这取决于编译架构?
我显然需要使用 PWideChar,因为名称应该写入 C# 字符串,由 ref 或 out 传递.
有人知道为什么会这样吗?
编辑:@Remy Lebau:你绝对正确,这段代码甚至无法编译,因为我将 0 作为 Var 传递,我只是这样写下来,因为我在分解我的代码时很懒惰代码到要点。我相应地调整了代码
您的 DLL 函数通过 var
引用获取参数,这意味着它可以修改调用者的变量(这对于 id
参数来说不是必需的,因为该函数不会更改其值).
该函数仅仅是 re-assigning 它的 name
参数(因此改变了调用者的 PWideChar
变量)指向不同的内存地址。它实际上并没有尝试将任何内容写入所指向的内存中,因此您不可能从 Move()
中得到任何错误,因为它不应该被调用。
如果 Move()
确实被调用,那么您在此处显示的代码并不是您在实际项目中使用的实际代码。事实上,您在此处显示的代码甚至不应该编译,因为您不能像您所做的那样将 0
文字传递给 var
参数。
话虽这么说,即使代码已编译并且 运行 没有崩溃,请注意您显示的 DLL 函数正在设置调用者的 PWideChar
变量以指向在以下情况下释放的内存函数退出。如果调用者之后试图从该内存中读取任何内容,这将导致问题。
听起来您希望调用者分配 DLL 函数填充数据的内存。如果是这样,那么您显示的代码就完全错误了。试试这样的东西:
function GetName(id: integer; name: PWideChar; namesize: integer): integer; stdcall;
var
tempstr: UnicodeString;
size: Integer;
begin
tempstr := GetNameByID(id);
size := Length(tempstr) + 1;
if (name <> nil) and (namesize >= size) then
begin
Move(PWideChar(tempstr)^, name^, size * SizeOf(WideChar));
Dec(size);
end;
Result := size;
end;
然后您的 VCL 应用程序可以执行此操作:
function Getname(id: integer; name: PWideChar; namesize: integer): integer; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName: string;
begin
SetLength(returnedName, 100);
if GetName(0, PWideChar(returnedName), Length(returnedName) + 1) <= Length(returnedName) then
begin
// use returnedName as needed...
end;
end;
或者:
function Getname(id: integer; name: PWideChar; namesize: integer): integer; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName: string;
size: Integer;
begin
size := GetName(0, nil, 0);
SetLength(returnedName, size - 1);
GetName(0, PWideChar(returnedName), size);
// use returnedName as needed...
end;
然后您的 C# 应用程序可以执行此操作:
[DllImport("<dllname>.dll", CharSet = CharSet.Unicode)]
public static extern int GetName(int id, StringBuilder name, int namesize);
void TryGetName()
{
StringBuilder buf = new StringBuilder(100);
int size = GetName(0, buf, buf.Capacity + 1);
if (size <= buf.Capacity)
{
string returnedName = buf.ToString(0, size);
// use returnedName as needed...
}
}
或者:
[DllImport("<dllname>.dll", CharSet = CharSet.Unicode)]
public static extern int GetName(int id, StringBuilder name, int namesize);
void TryGetName()
{
int size = GetName(0, nil, 0);
StringBuilder buf = new StringBuilder(size - 1);
size = GetName(0, buf, size);
string returnedName = buf.ToString(0, size);
// use returnedName as needed...
}
也就是说,您可以让 DLL 为调用者分配一个新字符串 return,而不是使用输出参数,例如:
function GetName(id: integer): PWideChar; stdcall;
var
tempstr : UnicodeString;
size: Integer;
begin
tempstr := GetNameByID(id);
size := (Length(tempstr) + 1) * SizeOf(WideChar);
Result := PWideChar(CoTaskMemAlloc(size));
if Result <> nil then
Move(PWideChar(tempstr)^, Result^, size);
end;
然后您的 VCL 应用程序可以执行此操作:
function Getname(id: integer): PWideChar; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName: PWideChar;
begin
returnedName := GetName(0);
if returnedName <> nil then
try
// use returnedName as needed...
finally
CoTaskMemFree(returnedName);
end;
end;
您的 C# 应用程序可以做到这一点:
[DllImport("<dllname>.dll", CharSet = CharSet.Unicode)]
public static extern string GetName(int id);
void TryGetName()
{
string returnedName = GetName(0);
// use returnedName as needed...
}
前面,这是我的第一个post/question,所以请温柔点
我遇到 Delphi (10.3) dll 的问题,它应该填充一个字符串,或者更准确地说是一个 PWideChar。
奇怪的是,它在编译 32 位时有效,但在 64 位时无效?
如标题中所述,dll 函数旨在由 c# 应用程序调用,但由于 Delphi 调试器问题,我正在使用 VCL 应用程序测试该函数。
所以这是我的 DLL 代码:
function GetName(var id: integer; var name : PWideChar) : integer; stdcall;
var
tempstr : string;
test : PwideChar;
begin
tempstr := GetNameByID(id) // this just writes the name to a local variable
name := PWideChar(tempstr); //this throws a Access violation in System.Move
result := 0;
end;
调用应用代码:
function Getname(var id: integer; var name : PWideChar) : integer; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName : string;
temp : PWideChar;
tempint : Integer;
Begin
returnedName:= 'here is som content which is definitly longer then the name to return';
temp := PWideChar(returnedName);
tempint := 0;
GetName(tempint, temp);
end;
所以这在 32 位中工作正常,但在 64 位中,DLL 中的名称分配会在分析 NativeInt 的函数 System.Move 中引发访问冲突。
到目前为止,我尝试了不同的变体,例如
StrLCopy
StringToWideChar
或将字符串移动到局部变量。看起来,DLL 无法访问主机应用程序正在创建的 WideChar 数组,但我不明白为什么这取决于编译架构?
我显然需要使用 PWideChar,因为名称应该写入 C# 字符串,由 ref 或 out 传递.
有人知道为什么会这样吗?
编辑:@Remy Lebau:你绝对正确,这段代码甚至无法编译,因为我将 0 作为 Var 传递,我只是这样写下来,因为我在分解我的代码时很懒惰代码到要点。我相应地调整了代码
您的 DLL 函数通过 var
引用获取参数,这意味着它可以修改调用者的变量(这对于 id
参数来说不是必需的,因为该函数不会更改其值).
该函数仅仅是 re-assigning 它的 name
参数(因此改变了调用者的 PWideChar
变量)指向不同的内存地址。它实际上并没有尝试将任何内容写入所指向的内存中,因此您不可能从 Move()
中得到任何错误,因为它不应该被调用。
如果 Move()
确实被调用,那么您在此处显示的代码并不是您在实际项目中使用的实际代码。事实上,您在此处显示的代码甚至不应该编译,因为您不能像您所做的那样将 0
文字传递给 var
参数。
话虽这么说,即使代码已编译并且 运行 没有崩溃,请注意您显示的 DLL 函数正在设置调用者的 PWideChar
变量以指向在以下情况下释放的内存函数退出。如果调用者之后试图从该内存中读取任何内容,这将导致问题。
听起来您希望调用者分配 DLL 函数填充数据的内存。如果是这样,那么您显示的代码就完全错误了。试试这样的东西:
function GetName(id: integer; name: PWideChar; namesize: integer): integer; stdcall;
var
tempstr: UnicodeString;
size: Integer;
begin
tempstr := GetNameByID(id);
size := Length(tempstr) + 1;
if (name <> nil) and (namesize >= size) then
begin
Move(PWideChar(tempstr)^, name^, size * SizeOf(WideChar));
Dec(size);
end;
Result := size;
end;
然后您的 VCL 应用程序可以执行此操作:
function Getname(id: integer; name: PWideChar; namesize: integer): integer; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName: string;
begin
SetLength(returnedName, 100);
if GetName(0, PWideChar(returnedName), Length(returnedName) + 1) <= Length(returnedName) then
begin
// use returnedName as needed...
end;
end;
或者:
function Getname(id: integer; name: PWideChar; namesize: integer): integer; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName: string;
size: Integer;
begin
size := GetName(0, nil, 0);
SetLength(returnedName, size - 1);
GetName(0, PWideChar(returnedName), size);
// use returnedName as needed...
end;
然后您的 C# 应用程序可以执行此操作:
[DllImport("<dllname>.dll", CharSet = CharSet.Unicode)]
public static extern int GetName(int id, StringBuilder name, int namesize);
void TryGetName()
{
StringBuilder buf = new StringBuilder(100);
int size = GetName(0, buf, buf.Capacity + 1);
if (size <= buf.Capacity)
{
string returnedName = buf.ToString(0, size);
// use returnedName as needed...
}
}
或者:
[DllImport("<dllname>.dll", CharSet = CharSet.Unicode)]
public static extern int GetName(int id, StringBuilder name, int namesize);
void TryGetName()
{
int size = GetName(0, nil, 0);
StringBuilder buf = new StringBuilder(size - 1);
size = GetName(0, buf, size);
string returnedName = buf.ToString(0, size);
// use returnedName as needed...
}
也就是说,您可以让 DLL 为调用者分配一个新字符串 return,而不是使用输出参数,例如:
function GetName(id: integer): PWideChar; stdcall;
var
tempstr : UnicodeString;
size: Integer;
begin
tempstr := GetNameByID(id);
size := (Length(tempstr) + 1) * SizeOf(WideChar);
Result := PWideChar(CoTaskMemAlloc(size));
if Result <> nil then
Move(PWideChar(tempstr)^, Result^, size);
end;
然后您的 VCL 应用程序可以执行此操作:
function Getname(id: integer): PWideChar; stdcall; external '<dllname>.dll';
procedure TryGetName;
var
returnedName: PWideChar;
begin
returnedName := GetName(0);
if returnedName <> nil then
try
// use returnedName as needed...
finally
CoTaskMemFree(returnedName);
end;
end;
您的 C# 应用程序可以做到这一点:
[DllImport("<dllname>.dll", CharSet = CharSet.Unicode)]
public static extern string GetName(int id);
void TryGetName()
{
string returnedName = GetName(0);
// use returnedName as needed...
}