【问题标题】:Exchanging strings (PChar) between a Freepascal compiled DLL and a Delphi compiled EXE在 Freepascal 编译的 DLL 和 Delphi 编译的 EXE 之间交换字符串 (PChar)
【发布时间】:2010-04-14 15:21:13
【问题描述】:

经过大量实验,我找到了一种将 FreePascal 编译的 DLL 中的 PChar 与 Delphi 编译的 EXE 交换的方法。我负责 DLL 和 EXE 源代码,但一个必须在 FreePascal 中,另一个在 Delphi 中。我的解决方案涉及DLL中的以下方法:

function GetAString(): PChar;
var aString: string;
begin
  aString := 'My String';
  result := StrAlloc(length(aString) + 1);
  StrPCopy(result, aString); 
end;

procedure FreeString(aString: PChar);
begin
  StrDispose(aString); 
end;

从Delphi EXE中,要调用GetAString方法,我需要调用GetAString方法,将PChar保存到一个实际的Delphi String,然后调用FreeString方法。

这是从 FreePascal DLL 与 Delphi EXE 交换字符串的最佳方式吗?我可以避免从 Delphi 调用 FreeString 吗?

最后,如果这是正确的解决方案,默认情况下它在 Delphi 2010 和 WideString 中的表现如何:我是否也需要在 FreePascal 中强制 WidePChar ?

【问题讨论】:

  • 出于兴趣,为什么必须在 FreePascal 中?
  • 我需要使用一些内置库,这些库移植和维护到 Delphi 的成本可能很高。这样我只需要调用 DLL 中的几个函数。

标签: delphi string dll pascal freepascal


【解决方案1】:

在不使用 FreeString 调用的情况下在 DLL 和 Delphi 应用程序之间交换字符串的一种方法是从调用应用程序获取字符串缓冲区作为 PChar,并将缓冲区填充到 DLL 中。这就是 Windows API 函数在需要与调用应用程序交换字符串时的工作方式。

为此,您的调用应用程序创建一个字符串缓冲区,并将引用该缓冲区的 PChar 连同缓冲区大小一起发送到您的 DLL 函数。如果缓冲区大小小于 DLL 必须发送给应用程序的实际字符串,则您的 DLL 函数可以将缓冲区实际所需的大小发送给调用应用程序。

它在 Delphi 2010 中的表现如何 和默认情况下的 WideString:我 需要在 FreePascal 中强制 WidePChar 也是?

在 Delphi 2009 和 Delphi 2010 中,PChar 等于 PWideChar。在以前的 Delphi 版本中,据我所知,在 FreePascal 中,PChar 等于 PAnsiChar。因此,如果您从 DLL 中返回 PChar,您的代码将无法在 Delphi 2010 中正常工作。您应该明确使用 PAnsiChar 或 PWideChar。您可以再次关注 Windows API 函数。它们提供了许多 API 函数的两个版本,一个支持 WideChar,其名称以 W 字符作为后缀,另一个支持 ANSI,其名称以 A 字符作为后缀。

你的 DLL 函数声明应该是这样的:

function AStringFuncW(Buffer: PWideChar; var BufferSize: Integer): Boolean;

function AStringFuncA(Buffer: PAnsiChar; var BufferSize: Integer): Boolean;

编辑:

这是一个示例代码:

1- 您的 Widechar DLL 函数如下所示:

function AStringFuncW(Buffer: PWideChar; var BufferSize: Integer): Boolean; stdcall;
var
  MyOutputStr : WideString;
begin
  Result := False;

  // Calculate your output string here.
  MyOutputStr := 'This is a sample output';

  // Check if buffer is assigned, and its given length is enough
  if Assigned(Buffer) and (BufferSize >= Length(MyOutputStr) + 1) then
  begin
    //Copy output string into buffer
    StrPCopy(Buffer,MyOutputStr);
    Result := True;
  end;
  //Return actual size of output string.
  BufferSize := Length(MyOutputStr) + 1;
end;

对于 AnsiChar 版本,您可以使用与 AnsiString 和 PAnsiChar 相同的代码,或者将 ANSI 字符串参数转换为 Unicode,并在 AStringFuncA 函数中调用 AStringFuncW,然后将返回字符串从 AStringFuncW 转换为 PAnsiChar。

2- 您可以在接口单元中定义这些函数以供您的 DLL 客户端使用:

unit TestDLLIntf;

interface
const
  TestDll = 'Test.dll';

  function AStringFuncW(Buffer: PWideChar; var BufferSize: Integer): Boolean; stdcall;
  function AStringFuncA(Buffer: PWideChar; var BufferSize: Integer): Boolean; stdcall;
  function AStringFunc(Buffer: PWideChar; var BufferSize: Integer): Boolean; stdcall;

implementation

function AStringFuncW; external TestDll name 'AStringFuncW';
function AStringFuncA; external TestDll name 'AStringFuncA';
{$IFDEF UNICODE}
 function AStringFunc; external TestDll name 'AStringFuncW';
{$ELSE}
 function AStringFunc; external TestDll name 'AStringFuncA';
{$ENDIF}

end.

在上面的代码中,AStringFuncW 和 AStringFuncA 函数都被声明为外部函数。 AStringFunc函数在Delphi 2009-2010中指的是WideChar版本,其他版本指的是AnsiChar版本。

3- 在这里您可以看到您的 DLL 客户端如何使用您的函数:

