www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Can't use a C++ class from a DLL

reply "Artie" <apple2000 mail.ru> writes:
I have a DLL with a C++ class and a factory function that creates 
it. The aim is to load the DLL, get an instance of the class and 
use it.

The interface of the DLL is as follows:
-----------------
class IBank
{
     public:
         virtual const char* APIENTRY getLastError() = 0;
         virtual const char* APIENTRY getDetail(char* detail) = 0;
         virtual const bool APIENTRY deposit(unsigned long number, 
double amount) = 0;
         virtual const bool APIENTRY withdraw(unsigned long 
number, double amount) = 0;
         virtual const double APIENTRY getBalance(unsigned long 
number) = 0;
         virtual const bool APIENTRY transfer(unsigned long 
numberFrom, IBank* bankTo, unsigned long numberTo, double amount) 
= 0;
         virtual const bool APIENTRY transferAccept(IBank* 
bankFrom, unsigned long numberTo, double amount) = 0;
};
-----------------

I've followed the instructions given at dlang.org to interface to 
C/C++ code but got no success. If I use extern(C++) at the place 
in D code where extern declaration is required I get an access 
violation when calling any method. On the other hand, if I use 
extern(Windows, C or Pascal) I can call a method successfully, 
except that I get wrong return value.

The D interface is declared as follows:
-----------------
extern (Windows) interface IBank
{
	const char* getLastError();
	const char* getDetail(char* detail);
	const bool deposit(uint number, double amount);
	const bool withdraw(uint number, double amount);
	const double getBalance(uint number);
	const bool transfer(uint numberFrom, IBank* bankTo, uint 
numberTo, double amount);
	const bool transferAccept(IBank* bankFrom, uint numberTo, double 
amount);
}

export extern (C) IBank Get();
-----------------


And the main program in D that uses the DLL:
-----------------
module main;

import std.stdio;
import core.runtime;
import core.sys.windows.windows;
import std.string;
import std.conv;

import ibank;

