17
votes

I have gone through the docs explaining how to call C++ from D explained here: http://dlang.org/cpp_interface.html . There are a however a couple of things that are not quite clear to me.

Taking the example provided on the D website:

#include <iostream>

using namespace std;

class D {
   public:
   virtual int bar(int i, int j, int k)
   {
       cout << "i = " << i << endl;
       cout << "j = " << j << endl;
       cout << "k = " << k << endl;
       return 8;
   }
};

D *getD() {
   D *d = new D();
   return d;
}

The C++ class can then be called from D as shown below:

extern (C++) {
    interface D {
        int bar(int i, int j, int k);
    }

    D getD();
}

void main() {
   D d = getD();
   d.bar(9,10,11);
}

What is not quite clear to me is how the C++ object gets deleted. Does the D garbage collector call delete on the C++ object or do we need to provide a "deleter" function which deletes the object and call it manually from D? It seems to me that if I add a destructor to the C++ class, it is never called. Also I noticed that the C++ class must declare the member functions in the exact same order as they are declared in the D interface (for example if I add a destructor before the bar() method, the C++ object cannot be called from D, but if the destructor is declared after the bar() method, everything works fine).

Also if the D interface is defined as:

extern(C++){
   interface D{
       int bar();
       int foo();
   }
}

And the correspondind C++ class is given by:

class D{
public:
   virtual int bar(){};
   virtual int foo(){};

};

How can you guarantee that the C++ virtual methods vtbl will be created in the same order as the methods declared in the D interface. To me there is no guarantee for this. In other words, how can we be sure that D::bar() will be in the first position in the vtbl? Is this not implementation/compiler dependant?

4

4 Answers

7
votes

I wouldn't expect D's garbage collector to know how to free a C++ object. That would imply (at least) that the D runtime:

  1. make assumptions about the C++ runtime, i.e., how to delete a C++ object
  2. that the object is no longer needed by other C++ code

I'm sure you'll have to provide another C++ function that calls the object passed to it. In fact, many C++ libraries (even when also being used from C++), have this same pattern in cases where the constructor is called from inside the library. Even in straight C, it's usually a bad idea to allocate memory in one dll/exe, and free it in another. This can break badly if the two binaries don't share the same runtime library.

4
votes

The specific way this is implemented is the D object simply has a C++ compatible vtable. So only virtual functions on it work, and since the table is laid out by index, they must appear in the same order.

D has no idea about C++ constructors, destructors, or any other specialish method, but if they are virtual, it can throw off the vtable.

I wrote a quick little program called dtoh pending review right now that can help auto-generate C++ headers from D source, to keep this simple. It isn't finished yet, but it might be helpful anyway: https://github.com/adamdruppe/tools/blob/7d077b26d991dd5705e834900f66bea737a233b2/dtoh.d

First compile it, dmd dtoh.d, then make the JSON from your D file: dmd -X yourfile.d, then run dtoh yourfile.json and it should spit out a usable yourfile.h. But like I said, it isn't finished yet and is still waiting on review for the overall design, so it might suck horribly. You can always do what you're doing now though and do it yourself.

Anyway, the object as seen in D is just like a Class* in C++. You always pass it around through the pointer, so there's no construction or copy construction ever done.

D and C++ also don't understand each other's memory allocation systems. The rule I follow is anything your library creates, your library should be able to destroy. So if your C++ program newed it, be sure it is deleted in C++ as well. Any object you create in D for passing to C++ should also be destroyed by D... and you might want to do it manually. If your C++ function keeps a reference to the D object, but there isn't one in D, it might get garbage collected! So you'll either want to make sure there's always a living reference in D for the lifetime of the object, or create an destroy it yourself with functions like malloc and free.

I don't like having the caller use even generic free(), since the version won't necessarily match. I say always provide a method from your library to free things. Even if its implementation is just free(ptr);, giving your own function will make it explicit that it should be used, and give you protection against such mismatches.

2
votes

You need to add a method in your D class which calls the c++ delete operator. Or, you can use a global destroy method.

Also, don't forget that any interfaces to another language must be declared as extern "C" in order to avoid compiler function name mangling.

#include <iostream>

using namespace std;

class D {
   public:
   virtual int bar(int i, int j, int k)
   {
       cout << "i = " << i << endl;
       cout << "j = " << j << endl;
       cout << "k = " << k << endl;
       return 8;
   }

   // option 1
   virtual void destroy()
   {
       delete this;
   }
};

extern "C"
D *getD() {
   D *d = new D();
   return d;
}

// option 2
extern "C"
void killD(void* d) {
   delete d;
   return;
}

Then in your d code you need to create a scope clause that calls the destroy method.

1
votes

Since your question has title "Calling C++ from D", I will assume that you are trying to interface to C++.

Does the D garbage collector call delete on the C++ object or do we need to provide a "deleter" function which deletes the object and call it manually from D?

By the "C++ object" I assume you mean an object allocated using the new operator. D has no idea about C++ objects created with the C++ new operator. Therefore, whenever you need to delete object allocated by C++, you must provide your own code to free the memory.

C++ support in D is very limited, for a good reason. - Full C++ support would mean a full-blown C++ compiler (with C++ preprocessor) should be included in the D compiler. It would make D compiler implementation much more difficult.

Also I noticed that the C++ class must declare the member functions in the exact same order as they are declared in the D interface (for example if I add a destructor before the bar() method, the C++ object cannot be called from D, but if the destructor is declared after the bar() method, everything works fine).

In this particular case, I believe you first write the C++ class, keeping in mind it will be used in a D project, and then you write the D interface. The D interface should closely match methods in the C++ class because D compiler will generate a C++-compatible virtual table.

C++ support will improve but D will highly unlikely have full C++ support. Work has been done to support C++ namespaces (an improvement requested by the D community).

Since D fully supports C, the best idea is to "flatten" complex C++ code to C in way similar to how it is done in the article "Mixed C and C++". Long ago I used similar approach to call C++ methods from Delphi.