Thread overview
Can't use a C++ class from a DLL
Oct 28, 2012
Artie
Oct 29, 2012
Denis Shelomovskij
Oct 29, 2012
Jakob Ovrum
Oct 29, 2012
Denis Shelomovskij
Oct 29, 2012
Artie
Oct 29, 2012
Daniel Murphy
Oct 29, 2012
Artie
Oct 29, 2012
Gor Gyolchanyan
October 28, 2012
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!
-----------------
October 29, 2012
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
October 29, 2012
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.

October 29, 2012
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
October 29, 2012
>
> 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.
October 29, 2012
"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.


October 29, 2012
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.


October 29, 2012
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.