procedure TForm1.Button1Click(Sender: TObject);
var
  Str : string;
  Size : Integer;
begin
  // Retrieve required buffer size
  AStringFunc(nil,Size);
  // Set buffer
  SetLength(Str,Size);
  // Retrieve output string from DLL function.
  if AStringFunc(PChar(Str),Size) then
    ShowMessage(Str);
end;

在上面的代码中,客户端应用程序首先从 AStringFunc 获取实际输出大小,然后设置一个字符串缓冲区,并从 DLL 中检索输出字符串。请注意,相同的代码应该适用于 Unicode 和非 Unicode 版本的 Delphi,因为 AStringFunc 在您的 DLL 中引用 AStringFuncA 或 AStringFuncW,具体取决于您的编译器是否支持 Unicode。

【讨论】:

  • 非常感谢您非常详细的回答。尽管它看起来比我的第一个版本更复杂,但我可能会使用这种方法,因为这是 Windows API 中的标准。
  • AStringFuncW() 函数中存在错误。缓冲区大小错过了 +1。最后一行“ BufferSize := Length(MyOutputStr);”应该是:“BufferSize := Length(MyOutputStr) + 1;”
  • 是的 Jan,你是对的,BufferSize 错过了字符串中的最后一个 #0。我编辑了代码。谢谢。
【解决方案2】:

根据您传递的数据,您可以改用 WideStrings。它们是在 Windows 堆上分配的,所以如果你在 DLL 中分配一个并在 EXE 中释放它,它们都将通过同一个内存管理器。

你应该可以使用这样的函数声明:

procedure GetAString(var Result: WideString); stdcall;

Delphi 和 FreePascal 都会自动处理分配和释放。 WideString 在支持 Unicode 的 Delphi 中与之前的 Unicode 相同,因此您也无需为此进行更改。

【讨论】:

  • 谢谢克雷格。正如我向 Serg 提到的,我在 DLL 中使用 WideString 有 AV,这就是我采用 PChar 方式的原因。
【解决方案3】:

在 EXE 和 DLL 中分配/释放内存的经验法则是:分配内存的模块负责释放相同的内存。在您的示例中,分配和释放内存的是 DLL,所以它是正确的。

一般规则有一个例外。 Delphi 和最新的 Free Pascal 版本都将 WideStrings 实现为 BSTR - COM 使用的字符串数据类型。 WideStrings 由 Windows 分配和释放,而不是由 Delphi(或 Free Pascal)内存管理器。所以不需要使用 PWideChar 在 EXE 和 DLL 之间传递 WideString 参数 - WideStrings 可以直接传递。


更新:

我已经测试了以下代码:

免费 Pascal(2.2.4 版)DLL:

library TestLib1;

{$mode objfpc}{$H+}

{$IFDEF WINDOWS}{$R TestLib1.rc}{$ENDIF}

procedure GetAStringProc(var S: WideString); stdcall;
begin
  S:= '12345';
end;

function GetAStringFunc: WideString; stdcall;
begin
  Result:= '12345';
end;

exports
  GetAStringProc, GetAStringFunc;

begin
end.

Delphi 2009 EXE(主格式):

unit Unit1;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls;

type
  TForm1 = class(TForm)
    Button1: TButton;
    Button2: TButton;
    procedure FormCreate(Sender: TObject);
    procedure Button1Click(Sender: TObject);
    procedure Button2Click(Sender: TObject);
  private
    S: WideString;
  end;

var
  Form1: TForm1;

type
  TGetAStringProc = procedure(var S: WideString); stdcall;
  TGetAStringFunc = function: WideString; stdcall;

var
  GetAStringProc: TGetAStringProc;
  GetAStringFunc: TGetAStringFunc;

implementation

{$R *.dfm}

procedure TForm1.Button1Click(Sender: TObject);
begin
    GetAStringProc(S);
    Caption:= S;
    S:= '';
end;

procedure TForm1.Button2Click(Sender: TObject);
begin
    S:= GetAStringFunc;
    Caption:= S;
    S:= '';
end;

procedure TForm1.FormCreate(Sender: TObject);
var
  LibHandle: THandle;

begin
  LibHandle:= LoadLibrary('TestLib1.dll');
  if LibHandle <> 0 then begin
    @GetAStringProc:= GetProcAddress(LibHandle, 'GetAStringProc');
    @GetAStringFunc:= GetProcAddress(LibHandle, 'GetAStringFunc');
  end;
  if not Assigned(GetAStringProc) or not Assigned(GetAStringFunc) then
      ShowMessage('Error!');
end;

end.

似乎 GetAStringProc 过程工作正常,而函数 GetAStringFunc 导致访问冲突。 Delphi 和 Free Pascal 似乎返回的字符串类型结果不同。

【讨论】:

  • 如果 FreePascal 和 Delphi 以相同的方式处理 WideString 返回值,这将起作用。显然不能保证其他语言:groups.google.com/group/…
  • 谢谢军士。不幸的是,我一直使用这种方法获得 AV。这就是为什么我尝试了我在原帖中提到的 PChar 方式。
  • @John Riche:你是对的,函数导致 AV - 你应该使用过程而不是函数。我已经更新了帖子。
猜你喜欢
  • 2014-06-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-04-08
  • 2014-11-28
  • 1970-01-01
相关资源
最近更新 更多