Writing part of a C program in D

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.

Getting ready

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/.

How to do it…

Let's execute the following steps to write a part of a C program in D:

  1. Before calling any D function from C, be sure to call 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.
  2. In your D file, write the functions you want as 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).
  3. In the C file, copy the function prototype of the D functions and call them.
  4. Call rt_term() before terminating so that you can terminate the D runtime.
  5. Compile and link the programs. Don't forget to link the Phobos library as well.

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.

How it works…

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:

  • Don't pass immutable data to or from a C program because C cannot guarantee that it will not be changed. If immutable data is changed, the result is undefined behavior in D.
  • Don't throw exceptions of any type that pass through the C program. The C compiler knows nothing about exceptions and may not generate the correct code to continue stack unwinding—the exception may be silently lost and the C code almost certainly is not exception-safe, putting it at risk for resource leaks even if the code does work. Note that D's 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.
  • Don't pass the garbage collector (GC) allocated data that may be stored by the C code. If no reference exists on the D heap—even though C might still hold a reference to the object—the garbage collector will consider it unused and may free it. If your C program stores any pointers, be sure the resource is managed in a way that is compatible with C; for example, passing pointers to buffers created with malloc to receive data instead of storing the dynamic array pointers in D).

    Tip

    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.

See also

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.144.9.169