Interfacing to C
D is designed to fit comfortably with a C compiler for the target system. D makes up for not having its own VM by relying on the target environment's C runtime library. It would be senseless to attempt to port to D or write D wrappers for the vast array of C APIs available. How much easier it is to just call them directly.This is done by matching the C compiler's data types, layouts, and function call/return sequences.
Calling C Functions
C functions can be called directly from D. There is no need for wrapper functions, argument swizzling, and the C functions do not need to be put into a separate DLL.The C function must be declared and given a calling convention, most likely the "C" calling convention, for example:
extern (C) int strcmp(char* string1, char* string2);and then it can be called within D code in the obvious way:
import std.string; int myDfunction(char[] s) { return strcmp(std.string.toStringz(s), "foo"); }There are several things going on here:
- D understands how C function names are "mangled" and the correct C function call/return sequence.
- C functions cannot be overloaded with another C function with the same name.
- There are no __cdecl, __far, __stdcall, __declspec, or other such C type modifiers in D. These are handled by attributes, such as extern (C).
- There are no const or volatile type modifiers in D. To declare a C function that uses those type modifiers, just drop those keywords from the declaration.
- Strings are not 0 terminated in D. See "Data Type Compatibility" for more information about this. However, string literals in D are 0 terminated.
// myfunc() can be called from any C function extern (C) { void myfunc(int a, int b) { ... } }
Storage Allocation
C code explicitly manages memory with calls to malloc() and free(). D allocates memory using the D garbage collector, so no explicit free's are necessary.D can still explicitly allocate memory using c.stdlib.malloc() and 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 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 gc.addRoot() or gc.addRange() calls.
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 |
bit | no equivalent |
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 | float _Imaginary |
idouble | double _Imaginary |
ireal | long double _Imaginary |
cfloat | float _Complex |
cdouble | double _Complex |
creal | long double _Complex |
struct | struct |
union | union |
enum | enum |
class | no equivalent |
type* | 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.
Calling printf()
This mostly means checking that the printf format specifier matches the corresponding D data type. Although printf is designed to handle 0 terminated strings, not D dynamic arrays of chars, it turns out that since D dynamic arrays are a length followed by a pointer to the data, the %.*s format works perfectly:void foo(char[] string) { printf("my string is: %.*s\n", string); }The printf format string literal in the example doesn't end with \0. This is because string literals, when they are not part of an initializer to a larger data structure, have a \0 character helpfully stored after the end of them.
An improved D function for formatted output is std.stdio.writef().
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.
Interfacing to C++
D does not provide an interface to C++. Since D, however, interfaces directly to C, it can interface directly to C++ code if it is declared as having C linkage.
D class objects are incompatible with C++ class objects.