First up, here’s a big disclaimer if the title didn’t warn you enough: this is a hack. It’s just a proof-of-concept for getting extern(C++) classes working with betterC D. Also, DMD keeps getting better quickly, so if you’re reading this post when something more recent than version 2.080 is out, this hack is probably obsolete. Hopefully you’ll find this post interesting anyway if you’re either

If you haven’t read my earlier post about how polymorphism and inheritance work yet, I recommend doing that first.

Recap on Classes in D

In C++, there’s no essential difference between a struct and a class — they’re both like C structs with extra features. In particular, they support polymorphic inheritance, which requires hidden vtables. In D, a struct is like a plain, non-inheritable C struct, while a class is a more powerful thing that inherits from a base Object class. The Object class implements lots of convenient functionality like hash function support. In short, D structs are lower-level than C++ structs/classes, but D classes are higher-level.

It’s actually possible to get C++-style classes in D by marking the declaration with extern(C++). An extern(C++) class supports inheritance, but it doesn’t automatically inherit from D’s Object.

The Problem and Plan of Attack

The Object class is defined in the D runtime, so regular D classes don’t work in -betterC. In theory, extern(C++) classes should work in -betterC, but they don’t with the current version of DMD (version 2.080) because extern(C++) classes still rely on runtime type information (RTTI). RTTI is implemented using the TypeInfo D class, which creates a dependency on Object. Although this is only a link-time dependency, DMD barfs if it tries to compile a class definition with -betterC. (This is a good thing because compiler errors are much friendlier than linker errors, but it’s relevant to the hack.)

I did some experimenting, and the only real dependency on RTTI that I found was in class instance construction. Specifically, when constructing a new class instance, D initialises the memory used by the instance before calling the user-written constructor. This initialisation populates things like vtable pointers and default values for members (as defined in the class definition). It actually works by just blitting from a sample that’s stored in RTTI.

There’s one other little problem: destructors in D aren’t inherently virtual or recursive (in normal D code, the runtime emulates virtualness and recursiveness using RTTI). Making our own virtual and recursive destructors is easy (but a minor nuisance). You could use a template mixin to reduce the boilerplate, but I just wrote out the definitions explicitly for this example.

Here’s a high-level view of the workaround:

  1. Avoid the compiler error by putting the class definitions and related stuff into a separate file that’s compiled without -betterC.
  2. Reimplement class construction and destruction without RTTI.
  3. Do linker hacking to make up for the loss of -betterC.
  4. Link with code that’s compiled normally with -betterC.

The Class Code

Here’s the code for the classes. It’s a silly example that demonstrates

module classes;

import core.stdc.stdlib;
import core.stdc.stdio;
import core.stdc.string;

@nogc:

extern(C++)
{
        class Base
        {
                @nogc:

                this(int number)
                {
                        printf("Constructing Base instance at %p with number=%d.\n", this, number);
                        number_ = number;
                }

                ~this()
                {
                        printf("Destroying Base instance at %p.\n", this);
                }

                // Virtual destructor
                // (__dtor and __xdtor aren't virtual :/ )
                void destructor()
                {
                        this.__xdtor();
                }

                // Virtual method
                const(char)* whoAmI() const pure
                {
                        return "Base";
                }

                // Non-virtual method
                final int number() const pure
                {
                        return number_;
                }

                protected:
                int number_;

                private:
                // Used by construction hack
                this() pure {}
        }

        class Derived : Base
        {
                @nogc:

                this(int number, int multiplier)
                {
                        printf("Constructing Derived instance at %p with number=%d and multiplier=%d.\n", this, number, multiplier);
                        multiplier_ = multiplier;
                        super(number);
                }

                ~this()
                {
                        printf("Destroying Derived instance at %p.\n", this);
                }

                override void destructor()
                {
                        this.__xdtor();
                        // __dtor and __xdtor don't recursively destroy subclasses
                        super.destructor();
                }

                final int multiplyIt() const pure
                {
                        return number_ * multiplier_;
                }

                override const(char)* whoAmI() const pure
                {
                        return "Derived";
                }

                private:
                int multiplier_;

                // Used by construction hack
                this() pure {}
        }
}

