17

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?

BigONotation
  • 4,406
  • 5
  • 43
  • 72

4 Answers4

7

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.

Aaron
  • 594
  • 2
  • 12
4

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.

Adam D. Ruppe
  • 25,382
  • 4
  • 41
  • 60
  • So does it mean that I can add a destructor to the C++ class as long as it is not virtual so that it does not mess with the methods corresponding to the D interface? Another point I don't quite get is how the order of the methods are guaranteed to match. For example if a D interface is defined as follows: interface D{ void foo(); } – BigONotation Jan 03 '14 at 20:00
  • Yes, that should work, adding the dtor. BUT, D won't know anything about it and will never call it. The order of the methods aren't guaranteed to match, that's your responsibility to get right, by making sure that the functions appear in the same order in both D and C++. – Adam D. Ruppe Jan 03 '14 at 20:17
  • I thought that for C++ the order of the virtual methods in the vtbl was compiler dependant. In other words there is no guarantee that the vtbl methods will be in the same order as their declaration in the class definition. Am I missing something? – BigONotation Jan 03 '14 at 20:54
  • I'm not sure about what the standard says, but I've tried this a few times and it works in practice. – Adam D. Ruppe Jan 03 '14 at 21:50
  • It is not part of the standard: http://stackoverflow.com/questions/3674929/c-v-table-part-of-the-language-or-compiler-dependent that's why I am a bit worried my code would silently break if I switch compilers/versions... – BigONotation Jan 04 '14 at 12:59
  • yeah, i guess it could. If you want max compatibility I would recommend something with a well-defined ABI, like C opaque pointers plus functions or COM on windows. – Adam D. Ruppe Jan 04 '14 at 15:15
2

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.

Noishe
  • 1,411
  • 11
  • 14
  • 1
    You don't necessarily need extern C, since D has (partial but good enough for ths) extern(C++) support to understand the mangling. – Adam D. Ruppe Jan 03 '14 at 16:59
  • So your code will work until you use a new c++ compiler that D has never seen before. Or your c++ compiler changes it's mangling scheme on an update. – Noishe Jan 04 '14 at 01:53
  • yeah, those could be problems (though they'd break C++ code in the wild too, but hey it has happened before). I'd agree that maximum compatibility would be to use C interfaces. – Adam D. Ruppe Jan 04 '14 at 02:20
1

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.

DejanLekic
  • 18,787
  • 4
  • 46
  • 77
  • eh, I don't agree with the full-blown C++ compiler thing, at least not for most things. That'd be true of reading a C++ header file, but calling the function doesn't need all that much. It is mostly just calling convention (got it), mangling (have most of it), and data layouts (tricky w/o the header, but can be done - really no different than using C structs). Even C++ templates mostly comes down to mangling, since loading a c++ shared lib doesn't actually instantiate the template anyway, it just looks it up like any other function. I guess this doesn't matter to the question here tho. – Adam D. Ruppe Jan 03 '14 at 16:57
  • I would not simplify "C++ support" to calling a C++ function... :) Idk about you, but I totally agree with Walter on this. (*Being 100% compatible with C++ means more or less adding a fully functional C++ compiler front end to D. Anecdotal evidence suggests that writing such is a minimum of a 10 man-year project, essentially making a D compiler with such capability unimplementable.*) – DejanLekic Jan 03 '14 at 17:26
  • eh, I wouldn't simplify it that much either, but D can get pretty close by combining that with its own ability to do destructors. Certainly not 100% C++ support, but enough to make good use of C++ shared libraries at least then. – Adam D. Ruppe Jan 03 '14 at 20:19
  • That is why I think current approach is very good - pragmatic like the D language itself. I can't wait for the C++ namespace support. That is the only thing I really need at the moment. – DejanLekic Jan 03 '14 at 20:46