在Freepascal编译的DLL和Delphi编译的EXE之间交换字符串(PChar)

时间:2010-04-14 15:21:13

标签: delphi string dll pascal freepascal

经过大量的实验,我找到了一种方法,用FreePascal编译的DLL与Delphi编译的EXE交换PChar。我负责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方法。

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

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

3 个答案:

答案 0 :(得分:7)

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

为此,您的调用应用程序会创建一个字符串缓冲区,并向您的DLL函数发送一个引用该缓冲区的PChar以及缓冲区大小。如果缓冲区大小小于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。

答案 1 :(得分:2)

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

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

procedure GetAString(var Result: WideString); stdcall;

Delphi和FreePascal将自动处理分配和释放。 WideString在支持Unicode的Delphis中与Unicode之前的版本相同,因此您也不需要为此更改内容。

答案 2 :(得分: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似乎以不同的方式返回字符串类型的结果。