通过Delphi DLLx64填充C#字符串/ PWideChar

问题描述

在前面,这是我的第一个帖子/问题,所以请保持温柔

我在使用Delphi(10.3)dll时遇到问题,该dll应该可以填充字符串,或更确切地说是PWideChar。

奇怪的是,它在编译32位而不是64位时有效吗?

标题中所述,dll函数是由c#应用程序调用的,但是由于Delphi Debugger问题,我正在使用VCL App测试该函数

这是我的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中的名称分配会在System.Move函数中引发访问冲突,在该函数中将分析NativeInt。

到目前为止,我尝试了类似的各种变体

StrLcopy
StringToWideChar

或将字符串移动到局部变量。似乎DLL无法访问Host应用程序正在创建的WideChar数组,但是我不明白为什么这取决于编译体系结构?

我显然需要使用PWideChar,因为该名称应写入C#字符串中,并由 ref out 传递。

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

编辑:@Remy Lebau:绝对正确,该代码甚至无法编译,因为我以Var形式传递了0,我之所以这样写下来,是因为我在将代码分解为基本内容时很懒。我相应地调整了代码

解决方法

您的DLL函数正在通过var引用获取参数,这意味着可以修改调用者的变量(id参数不需要此变量,因为该函数不会更改其值)。

该函数只是重新分配其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,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",int namesize);

void TryGetName()
{
    int size = GetName(0,0);
    StringBuilder buf = new StringBuilder(size - 1);
    size = GetName(0,size);
    string returnedName = buf.ToString(0,size);
    // use returnedName as needed...
}

话虽如此,您可以让DLL分配一个新字符串以返回给调用者,而不是使用输出参数,例如:

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...
}