Next there’s some templated factory code that does the construction, with a corresponding templated convenience function for destruction. This code is @nogc, so memory is managed using standard C malloc() and free().

The tricky part is correctly initialising the raw memory before calling the constructor. D’s ABI for class layout is specced out, but I dodged the issue completely with a little hack. For the purposes of this PoC, I made each class constructable with a side-effect-free default constructor. The factory function contains a static immutable instance of the class. The raw memory of this “model” instance gets blitted into the newly allocated instance memory, and we’ll know that we’ll have all the right vtable pointers, etc, in the right place. After that, we can safely call the normal class constructor on the new instance. Thanks to D’s CTFE, the model instance gets constructed at compile time, so any runtime library dependencies can be ripped out before linking with -betterC code. Effectively, we temporarily borrow the standard class construction code during compilation.

By the way, C++ classes have value semantics, but extern(C++) classes in D still have reference semantics, and a class instance reference is identical to a pointer at the binary level.

T make(T,Args...)(auto ref Args args)
{
        static immutable model = new T();
        enum kTSize = __traits(classInstanceSize, T);
        auto instance = cast(T)malloc(kTSize);
        memcpy(cast(void*)instance, cast(void*)model, kTSize);
        instance.__ctor(args);
        return instance;
}

void unmake(T)(T instance)
{
        instance.destructor();
        free(cast(void*)instance);
}

There were two things I had to do to get this code to link with -betterC code. First, DMD inserted a reference to a TypeInfo vtable from the runtime in an inconvenient place in the compiled code. Disassembly showed it wasn’t truly needed, so I just made the linker happy by inventing a dummy implementation:

pragma(mangle, "_D14TypeInfo_Class6__vtblZ")
immutable void* horrible_hack = null; // D:

Other D runtime references could be ripped out like it’s 2016 again:


$ dmd -c classes.d
$ objcopy -R '.[cd]tors.d_dso_[cd]tor' -R .text.d_dso_init classes.o

The User Code

Here’s the code that uses the classes. Again, it’s just a silly example that uses a bunch of class functionality. However, this time it’s hackless code that compiles cleanly with -betterC.

import core.stdc.stdio;

import classes;

@nogc:

void doStuff(Base instance)
{
        puts("***");
        printf("Got a class instance at %p\n", instance);
        // Demonstrate virtual function call
        printf("It's a %s.\n", instance.whoAmI());
        // Demonstrate non-virtual function call and base instance variable
        printf("It has value %d.\n", instance.number());
        puts("***");
}

extern(C)
int main()
{
        puts("make!Base();");
        auto base = make!Base(2);
        puts("doStuff(base);");
        doStuff(base);

        puts("make!Derived();");
        auto derived = make!Derived(21, 2);
        // Demonstrate "derived" working polymorphically
        puts("doStuff(derived);");
        doStuff(derived);
        // Demonstrate subclass instance variable and function call
        printf("assert(derived.multiplyIt() == %d);\n", derived.multiplyIt());

        puts("unmake(base);");
        unmake(base);
        puts("unmake(derived);");
        unmake(derived);

        return 0;
}

And it works!


$ dmd -betterC main.d classes.o
$ ./main
make!Base();
Constructing Base instance at 0x558bcf134270 with number=2.
doStuff(base);
***
Got a class instance at 0x558bcf134270
It's a Base.
It has value 2.
***
make!Derived();
Constructing Derived instance at 0x558bcf134290 with number=21 and multiplier=2.
Constructing Base instance at 0x558bcf134290 with number=21.
doStuff(derived);
***
Got a class instance at 0x558bcf134290
It's a Derived.
It has value 21.
***
assert(derived.multiplyIt() == 42);
unmake(base);
Destroying Base instance at 0x558bcf134270.
unmake(derived);
Destroying Derived instance at 0x558bcf134290.
Destroying Base instance at 0x558bcf134290.