Thread overview
Interfacing with basic C++ class
Sep 28, 2022
Riccardo M
Sep 28, 2022
Ali Çehreli
Sep 29, 2022
Riccardo M
Sep 29, 2022
Ali Çehreli
Sep 29, 2022
Ogi
Sep 29, 2022
Riccardo M
Sep 30, 2022
Ogi
Oct 02, 2022
Riccardo M
September 28, 2022

I think I am stuck in the easiest of the issues and yet it seems I cannot get around this.
I have a C++ file:

class MyClass {
public:
    int field;
    MyClass(int a) : field(a) {}

    int add(int asd) {
        return asd + 1;
    }
};

MyClass* instantiate(int asd) {
    return new MyClass(asd);
}

and a D file:

extern(C++) {
	class MyClass {
		public:	
			int field;
			@disable this();
			final int add(int asd);
		}

	MyClass instantiate(int asd);
}

void main()
{
	import std : writeln;
	auto myclass = instantiate(100);
	writeln(myclass.field);
}

This is a very simplified version of the dlang official example of interfacing with C++ classes.

When I compile, I can't correctly read 'field'. What I do:

g++ -c cpp.cpp
dmd app.d cpp.o -L-lstdc++
./app

What I get:

0

Instead of the expected 100.

Care to help me understand what am I doing wrong?

Side question: it seems that I need to declare every class method as "final" otherwise I get undefined references during linking. Is this expected behaviour?

Thanks

September 28, 2022
On 9/28/22 12:57, Riccardo M wrote:

> class MyClass {
> public:
>      int field;
>      MyClass(int a) : field(a) {}

Make the following function 'virtual':

>      int add(int asd) {

    virtual int add(int asd) {

I think the C++ class does not get a vptr without a virtual function and apparently D expects this.

>              final int add(int asd);

You don't need that 'final' anymore.

> This is a very simplified version of the dlang official example of
> interfacing with C++ classes.

The example there uses virtual functions. That must be the difference.

> I need to declare every class method as
> "final" otherwise I get  undefined references during linking. Is this
> expected behaviour?

Apparently, not with that added 'virtual'.

Ali


September 29, 2022
On Wednesday, 28 September 2022 at 20:41:13 UTC, Ali Çehreli wrote:
> [...]
>
> Ali

Thank you, that is perfect!

However that begs the following observation: it would be rather hard to link to a C++ library only by means of 'extern (C++)' if one should slightly rearrange C++ code as well. This sounds like it is rather obvious to the community, actually, but I am a recent addition to D language :)

Do you know that this is documented somewhere? The examples in the official docs are rather limited, maybe studying a github with previous work might help.

Cheers
September 29, 2022

On Wednesday, 28 September 2022 at 19:57:10 UTC, Riccardo M wrote:

>

I think I am stuck in the easiest of the issues and yet it seems I cannot get around this.
I have a C++ file:

class MyClass {
public:
    int field;
    MyClass(int a) : field(a) {}

    int add(int asd) {
        return asd + 1;
    }
};

Ali is correct. However, you don’t have to change anything on the C++ side, just map C++ class to D struct:

extern(C++, class) {
    struct MyClass {
    public:	
        int field;
        @disable this();
        int add(int asd); //struct fields are always `final`
    }

    MyClass* instantiate(int asd); //mind the *
}
September 29, 2022
On 9/29/22 01:28, Riccardo M wrote:

> if one should
> slightly rearrange C++ code as well.

Right. Additionally, the order of members must match (I am pretty sure, which means I am not :p).

> I am a recent addition to D
> language :)

Welcome! :)

> Do you know that this is documented somewhere? The examples in the
> official docs are rather limited

Manu Evans said at DConf 2022 that the documentation is very lacking compared to actual capability. People who know should update the docs.

Ali


September 29, 2022

On Thursday, 29 September 2022 at 11:13:15 UTC, Ogi wrote:

>

Ali is correct. However, you don’t have to change anything on the C++ side, just map C++ class to D struct:
[...]

Thanks, this works perfectly.
Now i see that I can even instantiate directly from D, calling the default ctor, calling the custom ctor or even overriding the custom ctor in D (the fact that ctors could be called, in contrast with docs, was in fact hinted at DConf 2022 as suggested by Ali).

So it turns out that D's structs are a much better match for C++'s classes in this case. But why is this? Can you elaborate? It must have to do with the fact that D structs are passed by value?

