2

I have written a DLL with Delphi and now I need to test a function of this DLL from C++ code.

I've never used C++ before. I installed Code::Blocks and tried to code in C++.

There are no problems with functions that return integers or booleans, but I have problems with functions that return BSTR type:

error: cannot convert 'GetLastErrText_funtype {aka wchar_t* (__attribute__((__stdcall__)) *)()}' to 'BSTR {aka wchar_t*}' in initialization

Delphi function header:

function GetLastErrText: BSTR; stdcall;

C++ code:

#include <iostream>
#include <cstdlib>
#include <windows.h>

using namespace std;

int main()
{
    HINSTANCE dll_module = LoadLibrary("test.dll");

    if(dll_module == NULL) { return -1; }

    FARPROC GetLastErrText = GetProcAddress(dll_module, "GetLastErrText");
    typedef BSTR (__stdcall* GetLastErrText_funtype)();

    std::cout << "\nGetLastErrText = " << (void*)GetLastErrText;

    if (GetLastErrText != 0) {
        BSTR bstr = (GetLastErrText_funtype) GetLastErrText();  // <<<< ERROR
        std::cout << "\nstr = " << bstr;
    }

    FreeLibrary(dll_module);

    std::cout << "\n";

    return 0;
}

How do I fix this code?


UPDATE (after reading comment of Remy Lebeau):

Very simplified code in Delphi

library test_ws;

uses
  System.SysUtils,
  Vcl.Dialogs;

function GetLastErrText: WideString; stdcall;
begin
  try
    Result := 'This is the result of GetLastErrText';
  except
    on E:Exception do
      ShowMessage('Delphi exception::GetLastErrText : ' + E.Message); // when call from C++ code : Out of memory
  end;
end;

exports
  GetLastErrText;

begin
end.

according code in C++:

#include <iostream>
#include <windows.h>

using namespace std;

int main()
{
    HMODULE lib = LoadLibrary("test_ws.dll");

    typedef BSTR (__stdcall *Func)();
    Func GetLastErrText = (Func) GetProcAddress(lib, "GetLastErrText");

    BSTR bstr = GetLastErrText(); // <<<--- Out of memory
    std::wcout << bstr;
    SysFreeString(bstr);

    return 0;
}
Zurab
  • 1,411
  • 3
  • 17
  • 39
  • This is a very common mistake of making functions return strings in interop. There are endless examples out there. You need to re-think how the function works. Think of memory management. – Jerry Dodge Jul 09 '19 at 15:01
  • You possibly want to read this article about DLLs: http://rvelthuis.de/articles/articles-dlls.html – Delphi Coder Jul 09 '19 at 15:38

1 Answers1

2

You are trying to call the DLL function first and then type-cast its return value to a function pointer. You cannot assign a function pointer to a BSTR pointer, that is what the compiler is complaining about.

You need to type-cast the function pointer before you can call the function properly. You are trying to do that at the function call, but to do it properly you need to do that like this instead:

BSTR bstr = ((GetLastErrText_funtype)GetLastErrText)(); // <-- note the extra parenthesis around the cast!

Otherwise, it would be better to cast the function pointer when you first obtain it, then you can call it like a normal function when needed:

typedef BSTR (__stdcall* GetLastErrText_funtype)();
GetLastErrText_funtype GetLastErrText = (GetLastErrText_funtype) GetProcAddress(dll_module, "GetLastErrText");
...
BSTR bstr = GetLastErrText();

Don't forget to free the BSTR when you are done using it (provided the DLL is allocating it properly using one of the SysAllocString...() functions):

BSTR bstr = GetLastErrText();
std::cout << "\nstr = "; // <-- std::cout does not support wchar_t data!
std::wcout << bstr;      // <-- use std::wcout instead...
SysFreeString(bstr);     // <-- free it!

UPDATE: your DLL's Delphi code is not actually returning a raw BSTR pointer, like your C++ code is expecting. It is actually returning a WideString, which is a managed type in Delphi, and is always returned from a function via a hidden output parameter, which your C++ code is not populating. That is why your C++ code fails. See Why can a WideString not be used as a function return value for interop?. The correct function signature on the C++ side would have to look more like this instead:

typedef void (__stdcall* GetLastErrText_funtype)(WideString&);

However, WideString is a wrapper for a BSTR, it is not itself an actual BSTR. You cannot use WideString as-is outside of Delphi and C++Builder compilers.

You need to rewrite the DLL function to make it more compatible with non-C++Builder compilers. For example:

function GetLastErrText: PWideChar; stdcall;
var
  RealResult: WideString absolute Result;
begin
  try
    Initialize(RealResult);
    RealResult := 'This is the result of GetLastErrText';
  except
    on E: Exception do
      ShowMessage('Delphi exception::GetLastErrText : ' + E.Message);
  end;
end; 

Alternatively:

function GetLastErrText: PWideChar; stdcall;
begin
  try
    Result := nil;
    WideString(Result) := 'This is the result of GetLastErrText';
  except
    on E: Exception do
      ShowMessage('Delphi exception::GetLastErrText : ' + E.Message);
  end;
end; 
Remy Lebeau
  • 555,201
  • 31
  • 458
  • 770
  • I added the Delphi code and the fixed C++ code. I still get an "Out of memory" error. And If you call ths DLL from Delphi program it will work fine. – Zurab Jul 10 '19 at 10:16