int main()
{
	alias extern(C) IBank function() getBankInstance;
	FARPROC pDllFunctionVBank, pDllFunctionSberbank;

	// Load DLL file
	void* handleVBank = Runtime.loadLibrary("vbank.dll");
	void* handleSberbank = Runtime.loadLibrary("sberbank.dll");

	if ( (handleVBank is null) || (handleSberbank is null) )
	{
		writeln("Couldn't find necessary DLL files");
		return 1;
	}

	getBankInstance get1 = cast(getBankInstance) 
GetProcAddress(handleVBank, "Get".toStringz);
	getBankInstance get2 = cast(getBankInstance) 
GetProcAddress(handleSberbank, "Get".toStringz);

	if ( get1 is null || get2 is null )
	{
		writeln("Couldn't load factory functions");
		return 2;
	}

	getBankInstance get;
	IBank vbank = (*get1)();
	IBank sberbank = get2();


	uint sbnum = 100500;
	uint vbnum = 128500;

	writeln("You have an account in Sberbank (100500)");
	auto balance = sberbank.getBalance(sbnum);
	writefln("getBalance(%d) = %s", sbnum, balance);
	bool res = sberbank.withdraw(sbnum, 500.0);
	writefln("withdraw(%d, %f) = %s", sbnum, 500.0, res);
	writeln("You got it!");
...
-----------------

The output I get is (in case I use extern (Windows, C or Pascal)):
-----------------
You have an account in Sberbank (100500)
getBalance(100500) = -nan
got into GenericBank::getBalance() // this is an output from a 
method called inside the DLL
account number = 100500 // inside the DLL
balance is 1100 // inside the DLL
withdraw(100500, 500.000000) = false
You got it!
-----------------
Oct 28 2012
parent reply Denis Shelomovskij <verylonglogin.reg gmail.com> writes:
28.10.2012 23:52, Artie пишет:
 I have a DLL with a C++ class and a factory function that creates it.
 The aim is to load the DLL, get an instance of the class and use it.

 The interface of the DLL is as follows:
 -----------------
 class IBank
 {
      public:
          virtual const char* APIENTRY getLastError() = 0;
          virtual const char* APIENTRY getDetail(char* detail) = 0;
          virtual const bool APIENTRY deposit(unsigned long number,
 double amount) = 0;
          virtual const bool APIENTRY withdraw(unsigned long number,
 double amount) = 0;
          virtual const double APIENTRY getBalance(unsigned long number)
 = 0;
          virtual const bool APIENTRY transfer(unsigned long numberFrom,
 IBank* bankTo, unsigned long numberTo, double amount) = 0;
          virtual const bool APIENTRY transferAccept(IBank* bankFrom,
 unsigned long numberTo, double amount) = 0;
 };
 -----------------

 I've followed the instructions given at dlang.org to interface to C/C++
 code but got no success. If I use extern(C++) at the place in D code
 where extern declaration is required I get an access violation when
 calling any method. On the other hand, if I use extern(Windows, C or
 Pascal) I can call a method successfully, except that I get wrong return
 value.

 The D interface is declared as follows:
 -----------------
 extern (Windows) interface IBank
 {
      const char* getLastError();
      const char* getDetail(char* detail);
      const bool deposit(uint number, double amount);
      const bool withdraw(uint number, double amount);
      const double getBalance(uint number);
      const bool transfer(uint numberFrom, IBank* bankTo, uint numberTo,
 double amount);
      const bool transferAccept(IBank* bankFrom, uint numberTo, double
 amount);
 }

 export extern (C) IBank Get();
 -----------------


 And the main program in D that uses the DLL:
 -----------------
 module main;

 import std.stdio;
 import core.runtime;
 import core.sys.windows.windows;
 import std.string;
 import std.conv;

 import ibank;

 int main()
 {
      alias extern(C) IBank function() getBankInstance;
      FARPROC pDllFunctionVBank, pDllFunctionSberbank;

      // Load DLL file
      void* handleVBank = Runtime.loadLibrary("vbank.dll");
      void* handleSberbank = Runtime.loadLibrary("sberbank.dll");

      if ( (handleVBank is null) || (handleSberbank is null) )
      {
          writeln("Couldn't find necessary DLL files");
          return 1;
      }

      getBankInstance get1 = cast(getBankInstance)
 GetProcAddress(handleVBank, "Get".toStringz);
      getBankInstance get2 = cast(getBankInstance)
 GetProcAddress(handleSberbank, "Get".toStringz);

      if ( get1 is null || get2 is null )
      {
          writeln("Couldn't load factory functions");
          return 2;
      }

      getBankInstance get;
      IBank vbank = (*get1)();
      IBank sberbank = get2();


      uint sbnum = 100500;
      uint vbnum = 128500;

      writeln("You have an account in Sberbank (100500)");
      auto balance = sberbank.getBalance(sbnum);
      writefln("getBalance(%d) = %s", sbnum, balance);
      bool res = sberbank.withdraw(sbnum, 500.0);
      writefln("withdraw(%d, %f) = %s", sbnum, 500.0, res);
      writeln("You got it!");
 ...
 -----------------

 The output I get is (in case I use extern (Windows, C or Pascal)):
 -----------------
 You have an account in Sberbank (100500)
 getBalance(100500) = -nan
 got into GenericBank::getBalance() // this is an output from a method
 called inside the DLL
 account number = 100500 // inside the DLL
 balance is 1100 // inside the DLL
 withdraw(100500, 500.000000) = false
 You got it!
 -----------------
First, to interact with C++ `interface` you need: --- extern(C++) interface Ixxx { ... } --- Your `IBank` C++ functions are declared as `APIENTRY` which is almost definitely defined as `__stdcall`. So the correct interface declaration is: --- extern(C++) interface IBank { extern(Windows) const char* getLastError(); ... } --- As all your functions are `APIENTRY`, write `extern(Windows):` before them. And use `c_ulong` as analogue of `unsigned long`. So full correct `IBank` interface declaration here: --- import core.stdc.config: c_ulong; extern(C++) interface IBank { extern(Windows): const char* getLastError(); const char* getDetail(char* detail); bool deposit(c_ulong number, double amount); bool withdraw(c_ulong number, double amount); double getBalance(c_ulong number); bool transfer(c_ulong numberFrom, IBank* bankTo, c_ulong numberTo, double amount); bool transferAccept(IBank* bankFrom, c_ulong numberTo, double amount); }; --- -- Денис В. Шеломовский Denis V. Shelomovskij
Oct 29 2012
next sibling parent reply "Jakob Ovrum" <jakobovrum gmail.com> writes:
On Monday, 29 October 2012 at 12:11:11 UTC, Denis Shelomovskij 
wrote:
     const char* getLastError();
     const char* getDetail(char* detail);
These return values should be const(char)* and the method shouldn't be const.
Oct 29 2012
parent Denis Shelomovskij <verylonglogin.reg gmail.com> writes:
29.10.2012 16:40, Jakob Ovrum пишет:
 On Monday, 29 October 2012 at 12:11:11 UTC, Denis Shelomovskij wrote:
     const char* getLastError();
     const char* getDetail(char* detail);
These return values should be const(char)* and the method shouldn't be const.
Sorry, my bad. -- Денис В. Шеломовский Denis V. Shelomovskij
Oct 29 2012
prev sibling parent reply "Artie" <apple2000 mail.ru> writes:
 As all your functions are `APIENTRY`, write `extern(Windows):` 
 before them. And use `c_ulong` as analogue of `unsigned long`. 
 So full correct `IBank` interface declaration here:
 ---
 import core.stdc.config: c_ulong;

 extern(C++) interface IBank
 {
 extern(Windows):
     const char* getLastError();
     const char* getDetail(char* detail);
     bool deposit(c_ulong number, double amount);
     bool withdraw(c_ulong number, double amount);
     double getBalance(c_ulong number);
     bool transfer(c_ulong numberFrom, IBank* bankTo, c_ulong 
 numberTo, double amount);
     bool transferAccept(IBank* bankFrom, c_ulong numberTo, 
 double amount);
 };
 ---
Thank you very much, Denis. It was quite confusing to mix extern(C++) and extern(Windows). And I also thank Jakob for syntax specification. BTW, it's said in the ABI reference that `unsigned long` must be substituted with `uint`. And it seems to work fine for the data I used in the example.
Oct 29 2012
next sibling parent reply "Daniel Murphy" <yebblies nospamgmail.com> writes:
"Artie" <apple2000 mail.ru> wrote in message 
news:uhdpnavdyokxigczlxto forum.dlang.org...
 BTW, it's said in the ABI reference that `unsigned long` must be 
 substituted with `uint`. And it seems to work fine for the data I used in 
 the example.
unsigned int and unsigned long are the same size in 32 bit C/C++, but are mangled differently when using C++ name mangling. unsigned long may not be 32 bits on all platforms, so to portably match the size used by the native C/C++ compiler you should use the c_ulong aliases. The problem with name mangling is avoided in this case as you're not using C++ name mangling, you're using stdcall name mangling, which only keeps track of argument sizes, not their types.
Oct 29 2012
parent "Artie" <apple2000 mail.ru> writes:
On Monday, 29 October 2012 at 14:01:09 UTC, Daniel Murphy wrote:
 "Artie" <apple2000 mail.ru> wrote in message
 news:uhdpnavdyokxigczlxto forum.dlang.org...
 BTW, it's said in the ABI reference that `unsigned long` must 
 be substituted with `uint`. And it seems to work fine for the 
 data I used in the example.
unsigned int and unsigned long are the same size in 32 bit C/C++, but are mangled differently when using C++ name mangling. unsigned long may not be 32 bits on all platforms, so to portably match the size used by the native C/C++ compiler you should use the c_ulong aliases. The problem with name mangling is avoided in this case as you're not using C++ name mangling, you're using stdcall name mangling, which only keeps track of argument sizes, not their types.
That makes sense. I was unaware of such details. Thanks a lot.
Oct 29 2012
prev sibling parent Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> writes:
It will be fine for Windows, because in Window long and unsigned long are
always 4 byte. But other systems (for instance, all Linux distros) have
long and unsigned long 8 bytes under 64-bit systems. c_ulong makes sure,
that it's the correct size on all systems.

On Mon, Oct 29, 2012 at 5:55 PM, Artie <apple2000 mail.ru> wrote:

 As all your functions are `APIENTRY`, write `extern(Windows):` before
 them. And use `c_ulong` as analogue of `unsigned long`. So full correct
 `IBank` interface declaration here:
 ---
 import core.stdc.config: c_ulong;

 extern(C++) interface IBank
 {
 extern(Windows):
     const char* getLastError();
     const char* getDetail(char* detail);
     bool deposit(c_ulong number, double amount);
     bool withdraw(c_ulong number, double amount);
     double getBalance(c_ulong number);
     bool transfer(c_ulong numberFrom, IBank* bankTo, c_ulong numberTo,
 double amount);
     bool transferAccept(IBank* bankFrom, c_ulong numberTo, double amount);
 };
 ---
Thank you very much, Denis. It was quite confusing to mix extern(C++) and extern(Windows). And I also thank Jakob for syntax specification. BTW, it's said in the ABI reference that `unsigned long` must be substituted with `uint`. And it seems to work fine for the data I used in the example.
-- Bye, Gor Gyolchanyan.
Oct 29 2012