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

发布于 2024-08-29 08:54:29 字数 699 浏览 8 评论 0原文

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

After a lot of experimentations, I found a way to exchange PChar from a FreePascal compiled DLL with a Delphi compiled EXE. I'm in charge of both the DLL and EXE source code but one MUST BE in FreePascal and the other one in Delphi. My solution involves the following methods in the 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;

And from the Delphi EXE, to call the GetAString method, I need to Call the GetAString method, save the PChar to an actual Delphi String and call the FreeString method.

Is this the best way of exchanging a string from a FreePascal DLL with a Delphi EXE ? Can I avoid the call to FreeString from Delphi ?

And finally, if that's the correct solution, how will it behave with Delphi 2010 and the WideString by default: do I need to force WidePChar in FreePascal too ?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

北方的巷 2024-09-05 08:54:29

在 DLL 和 Delphi 应用程序之间交换字符串而不使用 FreeString 调用的一种方法是从调用应用程序中获取字符串缓冲区作为 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 中正常工作。您应该显式使用 PAnsiCharPWideChar。您可以再次遵循 Windows API 函数的模式:它们几乎为每个 API 函数提供两个版本 - 一个支持 WideChar,其名称以 W 字符作为后缀,另一个版本支持 AnsiChar,其名称以 A 字符作为后缀。

您的 DLL 函数声明将如下所示:

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

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

编辑:

这是示例代码:

  1. WideChar 的 DLL 函数将如下所示:

    函数 AStringFuncW(Buffer: PWideChar; var BufferSize: Integer): Boolean;标准调用;
    变量
      MyOutputStr:宽字符串;
    开始
      结果:=假;
    
      // 在这里计算你的输出字符串。
      MyOutputStr := '这是一个示例输出';
    
      // 检查缓冲区是否已分配,以及其给定的长度是否足够
      如果分配(缓冲区)和(缓冲区大小>=长度(MyOutputStr)+ 1)然后
      开始
        // 将输出字符串复制到缓冲区
        StrPCopy(缓冲区,MyOutputStr);
        结果:=真;
      结尾;
      // 返回输出字符串的实际大小。
      BufferSize := 长度(MyOutputStr) + 1;
    结尾;
    

    对于 AnsiChar 版本,您使用 PAnsiChar 作为参数,并使用几乎相似的代码以 AnsiString 作为变量,或者转换参数到 PWideChar 并在 AStringFuncA 函数中调用您自己的 AStringFuncW,其结果将转换回 PAnsiChar

  2. 以下是如何在接口单元中定义这些函数以供 DLL 客户端使用:

    单元 TestDLLIntf;
    
    界面
    
    常量
      TestDll = 'Test.dll';
    
      函数 AStringFuncW(Buffer: PWideChar; var BufferSize: Integer): Boolean;标准调用;
      函数 AStringFuncA(Buffer: PWideChar; var BufferSize: Integer): Boolean;标准调用;
      函数 AStringFunc(Buffer: PWideChar; var BufferSize: Integer): Boolean;标准调用;
    
    执行
    
    函数 AStringFuncW;外部 TestDll 名称“AStringFuncW”;
    函数AStringFuncA;外部 TestDll 名称“AStringFuncA”;
    {$IFDEF UNICODE}
     函数 AStringFunc;外部 TestDll 名称“AStringFuncW”;
    {$其他}
     函数 AStringFunc;外部 TestDll 名称“AStringFuncA”;
    {$ENDIF}
    
    结尾。
    

    在上面的代码中,AStringFuncWAStringFuncA 函数都被声明为外部函数。 AStringFunc 函数在 Delphi 2009 或 2010 中引用 WideChar 版本,在旧版本中引用 AnsiChar

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

    过程 TForm1.Button1Click(Sender: TObject);
    变量
      字符串:字符串;
      大小:整数;
    开始
      // 获取所需的缓冲区大小
      AStringFunc(nil,大小);
      // 设置缓冲区
      SetLength(Str,大小);
      // 从 DLL 函数中检索输出字符串。
      如果 AStringFunc(PChar(Str),Size) 那么
        显示消息(Str);
    结尾;
    

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

One way to exchange strings between your DLL and your Delphi application without using a FreeString call is to take a string buffer from the calling application as a PChar, and fill the buffer in the DLL. That is how Windows API functions work when they need to exchange strings with calling applications.

To do so, your calling application creates a string buffer, and sends a PChar referring to that buffer along with buffer size to your DLL function. If the buffer size is smaller than the actual string the DLL has to send to the application, your DLL function can send the actual required size for the buffer to the calling application.

how will it behave with Delphi 2010 and the WideString by default: do I need to force WidePChar in FreePascal too ?

In Delphi 2009 and Delphi 2010, PChar equals to PWideChar. In previous versions of Delphi, and as far as I know, in FreePascal, PChar equals to PAnsiChar. So If you return PChar from your DLL, your code won't work correctly in Delphi 2010. You should explicitly use PAnsiChar or PWideChar. You can again follow Windows API function's pattern: they provide two versions of almost each API function - one with WideChar support whose names have a W character as suffix, and the other one with AnsiChar support whose names have an A character as suffix.

Your DLL function declaration would be something like this:

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

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

EDIT:

Here is a sample code:

  1. Your DLL function for WideChar would be like this:

    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;
    

    For the AnsiChar version you use PAnsiChar as parameter and either an almost similar code with either AnsiString as variable, or you convert the parameter to PWideChar and call your own AStringFuncW inside your AStringFuncA function, whose result you convert back to PAnsiChar.

  2. Here is how you can define these functions in your interface unit to be used by your DLL clients:

    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.
    

    In the above code, both AStringFuncW and AStringFuncA functions are declared as external. The AStringFunc function refers to the WideChar version in Delphi 2009 or 2010, and to AnsiChar in older versions.

  3. Here you can see how your DLL clients can use your function:

    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;
    

    In the above code, the client application first gets the actual output size from AStringFunc, then sets a string buffer, and retrieves the output string from the DLL. Note that the same code should work in both Unicode and non-Unicode versions of Delphi, because AStringFunc refers to either AStringFuncA or AStringFuncW inside your DLL, depending on whether your compiler supports Unicode or not.

风吹雨成花 2024-09-05 08:54:29

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

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

procedure GetAString(var Result: WideString); stdcall;

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

Depending on what data you're passing, you may be able to use WideStrings instead. They're allocated on the Windows heap, so if you allocate one in the DLL and free it in the EXE they'll both go through the same memory manager.

You should be able to use a function declaration like this:

procedure GetAString(var Result: WideString); stdcall;

And Delphi and FreePascal will both handle the allocation and freeing automatically. WideString is the same in the Unicode-enabled Delphis as the pre-Unicode ones, so you won't need to change things for that either.

叫嚣ゝ 2024-09-05 08:54:29

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

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


更新:

我已经测试了以下代码:

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

The rule of thumb for allocating/deallocating memory in EXE and DLL is: the module that allocates memory is responsible for deallocation the same memory. In your example it is DLL who allocates and deallocates memory, so it is correct.

There is one exception from the general rule. Both Delphi and the latest Free Pascal versions implement WideStrings as BSTR - a string data type that is used by COM. WideStrings are allocated and freed by Windows, not by Delphi (or Free Pascal) memory manager. So there is no need to use PWideChar to pass WideString arguments between EXE and DLL - WideStrings can be passed directly.


Updated:

I have tested the following code:

Free Pascal (version 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 (main form):

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.

It appeared that the procedure GetAStringProc works fine while the function GetAStringFunc leads to access violation. Delphi and Free Pascal seems to return the string-type result differently.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文