Interfacing to C++
While D is fully capable of interfacing to C, its ability to interface to C++ is much more limited. There are three ways to do it:
- Use C++'s ability to create a C interface, and then use D's ability to interface with C to access that interface.
- Use C++'s ability to create a COM interface, and then use D's ability to interface with COM to access that interface.
- Use the limited ability described here to connect directly to C++ functions and classes.
The General Idea
Being 100% compatible with C++ means more or less adding a fully functional C++ compiler front end to D. Anecdotal evidence suggests that writing such is a minimum of a 10 man-year project, essentially making a D compiler with such capability unimplementable. Other languages looking to hook up to C++ face the same problem, and the solutions have been:
- Support the COM interface (but that only works for Windows).
- Laboriously construct a C wrapper around the C++ code.
- Use an automated tool such as SWIG to construct a C wrapper.
- Reimplement the C++ code in the other language.
- Give up.
D takes a pragmatic approach that assumes a couple modest accommodations can solve a significant chunk of the problem:
- matching C++ name mangling conventions
- matching C++ function calling conventions
- matching C++ virtual function table layout for single inheritance
Calling C++ Global Functions From D
Given a C++ function in a C++ source file:
#include <iostream>
using namespace std;
int foo(int i, int j, int k) {
cout << "i = " << i << endl;
cout << "j = " << j << endl;
cout << "k = " << k << endl;
return 7;
}
In the corresponding D code, foo is declared as having C++ linkage and function calling conventions:
extern (C++) int foo(int i, int j, int k);
and then it can be called within the D code:
extern (C++) int foo(int i, int j, int k);
void main() {
foo(1,2,3);
}
Compiling the two files, the first with a C++ compiler, the second with a D compiler, linking them together, and then running it yields:
i = 1
j = 2
k = 3
There are several things going on here:
- D understands how C++ function names are "mangled" and the correct C++ function call/return sequence.
- Because modules are not part of C++, each function with C++ linkage must be globally unique within the program.
- There are no __cdecl, __far, __stdcall, __declspec, or other such nonstandard C++ extensions in D.
- There are no volatile type modifiers in D.
- Strings are not 0 terminated in D. See "Data Type Compatibility" for more information about this. However, string literals in D are 0 terminated.
C++ functions that reside in namespaces cannot be direcly called from D.
Calling Global D Functions From C++
To make a D function accessible from C++, give it C++ linkage:
import std.stdio;
extern (C++) int foo(int i, int j, int k) {
writefln("i = %s", i);
writefln("j = %s", j);
writefln("k = %s", k);
return 1;
}
extern (C++) void bar();
void main() {
bar();
}
The C++ end looks like:
int foo(int i, int j, int k);
void bar() {
foo(6, 7, 8);
}
Compiling, linking, and running produces the output:
i = 6
j = 7
k = 8
Classes
D classes are singly rooted by Object, and have an incompatible layout from C++ classes. D interfaces, however, are very similar to C++ single inheritance class heirarchies. So, a D interface with the attribute of extern (C++) will have a virtual function pointer table (vtbl[]) that exactly matches C++'s. A regular D interface has a vtbl[] that differs in that the first entry in the vtbl[] is a pointer to D's RTTI info, whereas in C++ the first entry points to the first virtual function.
Calling C++ Virtual Functions From D
Given C++ source code defining a class like:
#include <iostream>
using namespace std;
class D {
public:
virtual int bar(int i, int j, int k)
{
cout << "i = " << i << endl;
cout << "j = " << j << endl;
cout << "k = " << k << endl;
return 8;
}
};
D *getD() {
D *d = new D();
return d;
}
We can get at it from D code like:
extern (C++) {
interface D {
int bar(int i, int j, int k);
}
D getD();
}
void main() {
D d = getD();
d.bar(9,10,11);
}
Calling D Virtual Functions From C++
Given D code like:
extern (C++) int callE(E);
extern (C++) interface E {
int bar(int i, int j, int k);
}
class F : E {
extern (C++) int bar(int i, int j, int k)
{
writefln("i = ", i);
writefln("j = ", j);
writefln("k = ", k);
return 8;
}
}
void main() {
F f = new F();
callE(f);
}
The C++ code to access it looks like:
class E {
public:
virtual int bar(int i, int j, int k);
};
int callE(E *e) {
return e->bar(11,12,13);
}
Note:
- non-virtual functions, and static member functions, cannot be accessed.
- class fields can only be accessed via virtual getter and setter methods.
Function Overloading
C++ and D follow different rules for function overloading. D source code, even when calling extern (C++) functions, will still follow D overloading rules.
Storage Allocation
C++ code explicitly manages memory with calls to ::operator new() and ::operator delete(). D allocates memory using the D garbage collector, so no explicit delete's are necessary. D's new and delete are not compatible with C++'s ::operator new and ::operator delete. Attempting to allocate memory with C++ ::operator new and deallocate it with D's delete, or vice versa, will result in miserable failure.
D can still explicitly allocate memory using std.c.stdlib.malloc() and std.c.stdlib.free(), these are useful for connecting to C++ functions that expect malloc'd buffers, etc.
If pointers to D garbage collector allocated memory are passed to C++ functions, it's critical to ensure that that memory will not be collected by the garbage collector before the C++ function is done with it. This is accomplished by:
- Making a copy of the data using std.c.stdlib.malloc() and passing the copy instead.
- Leaving a pointer to it on the stack (as a parameter or automatic variable), as the garbage collector will scan the stack.
- Leaving a pointer to it in the static data segment, as the garbage collector will scan the static data segment.
- Registering the pointer with the garbage collector with the std.gc.addRoot() or std.gc.addRange() calls.
An interior pointer to the allocated memory block is sufficient to let the GC know the object is in use; i.e. it is not necessary to maintain a pointer to the beginning of the allocated memory.
The garbage collector does not scan the stacks of threads not created by the D Thread interface. Nor does it scan the data segments of other DLL's, etc.
Data Type Compatibility
D type | C type |
---|---|
void | void |
byte | signed char |
ubyte | unsigned char |
char | char (chars are unsigned in D) |
wchar | wchar_t (when sizeof(wchar_t) is 2) |
dchar | wchar_t (when sizeof(wchar_t) is 4) |
short | short |
ushort | unsigned short |
int | int |
uint | unsigned |
long | long long |
ulong | unsigned long long |
float | float |
double | double |
real | long double |
ifloat | no equivalent |
idouble | no equivalent |
ireal | no equivalent |
cfloat | no equivalent |
cdouble | no equivalent |
creal | no equivalent |
struct | struct |
union | union |
enum | enum |
class | no equivalent |
type* | type * |
no equivalent | type & |
type[dim] | type[dim] |
type[dim]* | type(*)[dim] |
type[] | no equivalent |
type[type] | no equivalent |
type function(parameters) | type(*)(parameters) |
type delegate(parameters) | no equivalent |
These equivalents hold for most 32 bit C++ compilers. The C++ standard does not pin down the sizes of the types, so some care is needed.
Structs and Unions
D structs and unions are analogous to C's.
C code often adjusts the alignment and packing of struct members with a command line switch or with various implementation specific #pragma's. D supports explicit alignment attributes that correspond to the C compiler's rules. Check what alignment the C code is using, and explicitly set it for the D struct declaration.
D does not support bit fields. If needed, they can be emulated with shift and mask operations. htod will convert bit fields to inline functions that do the right shift and masks.
Object Construction and Destruction
Similarly to storage allocation and deallocation, objects constructed in D code should be destructed in D, and objects constructed in C++ should be destructed in C++ code.
Special Member Functions
D cannot call C++ special member functions, and vice versa. These include constructors, destructors, conversion operators, operator overloading, and allocators.
Runtime Type Identification
D runtime type identification uses completely different techniques than C++. The two are incompatible.
C++ Class Objects by Value
D can access POD (Plain Old Data) C++ structs, and it can access C++ class virtual functions by reference. It cannot access C++ classes by value.
C++ Templates
D templates have little in common with C++ templates, and it is very unlikely that any sort of reasonable method could be found to express C++ templates in a link-compatible way with D.
This means that the C++ STL, and C++ Boost, likely will never be accessible from D.
Exception Handling
D and C++ exception handling are completely different. Throwing exceptions across the boundaries between D and C++ code will likely not work.
Future Developments
How the upcoming C++0x standard will affect this is not known.
Over time, more aspects of the C++ ABI may be accessible directly from D.