digitalmars.D - Can't use a C++ class from a DLL
- Artie (99/99) Oct 28 2012 I have a DLL with a C++ class and a factory function that creates
- Denis Shelomovskij (38/135) Oct 29 2012 First, to interact with C++ `interface` you need:
- Jakob Ovrum (4/6) Oct 29 2012 These return values should be const(char)* and the method
- Denis Shelomovskij (5/10) Oct 29 2012 Sorry, my bad.
- Artie (6/25) Oct 29 2012 Thank you very much, Denis. It was quite confusing to mix
- Daniel Murphy (9/12) Oct 29 2012 unsigned int and unsigned long are the same size in 32 bit C/C++, but ar...
- Artie (2/20) Oct 29 2012 That makes sense. I was unaware of such details. Thanks a lot.
- Gor Gyolchanyan (8/33) Oct 29 2012 It will be fine for Windows, because in Window long and unsigned long ar...
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
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
29.10.2012 16:40, Jakob Ovrum пишет:On Monday, 29 October 2012 at 12:11:11 UTC, Denis Shelomovskij wrote:Sorry, my bad. -- Денис В. Шеломовский Denis V. Shelomovskijconst 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
"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 14:01:09 UTC, Daniel Murphy wrote:"Artie" <apple2000 mail.ru> wrote in message news:uhdpnavdyokxigczlxto forum.dlang.org...That makes sense. I was unaware of such details. Thanks a lot.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
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:-- Bye, Gor Gyolchanyan.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