用 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# 字符串,由 refout 传递.

有人知道为什么会这样吗?

编辑:@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...
}