Home > Enterprise >  How to receive string result from Delphi DLL in C Builder?
How to receive string result from Delphi DLL in C Builder?

Time:10-25

Calling a function from a DLL returning a string gives a memory error. What am I doing wrong?

Note: Following codes show me the first character of the returned string and then gives a memory error.

I am using Delphi 11.2 and C Builder 5.

void __fastcall TForm1::Button1Click(TObject *Sender)
{
    HINSTANCE MyDll;

    typedef AnsiString(__stdcall *pfRes)(int);
    pfRes TestFunction;

    if ((MyDll = LoadLibrary("D:\\MyFirstLibrary.dll")) == NULL){
        ShowMessage("Cannot Load DLL !");
        return;
    }

    if ((TestFunction = (pfRes)GetProcAddress(MyDll,"TestFunction")) == NULL ) {
       ShowMessage("Cannot find DLL function!");
       return;
    }

    ShowMessage(TestFunction(24));
    FreeLibrary(MyDll);
}

DLL Code: (Delphi)(It works fine if call the dll from a delphi application)

uses
  System.SysUtils,
  System.Classes,
  System.JSON;

{$R *.res}

function TestFunction(const _a: integer) : String; stdcall;
var
  StrJSon      : string;
begin
  StrJSon := 'TEST STRING'

  Result := StrJSon;
end;

exports
  TestFunction;
begin

end.

CodePudding user response:

Delphi's String type is an alias for AnsiString in Delphi 2007 and earlier, but is an alias for UnicodeString in Delphi 2009 and later. Since you are using Delphi 11.2, string is going to alias UnicodeString, not AnsiString. So, you have a type mismatch between your C and Delphi codes.

However, neither type is safe to pass over a DLL boundary, unless you compile both the DLL and EXE projects with Runtime Packages enabled. In which case, you should also change the DLL project to be a Package instead, and then have the EXE load it using LoadPackage() instead of LoadLibrary().

Unfortunately, that approach is not going to work in your situation, since you are using C Builder 5 instead of C Builder 11.2, to match your Delphi 11.2. Runtime Packages simply do not work across major version boundaries.

In which case, you will have to redesign your code to pass around a raw char* or wchar_t* pointer instead. Either:

  • make the DLL dynamically allocate a C-style null-terminated string and return its pointer to the EXE, and then have the EXE pass that pointer back into the DLL to free the memory, eg:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    HINSTANCE MyDll;

    typedef char* (__stdcall *pfRes)(int);
    typedef void (__stdcall *pfFree)(char*);

    pfRes TestFunction;
    pfFree FreeFunction;

    if ((MyDll = LoadLibrary("D:\\MyFirstLibrary.dll")) == NULL){
        ShowMessage("Cannot Load DLL !");
        return;
    }

    TestFunction = (pfRes) GetProcAddress(MyDll, "TestFunction");
    FreeFunction = (pfFree) GetProcAddress(MyDll,"FreeFunction");

    if (TestFunction == NULL || FreeFunction == NULL) {
       ShowMessage("Cannot find DLL function!");
       FreeLibrary(MyDll);
       return;
    }

    char *ptr = TestFunction(24);
    ShowMessage(ptr);
    FreeFunction(ptr);

    FreeLibrary(MyDll);
}
uses
  System.SysUtils,
  System.Classes,
  System.JSON;

{$R *.res}

function TestFunction(const _a: integer) : PAnsiChar; stdcall;
var
  StrJSon : string;
begin
  StrJSon := 'TEST STRING'
  Result := StrNew(PAnsiChar(UTF8String(StrJSon)));
end;

procedure FreeFunction(_a: PAnsiChar); stdcall;
begin
  StrDispose(_a);
end;

exports
  TestFunction,
  FreeFunction;

begin
end.
  • have the EXE pre-allocate a char[] or wchar_t[] buffer of sufficient size and pass a pointer to it into the DLL, which can then simply fill the buffer as needed, eg:
void __fastcall TForm1::Button1Click(TObject *Sender)
{
    HINSTANCE MyDll;

    typedef int (__stdcall *pfRes)(int, char*, int);
    pfRes TestFunction;

    if ((MyDll = LoadLibrary("D:\\MyFirstLibrary.dll")) == NULL){
        ShowMessage("Cannot Load DLL !");
        return;
    }

    TestFunction = (pfRes) GetProcAddress(MyDll, "TestFunction");
    if (TestFunction == NULL) {
       ShowMessage("Cannot find DLL function!");
       FreeLibrary(MyDll);
       return;
    }

    char buf[256];
    if (TestFunction(24, buf, sizeof(buf)) > 0)
        ShowMessage(buf);

    /* or:
    int len = TestFunction(24, NULL, 0);
    if (len > 0)
    {
        char *buf = new char[len];
        if (TestFunction(24, buf, len) > 0)
            ShowMessage(buf);
        delete[] buf;
    }
    */

    FreeLibrary(MyDll);
}
uses
  System.SysUtils,
  System.Classes,
  System.JSON;

{$R *.res}

function TestFunction(const _a: integer; _buf: PAnsiChar; _size: Integer) : Integer; stdcall;
var
  StrJSon : string;
  StrUtf8 : UTF8String;
  iSize : Integer;
begin
  StrJSon := 'TEST STRING'
  StrUtf8 := UTF8String(StrJSon);
  iSize := Length(StrUtf8)   1;
  if _buf = nil then
    Result := iSize
  else if _size < iSize then
    Result := -1
  else begin
    Move(PAnsiChar(StrUtf8)^, _buf^, iSize);
    Result := iSize - 1;
  end;
end;

exports
  TestFunction;

begin
end.
  • Related