C and D can link together. This lets us use C functions in D and also lets us use D functions from C. Here, we'll integrate some of the code in D into a C project. This knowledge is also useful for writing callback functions in D while using a C library from the D code, and can be used for the JNI or P/Invoke code in Java and .NET as well. Thanks to extern(C)
, anything that can interface with C can also interface with D.
If you are using DMD on 32-bit Windows, you'll also want to get DMC for Windows—the Digital Mars C compiler. DMC is automatically installed by the D installer. If you are using DMD from the ZIP file distribution, you can get DMC as a ZIP file from http://digitalmars.com/.
Let's execute the following steps to write a part of a C program in D:
rt_init()
. You can declare this to be extern int rt_init();
. Don't forget to check the return value—it returns 0
if initialization fails.extern(C)
, taking and returning only the types known to C (for example, integers, structs, pointers, and so on) and not throwing exceptions back to C. The D program can also call functions from the C program by declaring the function prototypes with extern(C)
.rt_term()
before terminating so that you can terminate the D runtime.The following is our C program, program.c
:
#include <stdio.h> int rt_init(); // prototypes of standard D runtime functions void rt_term(); void helloD(); // the prototype of own our D function void helloC() { // an existing C function we'll call from D printf("Hello from C! "); } int main() { if(!rt_init()) { // init the D runtime fprintf(stderr, "D rt init failed. "); return 1; } helloD(); // call the D function rt_term(); // end the D runtime return 0; }
The following is our D file, test.d
:
import std.stdio, core.stdc.stdio : stderr; extern(C) void helloC() nothrow; // a function from C extern(C) void helloD() nothrow { helloC(); try { writeln("hello from D!"); } catch(Throwable t) { fprintf(stderr, "writeln threw an exception. "); } }
Compile the program on 32-bit Windows system using the following commands:
dmc -c program.c dmd test.d program.obj phobos.lib
Compile the program on Linux using the following commands:
gcc –c program.c dmd test.d program.o
You may also choose to compile the D file separately with dmd –c test.d
, then link both object files together in a separate step. This method may be easier to integrate into a more complex C program build process. Add the D object files in the same way you add any other C object file, and don't forget to link them in the Phobos library as well. You may have to add the location of the Phobos library to your linker command line; for example, adding the –Lpath/to/dmd2/linux/bin32/lib
argument to ld
on Linux.
D functions with the extern(C)
linkage follow the C calling convention, and D compilers produce standard object files—just like C compilers. This works both ways; D can access C functions and C can access D functions. While integrating D into a C program, it is important to initialize the D runtime before calling most D functions. The druntime
library provides the C interface functions, rt_init
and rt_term
, to do this for you—just be sure to call them and check for failure, and the D runtime library will handle the other details of its initialization.
D and C cannot read each other's source code directly. This makes it necessary to reproduce the data structure and function prototype declarations in both languages. Often, this isn't much work, but care is required to ensure they remain in sync. If the definitions get out of sync, the code may still compile, but then crash at runtime due to an inconsistency between over what data goes where. This process can often be automated in both directions; dstep
is a utility that reads C header files and generates a matching D interface module, and dtoh
reads a D file and generates a corresponding C header file.
The D language will allow extern(C)
functions to do everything any other function can do, and in many cases, you can make them work in C as well. However, it is recommended to follow the following rules while interfacing with C:
nothrow
annotation does not apply to unrecoverable errors that, nevertheless, use the exception mechanism. So, it is important to wrap the outermost interface functions in a try
/catch
block, catching the generic Throwable
superclass to cover both errors and exceptions.malloc
to receive data instead of storing the dynamic array pointers in D).Once, I was writing an event loop program using the self-pipe technique. The pipe pair was created with an operating system function. The event loop wrote pointers to event structures of the pipe and read them back out on the other side. Under heavier use, it started to crash at random. The reason was that for the split second between writing an event and reading it back out, the garbage collector ran and found no reference to the event object (since the only existing reference was the pointer sitting in the operating system's pipe buffer), thus freeing it. I fixed the problem by allocating the event objects with malloc
and freeing them with free
when they were consumed, instead of relying on the garbage collector.
To help with the passing data, it is useful to pass pointer+length
pairs instead of arrays and pointers to structs instead of classes, similar to how you'd write the functions in C. Internally, your D program may use any features, just mind the interface.
dstep
program, refer to http://github.com/Jacob-carlborg/dstepdtoh
program, refer to http://github.com/adamdruppe/dtoh3.144.9.169