1

I have been trying to reuse a C dll file in delphi based on this documentation.

Server was running well, i could access and used the database on local server with java and php.

On delphi i used dynamic load and worked well on all functions that return variables but failed on those that return interface.

unit for library :
unit SQLDBC_C;

interface
uses windows, classes, sysutils;

type
  SQLDBC_IRuntime = interface
  end;

var
  getSDKVersion : function :Pchar; stdcall;
  ClientRuntime_GetClientRuntime: function (errorText:Pchar; errorTextSize:Integer) : SQLDBC_IRuntime; stdcall;

implementation

var
  libhandle : THandle;

procedure initLibrary;
begin
  libhandle := LoadLibrary('libSQLDBC_C.dll');
  if libhandle>=23 then begin
     @getSDKVersion:=GetProcAddress(libhandle,'getSDKVersion');
     @ClientRuntime_GetClientRuntime:=
        GetProcAddress(libhandle,'ClientRuntime_GetClientRuntime');
  end;
end;

initialization
begin
  initLibrary;
end;

finalization
begin
  if libhandle>=32 then
    FreeLibrary(libhandle);
end;

end.

here is the test procedure :

procedure TForm1.Button1Click(Sender: TObject);
var
  err : array [0..200] of char;
  rt : SQLDBC_IRuntime;

begin
  Memo1.Clear;
  FillChar(err, sizeof(err), 0);
  Memo1.Lines.Add(getSDKVersion); //this function successed

  rt := ClientRuntime_GetClientRuntime(@err,200); 
  //this function had no return value, (rt always nil) but no error return at err variable
  if assigned(rt) then begin
    ......
  end;
end;

I've read the similar problems asked by geskill, Dan Hacker, max and Ron but it could not solve my problem.

Could anyone told me what's wrong here?

Community
  • 1
  • 1
Theo
  • 454
  • 1
  • 7
  • 21
  • Which version of Delphi? Are you sure it's stdcall? Quite plausible that it's cdecl. – David Heffernan Sep 28 '12 at 23:09
  • This reminds me of this question: http://stackoverflow.com/questions/9349530/why-can-a-widestring-not-be-used-as-a-function-return-value-for-interop – David Heffernan Sep 28 '12 at 23:17
  • @DavidHeffernan thanks for reply. There's no differences between stdcall and cdecl. I'm on Windows XP machine, trying Delphi 6, 7 and XE2. – Theo Sep 28 '12 at 23:23
  • Make sure you use AnsiChar for XE2. Best to use it always for this lib. Try a C++ program and check if that works. If so, what differs? – David Heffernan Sep 28 '12 at 23:31
  • That's the problem. It works well on C++, java and php. On delphi 6, 7, and XE2, the library loaded sucessfully the functions that return String and numeric works well except all function that return Interface. If i use interface it returns nil. If i use pointer, it returns pointer but throws exceptions. – Theo Sep 28 '12 at 23:50
  • Try using cdecl instead of stdcall – chuckj Sep 29 '12 at 03:13
  • @chuckj Read the comments above. That was already suggested and tried. – David Heffernan Sep 29 '12 at 08:37

2 Answers2

3

I cannot test it because I have no libSQLDBC_C.dll.

The problem was already explained . As a workaround for your case you can return a pointer in Delphi ClientRuntime_GetClientRuntime declaration

ClientRuntime_GetClientRuntime: function (errorText:Pchar;
                                errorTextSize:Integer): Pointer; stdcall;

and cast it to SQLDBC_IRuntime interface:

var
  err : array [0..200] of char;
  rt : SQLDBC_IRuntime;

begin
  Pointer(rt):= ClientRuntime_GetClientRuntime(@err,200); 
Community
  • 1
  • 1
