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[]
orwchar_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.