www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Interfacing with basic C++ class

reply Riccardo M <asd asd.asd> writes:
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
Sep 28 2022
next sibling parent reply =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Sep 28 2022
parent reply Riccardo M <asd asd.asd> writes:
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
Sep 29 2022
parent =?UTF-8?Q?Ali_=c3=87ehreli?= <acehreli yahoo.com> writes:
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
Sep 29 2022
prev sibling parent reply Ogi <ogion.art gmail.com> writes:
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: ```D 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 * } ```
Sep 29 2022
parent reply Riccardo M <asd asd.asd> writes:
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
Sep 29 2022
parent reply Ogi <ogion.art gmail.com> writes:
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++ //c.h class C { public: int foo() { return 42; } void bar(); }; ``` ```C++ //c.cpp #include "c.h" void C::bar() { /* ... */ } ``` ```C++ //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.
Sep 30 2022
parent Riccardo M <asd asd.asd> writes:
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.
Oct 02 2022