Building a mixed C++ and D project

From D Wiki
Revision as of 11:26, 15 April 2018 by D user (talk | contribs) (edit)
Jump to: navigation, search

NOTE: This article is WIP!

In this tutorial, we're going to look at how to build a project consisting of both C++ and D source code.

Compilation model

A typically program goes through the following phases:

  1. Write code -> source files (.d, .cpp and .h)
  2. Compile -> object files (.o or .obj)
  3. Link (object files and static libraries) -> executable
  4. Run the executable -> profit

Quite often compilers do steps 2) and 3) in a single command, but it is important to know the difference.

If something happens during 2) we say it happens at compile-time. If it happens during 3) - it happened at link-time and if it occurred during 4) - it was at run-time.

The input to the compiler (one or more source files) is known as a compilation unit (or translation unit in C++). The output of the compiler is called an object file. Object files contain relocatable machine code and debugging and linking metadata. Object files can be packaged in archives, also known as library files. There are two types of library files - static and dynamic - with static libraries being designed to be added to an executable file at link-time, and dynamic libraries designed to be accessed at run-time.


-> static or dynamic libraries (.a or .lib for static and .so or .lib for dynamic)

Often the compiler

Compiling

The compiler reads the .cpp or .d file(s) and produces object files or archive files (static libraries)

$ dmd -c dfile1.d
Produces:
.d -> .obj on Windows
.d -> .o on Linux


Linking

The linker reads the object and/or static library files produced by the compiler and produces a new object file or library or executable.

The compiler can

Consuming D libraries from C++ and vice-versa 101

DRuntime

A large portion of D's features depend on the D runtime. This means that DRuntime needs to be initialized before these features are used.

ABI

  • To call C++ functions from D and vice-versa, they need to be declared in a particular way on both sides, so that C++ and D binaries are ABI-compatible.
  • To pass objects back and forth you need to match the layout in their declarations on both sides.
  • Generally there are 4 types of binaries:
    • executables (like .exe or .com on Windows)
    • object files (.obj on Windows and .o on Linux)
    • static libraries (.a on Linux .lib on Windows )
    • dynamic libraries (.dll on Windows, .so on Linux)

Program initialization

Typically, when the operating system starts an executable, the C runtime is first initialized before control is transferred to the executable's C main() function. With D the process is similar. The difference is that the C main() is automatically generated and is used to initialize DRuntime. After the DRuntime is initialized the control is passed to the executable's D main() function.

Examples

Consuming a D library with (no DRuntime and Phobos support) from C

Let's start with a simple D library that provides only a single function:

// ex1_d_library.d
module ex1_d_library;

extern (C) int add(int a, int b)
{
    return a + b;
}

// Only needed on Linux. Read below from more info.
extern(C) void _d_dso_registry() {}

And a C program that calls it:

// ex1_c_main.c
#include <stdio.h>

int add(int, int);

int main()
{
    int result = add(40, 2);
    printf("The result is: %i\n", result);
}

Since we do not need DRuntime support for such a small library, we can use the -betterC switch to tell the compiler to not produce references to DRuntime. That way we can avoid the need to link to DRuntime which will help to reduce the binary size.

Linux

On Linux the compiler generates code for shared libraries support that will call the _d_dso_registry from DRuntime. Since we don't need neither support for shared libraries, nor DRuntime, we can workaround this by adding an empty void _d_dso_registry() function with extern (C) linkage.

dmd -c -betterC ex1_d_library.d
# This should have produced a file named 'ex1_d_library.o'.

gcc ex1_c_main.c ex1_d_library.o -o ex1_prog
# The result should be an executable named 'ex1_prog'.

Windows 32-bit, Visual Studio 2015

# The commands below are tested in the VS2015 x86 Native Tools Command Prompt

dmd -c -m32mscoff -betterC ex1_d_library.d
# This should have produced a file named 'ex1_d_library.obj'.

cl /nologo /Feex1_prog.exe ex1_c_main.c ex1_d_library.obj
# The result should be an executable named 'ex1_prog.exe'.

Windows 64-bit, Visual Studio 2015

# The commands below are tested in the VS2015 x64 Native Tools Command Prompt

dmd -c -m64 -betterC ex1_d_library.d
# This should have produced a file named 'ex1_d_library.obj'.

cl /nologo /Feex1_prog.exe ex1_c_main.c ex1_d_library.obj
# The result should be an executable named 'ex1_prog.exe'.

////?? Currently, to produce a working library on Windows, the library needs to have an empty main() function. We can tell the compiler to provide one for us by specifying the -main switch.