4

经过大量实验,我找到了一种方法,可以将 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 ?

4

3 回答 3

7

在不使用 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。

于 2010-04-14T15:52:36.170 回答
2

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

您应该能够使用这样的函数声明:

procedure GetAString(var Result: WideString); stdcall;

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

于 2010-04-14T16:31:45.160 回答
2

在 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 似乎以不同的方式返回字符串类型的结果。

于 2010-04-14T16:40:16.120 回答