Now this D code runs as expected:

// D
extern(C++, class) {
	struct MyClass {
		public:	
			int field;
			this(int a);
			int add(int asd);
	}

	MyClass* instantiate(int asd);
}

void main()
{
	import std : writeln;

	auto myclass = instantiate(100); //actually, instantiation through C++ is not required any longer
	assert(myclass.field == 100);

	auto myclass2 = new MyClass;
	assert(myclass2.field == 0);

	auto myclass3 = new MyClass(50);
	assert(myclass3.field == 50);

	assert(myclass3.add(40) == 90);
}

However the 'add' function only links correctly if C++ has the function body defined outside of its class.

// C++
int MyClass::add(int asd) {
    return field + asd;
}

If the function is defined inside its class, it is an undefined reference at link time. Once again I ask for clarifications and workarounds, if possible.

Thanks

September 30, 2022

On Thursday, 29 September 2022 at 12:49:06 UTC, Riccardo M wrote:

>

On Thursday, 29 September 2022 at 11:13:15 UTC, Ogi wrote:
So it turns out that D's structs are a much better match for C++'s classes in this case. But why is this? Can you elaborate? It must have to do with the fact that D structs are passed by value?

In C++, class and struct are basically the same thing (AFAIK the only difference is that struct members are public by default). In D, they are two very different things: classes are reference types — structs are value types, classes support inheritance — structs only support alias this, and so on.

When interfacing to C++, disregard the keyword and look at the implementation instead. If all its member functions are non-virtual, map it to struct. Otherwise map it to class. If it defines at least one pure virtual member function, map it to abstract class. If all its member functions are either pure virtual or non-virtual and it contains no fields, map it to interface. Sounds complicated? Well, that’s because C++ is complicated.

>

However the 'add' function only links correctly if C++ has the function body defined outside of its class.

// C++
int MyClass::add(int asd) {
    return field + asd;
}

If the function is defined inside its class, it is an undefined reference at link time. Once again I ask for clarifications and workarounds, if possible.

In C++, member functions defined inside its class are called inline member functions. In contrast to normal functions which must be defined once and only once in your program, inline functions must be defined in every translation unit that uses them. Let’s replicate your linking error in C++:

//c.h
class C {
public:
    int foo() {
        return 42;
    }
    void bar();
};
//c.cpp
#include "c.h"

void C::bar() { /* ... */ }
//main.cpp
#include <stdio.h>

//Let’s see what happens if we forget `C::foo` definition:
class C {
public:
    int foo();
    void bar();
};

int main() {
    auto c = new C();
    printf("%d\n", c->foo());
    c->bar();
    return 0;
}
$ clang++ -c c.cpp
$ clang++ -c main.cpp
$ clang++ main.o c.o
main.o : error LNK2019: unresolved external symbol "public: int __cdecl C::foo(void)" (?foo@C@@QEAAHXZ) referenced in function main
a.exe : fatal error LNK1120: 1 unresolved externals
clang++: error: linker command failed with exit code 1120 (use -v to see invocation)

Copying C::foo definition to main.cpp will fix this. Of course, we could just include c.h.

Same goes for D. MyClass.add must be defined in your D module.

October 02, 2022

On Friday, 30 September 2022 at 22:56:06 UTC, Ogi wrote:

>

On Thursday, 29 September 2022 at 12:49:06 UTC, Riccardo M wrote:
When interfacing to C++, disregard the keyword and look at the implementation instead. If all its member functions are non-virtual, map it to struct. Otherwise map it to class. If it defines at least one pure virtual member function, map it to abstract class. If all its member functions are either pure virtual or non-virtual and it contains no fields, map it to interface. Sounds complicated? Well, that’s because C++ is complicated.

Ok, in layman terms, is it correct to say that I should match the underlining structure of the object (e.g in terms of vtbl) so that C++ side and D side can work with each other correctly?

>

In C++, member functions defined inside its class are called inline member functions. In contrast to normal functions which must be defined once and only once in your program, inline functions must be defined in every translation unit that uses them. Let’s replicate your linking error in C++:

Well, I didn't know the implications of inlining member functions: basically when a member function is inlined, it has no linkage so I am pretty much done with using D in such case. While in C++ you can import the header and call the member function anyway.
The only solution would be reimplementing the offending function on D side.