kludg
  • 27,213
  • 5
  • 67
  • 118
  • I've done some trials and agree with what you write here (see my update). Sadly however, the `Pointer` trick is of no use in this instance. – David Heffernan Sep 29 '12 at 10:38
  • Sure typecasting expects a COM interface (IUnknown descendant); but I suppose only a newbie can export a pointer to C++ class from dll, very strange to see it in a SAP product. – kludg Sep 29 '12 at 10:48
  • It's the same mistake as exporting a Delphi class from a DLL. – David Heffernan Sep 29 '12 at 10:52
  • @serg : excellent idea of using pointer. In my case, don't try to cast pointer to interface or you will get an exception. Leave the pointer as it is. Thanks. – Theo Sep 29 '12 at 19:20
  • But if you leave it as a pointer, how can you call methods on it? – David Heffernan Sep 29 '12 at 19:20
  • Sorry for my poor English. In this case, libSQLDBC_C.dll is a kind of C wrap. The original C++ library is libSQLDBC.dll. Methods on all interface has been wrapped outside the interface. For example, on original C++ we should use SQLDBC_Environment->createConnection as the "createConnection" is a method of SQLDBC_Environment class. On libSQLDBC_C.dll we use "SQLDBC_Environment_createConnection" with SQLDBC_Environment as input param. – Theo Sep 29 '12 at 19:33
  • OK, so that's the bridge part of my answer. – David Heffernan Sep 29 '12 at 20:32
3

A C++ function that returns an interface does not easily map to a Delphi function. The calling convention for return values that are managed types in Delphi does not match that used by C++.

To illustrate, I created a simple C++ test DLL that exports this function:

extern "C" __declspec(dllexport) IUnknown* __stdcall GetInterface()
{
    CoInitialize(NULL);
    IUnknown* result;
    CoCreateInstance(CLSID_ActiveDesktop, NULL, CLSCTX_INPROC_SERVER, 
      IID_IUnknown, (void**) &result);
    return result;
}

The obvious way to map this to Delphi is like this:

function GetInterface: IUnknown; stdcall; external DLL name '_GetInterface@0';

However, when we call this function it always returns nil.

The workaround is exactly as suggested by Serg:

function GetInterface: Pointer; stdcall; external DLL name '_GetInterface@0';

and we can then call it like this:

var
  intf: IUnknown;
....
Pointer(intf) := GetInterface;

When we do this, intf is not nil and we can call methods on it quite happily.

So, what we have learnt here is that Delphi cannot easily call external functions that return interfaces, unless those external functions were also implemented in Delphi. But at least we have a viable workaround.


Unfortunately this workaround is of no immediate use to you. That's because SQLDBC_IRuntime is a C++ class. It is not a COM compatible interface. Note that SQLDBC_IRuntime does not implement IInterface. So it doesn't provide _AddRef, _Release and QueryInterface. Delphi's interface support is predicated on the availability of IInterface. And this means that that you cannot use SQLDBC_IRuntime from Delphi.

You will need to make a C++ bridge DLL that exposes the functionality in a way that Delphi can invoke. For example by exposing plain old functions that call the C++ methods of SQLDBC_IRuntime.

David Heffernan
  • 601,492
  • 42
  • 1,072
  • 1,490
  • it does not return nil; the Delphi prototype is wrong, so the return value does not appear as the function's result. – kludg Sep 29 '12 at 08:14
  • @Serg Could you elaborate? Your link to my question doesn't apply here I think. There are many Win32 API functions that return COM interfaces so. My instincts are that the issue is that `SQLDBC_IRuntime` is simply not a COM interface. Even if you could get hold of it, what could you do with it? – David Heffernan Sep 29 '12 at 08:20
  • No, WinAPI functions usually return HResult (doubleword), not interfaces; I can elaborate but I need to analyze the argument stack of the function. – kludg Sep 29 '12 at 08:23
  • @Serg You could be right. Did you read this yet: http://support.smartbear.com/viewarticle/33100/ – David Heffernan Sep 29 '12 at 08:25
  • Good link, I thought exactly the same about the problem. – kludg Sep 29 '12 at 08:29
  • Finally i got the answer : using pointer and create a wrap class using base connection and dataset class. @Serg and David : Thanks for sharing. – Theo Sep 29 '12 at 19:16
  • Indeed `SQLDBC_IRuntime` is a C++ class: http://maxdb.sap.com/documentation/sqldbc/SQLDBC_API/classSQLDBC__IRuntime.html – Jeroen Wiert Pluimers May 15 '13 at 13:56