Status of Win32 C++ interop

Benjamin Thaut via Digitalmars-d-learn digitalmars-d-learn at puremagic.com
Mon Sep 7 11:37:48 PDT 2015


On Friday, 4 September 2015 at 16:19:49 UTC, Laeeth Isharc wrote:
> Hi Benjamin
>
> Would you be able to give a little more colour on what the 
> limits are of interoperability for C++ with DMD master or 
> release ?  As I understand it destructors and constructors 
> don't work, and obviously it will get tricky passing structures 
> and classes from C++ libraries not ported to D.


So, things that work really well are classes. Given that you 
construct them in the their "native environment". E.g. create D 
objects in D land and C++ objects in C++ land. I usually use 
factory methods for this. Manually destroying objects also works 
if you add some kind of "Destory" method (can be virtual, but 
doesn't have to be) to your class which simply does a "delete 
this" or similar. The only problems with interfacing C++ classes 
to D is if they have a virtual destructor. But there is a easy 
workaround like so:

C++:

class SomeBaseClass
{
public:
   virtual ~SomeBaseClass();
   virtual void SomeVirtualFunc();
}

D:
extern(C++) class SomeBaseClass
{
protected:
   abstract void __cppDtor(); // don't call

public:
   void SomeVirtualFunc(); // default virtual in D
}

Free functions, static functions, non virtual functions all work 
flawlessly.

I recommend that you create your own pointer type on the C++ 
side, e.g. DPtr which calls the GC.addRoot / GC.removeRoot 
methods of the D's gc interface in the apropriate places in case 
you want to pass D objects into C++ space. If you have a 
reference counting concept on the c++ side its also possible to 
build a small proxy object which does reference counting in c++ 
and calls GC.removeRoot when the last reference count drops.

If you want to bind more complex types, e.g. c++ templates you 
can either declare them extern(C++) in D and the compiler will do 
the correct mangling, or what I did to make them fully functional 
on both sides: I did a full implementation both in D and C++. The 
implementation ensures that the data layout is exactly the same 
for D and C++ but other than that the implementation is 
duplicated on both sides and even different in some cases. It 
still possible to pass this complex type from C++ to D because 
the data layout is the same. I did this for a custom hash map 
implementation and passing it around works flawlessly (by 
reference)

I had another complicated case where I wanted a complex value 
type that is implemented in C++ on the D side. It had a lot of 
members which types don't have a equivalent in D, so I did the 
following.

extern(C++)
{
   void constructComplexStruct(ComplexStruct* self);
   void destructComplexStruct(ComplexStruct* self);
}

struct DefaultCtor {}; // helper type
alias defaultCtor = DefaultCtor();

struct ComplexStruct
{
   @disable this();
   @disable this(this);
   this(DefaultCtor)
   {
     constructComplexStruct(&this);
   }

   ~this()
   {
     destructComplexStruct(~this);
   }

   extern(C++) void SomeMethodOfComplexStruct();

private:

   // must be sizeof(ComplexStruct) from c++, use unittest to 
ensure!
   void[288] m_data;
}

constructComplexStruct and destructComplexStruct just do a 
placement new / call the C++ dtor.

Usage then works like this:

void someDFunc()
{
   ComplexStruct myStruct(defaultCtor); // constructed using C++ 
default ctor
   myStruct.SomeMethodOfComplexStruct(); // call C++ implemented 
method
   // destructed using C++ dtor
}

So far I haven't found a situation where I couldn't make it work 
the way I wanted.  Its just some work to write the D headers for 
the C++ classes and vise versa, because you have to duplicate 
everything once more. An automated tool for this would be nice, 
but unfotunately there is currently none.

Kind Regards
Benjamin Thaut


More information about the Digitalmars-d-learn mailing list