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 ?
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:
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
.
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.
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.