digitalmars.D - Can't use a C++ class from a DLL
- "Artie" <apple2000 mail.ru> Oct 28 2012
- Denis Shelomovskij <verylonglogin.reg gmail.com> Oct 29 2012
- Denis Shelomovskij <verylonglogin.reg gmail.com> Oct 29 2012
- "Daniel Murphy" <yebblies nospamgmail.com> Oct 29 2012
- "Jakob Ovrum" <jakobovrum gmail.com> Oct 29 2012
- "Artie" <apple2000 mail.ru> Oct 29 2012
- Gor Gyolchanyan <gor.f.gyolchanyan gmail.com> Oct 29 2012
- "Artie" <apple2000 mail.ru> Oct 29 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!
-----------------
Oct 28 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
Oct 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
Oct 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.
Oct 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.
Oct 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.
Oct 29 2012
--f46d04083f3b2308b304cd331c11 Content-Type: text/plain; charset=UTF-8 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. --f46d04083f3b2308b304cd331c11 Content-Type: text/html; charset=UTF-8 Content-Transfer-Encoding: quoted-printable It will be fine for Windows, because in Window long and unsigned long are a= lways 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 i= t's the correct size on all systems.<br> <br><div class=3D"gmail_quote">On Mon, Oct 29, 2012 at 5:55 PM, Artie <span= dir=3D"ltr"><<a href=3D"mailto:apple2000 mail.ru" target=3D"_blank">app= le2000 mail.ru</a>></span> wrote:<br><blockquote class=3D"gmail_quote" s= tyle=3D"margin:0 0 0 .8ex;border-left:1px #ccc solid;padding-left:1ex"> <div class=3D"im"><blockquote class=3D"gmail_quote" style=3D"margin:0 0 0 .= 8ex;border-left:1px #ccc solid;padding-left:1ex"> <br> 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:<br> ---<br> import core.stdc.config: c_ulong;<br> <br> extern(C++) interface IBank<br> {<br> extern(Windows):<br> =C2=A0 =C2=A0 const char* getLastError();<br> =C2=A0 =C2=A0 const char* getDetail(char* detail);<br> =C2=A0 =C2=A0 bool deposit(c_ulong number, double amount);<br> =C2=A0 =C2=A0 bool withdraw(c_ulong number, double amount);<br> =C2=A0 =C2=A0 double getBalance(c_ulong number);<br> =C2=A0 =C2=A0 bool transfer(c_ulong numberFrom, IBank* bankTo, c_ulong numb= erTo, double amount);<br> =C2=A0 =C2=A0 bool transferAccept(IBank* bankFrom, c_ulong numberTo, double= amount);<br> };<br> ---<br> </blockquote> <br></div> Thank you very much, Denis. It was quite confusing to mix extern(C++) and e= xtern(Windows). And I also thank Jakob for syntax specification.<br> <br> BTW, it's said in the ABI reference that `unsigned long` must be substi= tuted with `uint`. And it seems to work fine for the data I used in the exa= mple.<br> </blockquote></div><br><br clear=3D"all"><div><br></div>-- <br>Bye,<br>Gor = Gyolchanyan.<br> --f46d04083f3b2308b304cd331c11--
Oct 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.
Oct 29 2012









Denis Shelomovskij <verylonglogin.reg gmail.com> 