Обмен строками (PChar) между скомпилированной DLL Freepascal и скомпилированным Delphi EXE - PullRequest
4 голосов
/ 14 апреля 2010

После долгих экспериментов я нашел способ обмена PChar из скомпилированной библиотеки FreePascal на скомпилированный 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 и вызвать метод FreeString.

Является ли это наилучшим способом обмена строкой из библиотеки FreePascal с помощью Delphi EXE? Можно ли избежать вызова FreeString из Delphi?

И, наконец, если это правильное решение, как оно будет работать с Delphi 2010 и WideString по умолчанию: нужно ли принудительно использовать WidePChar в FreePascal?

Ответы [ 3 ]

7 голосов
/ 14 апреля 2010

Один из способов обмена строками между вашей DLL и приложением Delphi без использования вызова FreeString - это взять строковый буфер из вызывающего приложения как PChar и заполнить буфер в DLL. Именно так работают функции API Windows, когда им нужно обмениваться строками с вызывающими приложениями.

Для этого вызывающее приложение создает строковый буфер и отправляет PChar со ссылкой на этот буфер вместе с размером буфера в вашу функцию DLL. Если размер буфера меньше, чем фактическая строковая DLL должна отправить в приложение, ваша функция DLL может отправить фактический необходимый размер буфера вызывающему приложению.

как он будет себя вести с Delphi 2010 и WideString по умолчанию: сделать нужно форсировать WidePChar во FreePascal тоже?

В Delphi 2009 и Delphi 2010 PChar соответствует PWideChar. В предыдущих версиях Delphi и, насколько мне известно, во FreePascal PChar равняется PAnsiChar. Поэтому, если вы вернете PChar из своей DLL, ваш код не будет работать правильно в 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;

EDIT:

Вот пример кода:

1- Ваша функция DLL для widechar будет выглядеть так:

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 и вызвать AStringFuncW внутри функции AStringFuncA, а затем преобразовать строку возврата из 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 ссылается на версию WideChar в Delphi 2009 - 2010 и ссылается на версию 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. Обратите внимание, что один и тот же код должен работать как в версиях Delphi для Unicode, так и для не Unicode, поскольку AStringFunc ссылается на AStringFuncA или AStringFuncW внутри вашей DLL в зависимости от того, поддерживает ли ваш компилятор Unicode или нет.

2 голосов
/ 14 апреля 2010

Практическое правило для выделения / освобождения памяти в EXE и DLL таково: модуль, который выделяет память, отвечает за освобождение той же памяти. В вашем примере это DLL, которая выделяет и освобождает память, так что это правильно.

Есть одно исключение из общего правила. Как Delphi, так и последние версии Free Pascal реализуют WideStrings как BSTR - строковый тип данных, используемый COM. WideStrings выделяются и освобождаются Windows, а не менеджером памяти Delphi (или Free Pascal). Поэтому нет необходимости использовать PWideChar для передачи аргументов WideString между EXE и DLL - можно напрямую передавать WideStrings.


Обновлен:

Я проверил следующий код:

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, похоже, по-разному возвращают результат строкового типа.

2 голосов
/ 14 апреля 2010

В зависимости от того, какие данные вы передаете, вы можете вместо этого использовать WideStrings. Они размещены в куче Windows, поэтому, если вы выделите один в DLL и освободите его в EXE, они оба будут проходить через один и тот же менеджер памяти.

Вы должны иметь возможность использовать объявление функции следующим образом:

procedure GetAString(var Result: WideString); stdcall;

А Delphi и FreePascal будут обрабатывать выделение и освобождение автоматически. WideString в Delphis с поддержкой Unicode такой же, как и до Unicode, поэтому вам не нужно ничего менять для этого.

...