2
votes

How i can pass pchar from the DLL? dll must be compatible with other apps, not just delphi. In help is written that it is dangerous to pass pointers to local variables, if we make this variable global, the code will not be thread-safe.

We can safely pass a wide string,but in this case, the dll will not be compatible with other (non-Delphi) apps.

{code in dll}
       function Test:pchar;
        var
          str:string;
        begin
           str:='Some string';
           result:=pchar(str); // wrong way, may be UB. 
        end;


        {code in dll}

        var
          str:string // global variable

        function Test:pchar;
        begin
          str:='Some string';
          result:=pchar(str); // code not threadsafe
        end;

        {code in dll}
        function Test:WideString;
        var
          str:WideString;
        begin
           str:='Some string';
           result:=str; // will works ONLY with Delphi apps :( 
        end;

:(

how do experienced programmers?



var
  Form1: TForm1;
  function Test(out p:pchar):Integer;stdcall; external 'project2';
  procedure FinalizeStr(P:Pointer);stdcall; external 'project2';

implementation

{$R *.dfm}

function StrFromDll:string;
var
  p:pchar;
begin
  try
    setstring(result,P, test(p));
  finally
    finalizestr(cardinal(p));
  end;
end;

procedure TForm1.Button1Click(Sender: TObject);
begin
  showmessage(strfromdll);
end;


{code in dll}


library Project2;

uses
  fastmm4,fastmm4messages,
  SysUtils,
  Classes;

{$R *.res}

function Test(out p:pchar):Integer;stdcall;
var
  str:string;
  len:integer;
begin
  str:='Hello, i am a string from the dll?!#@%!?'+inttostr(hinstance);  // only for example
  len:=length(str);
  p:=allocmem(len+1);
  StrPLCopy(P,str,len);
  result:=len;
end;

procedure FinalizeStr(P:Pointer);stdcall;
begin
  FreeMem(P);
end;

exports Test,FinalizeStr;
end.
1

1 Answers

5
votes

You have three main options.

Have the caller allocate memory that the callee populates

function GetString(Buffer: PWideChar; BufferLen: Integer): Integer; stdcall;

The caller has to know how much memory to allocate. You can do this by arranging the the function returns the number of characters copied, or, if Buffer is nil, the size of buffer required. So the caller might call this function like this:

var
  str: string;
....
SetLength(str, GetString(nil, 0) - 1);
GetString(PChar(str), Length(str) + 1);

The -1 and +1 are to deal with the null-terminator.

Have the callee allocate memory, and export a deallocator

That looks like this:

function GetString: PWideChar; stdcall;
function Free(P: Pointer); stdcall;

The callee allocates the memory off its internal heap. But the callee also has to export a function that frees the memory. The call sequence looks like this:

var
  P: PWideChar;    
  str: string;
....
P := GetString();
str := P;
Free(P);

A twist on this is to allocate off a shared heap, for instance the COM heap. That way you don't need to export a deallocator since the caller can obtain the COM heap deallocator without your help.

Return a COM string

The COM BSTR is allocated off the shared COM heap, and any Windows development environment can work with these objects. Delphi wraps these as WideString. A twist though is that the Delphi ABI differs from other tools and you cannot use WideString as a function return value and interop with other tools. Instead you should use an out parameter.

procedure GetString(out str: WideString); stdcall;

More details here: Why can a WideString not be used as a function return value for interop?