Thread overview
A question about C++ interop
Mar 27, 2020
YD
Mar 28, 2020
Jacob Carlborg
Mar 28, 2020
YD
Mar 28, 2020
YD
Mar 29, 2020
evilrat
Mar 29, 2020
YD
Mar 29, 2020
YD
Mar 29, 2020
kinke
Mar 29, 2020
YD
March 27, 2020
Hi, I have a C++ header file which looks like

    class A {
    public:
        static A *create();
        virtual int f() const = 0;
    };

And there is a C++ library file which provides the implementation, so that if I write a C++ program and call

    auto *p = A::create();
    std::cout << p->f() << '\n';

It will work.

Now I want to interface to this C++ library through D, and I wrote

    module test;

    import std.stdio;

    extern(C++) {
        class A {
                static A *create();
                abstract int f() const;
        }
    }

    void main() {
        auto p = A.create();
        writeln(p.f());
    }

This program will compile and link, but it core dumps at the call to f().

If I wrap up the C++ interface into a C interface (using a void *), and interface to the wrapped-up C library through D, it will work fine.

So what am I doing wrong here? Thanks!
March 28, 2020
On 2020-03-27 20:17, YD wrote:
> Hi, I have a C++ header file which looks like
> 
>      class A {
>      public:
>          static A *create();
>          virtual int f() const = 0;
>      };
> 
> And there is a C++ library file which provides the implementation, so that if I write a C++ program and call
> 
>      auto *p = A::create();
>      std::cout << p->f() << '\n';
> 
> It will work.
> 
> Now I want to interface to this C++ library through D, and I wrote
> 
>      module test;
> 
>      import std.stdio;
> 
>      extern(C++) {
>          class A {
>                  static A *create();
>                  abstract int f() const;
>          }
>      }
> 
>      void main() {
>          auto p = A.create();
>          writeln(p.f());
>      }
> 
> This program will compile and link, but it core dumps at the call to f().
> 
> If I wrap up the C++ interface into a C interface (using a void *), and interface to the wrapped-up C library through D, it will work fine.
> 
> So what am I doing wrong here? Thanks!

Classes in D are always passed by reference. Try dropping the pointer in the `create` method:

static A create();

-- 
/Jacob Carlborg
March 28, 2020
On Saturday, 28 March 2020 at 07:33:38 UTC, Jacob Carlborg wrote:
> On 2020-03-27 20:17, YD wrote:
>> [...]
>
> Classes in D are always passed by reference. Try dropping the pointer in the `create` method:
>
> static A create();

Thanks! I got it to work for now.
March 28, 2020
On Saturday, 28 March 2020 at 07:33:38 UTC, Jacob Carlborg wrote:
> On 2020-03-27 20:17, YD wrote:
>> Hi, I have a C++ header file which looks like
>> 
>>      class A {
>>      public:
>>          static A *create();
>>          virtual int f() const = 0;
>>      };
>> 
>> And there is a C++ library file which provides the implementation, so that if I write a C++ program and call
>> 
>>      auto *p = A::create();
>>      std::cout << p->f() << '\n';
>> 
>> It will work.
>> 
>> Now I want to interface to this C++ library through D, and I wrote
>> 
>>      module test;
>> 
>>      import std.stdio;
>> 
>>      extern(C++) {
>>          class A {
>>                  static A *create();
>>                  abstract int f() const;
>>          }
>>      }
>> 
>>      void main() {
>>          auto p = A.create();
>>          writeln(p.f());
>>      }
>> 
>> This program will compile and link, but it core dumps at the call to f().
>> 
>> If I wrap up the C++ interface into a C interface (using a void *), and interface to the wrapped-up C library through D, it will work fine.
>> 
>> So what am I doing wrong here? Thanks!
>
> Classes in D are always passed by reference. Try dropping the pointer in the `create` method:
>
> static A create();

Hi, now I have a further question: when the C++ class A actually has a method that looks like

    virtual void get_info(std::string &s) const = 0;

in order to preserve the virtual function table layout (I found that if I omit this function completely in the D declaration, and try to use a virtual member function originally defined in C++ after this function, the result is core dump), even if I don't use this function, in the D file I have to put in line like this

    abstract void get_info(basic_string!(char) s) const;

When I try this on Linux (Ubuntu 18.04), the compiler (both dmd and ldc2) will complain about "std::__cxx11::basic_string is not yet supported", but otherwise the code compiles and links correctly, and can run without problem.

But when I try this on Windows 10, dmd will simply refuse to compile it, saying "windows c++ runtime not supported", and ldc2 will allow the compilation but fail at the linking stage, saying something like

    "error LNK2019: unresolved external symbol __D4core6stdcpp9allocator33_Allocate_manually_vector_alignedFNixkZPv referenced in function __D4core6stdcpp9allocator__TQnTaZQs8allocateMFNikZPa"

So does this mean that there is no way I can interface to this C++ API in Windows? Thanks.



March 29, 2020
On Saturday, 28 March 2020 at 19:14:38 UTC, YD wrote:
>
> Hi, now I have a further question: when the C++ class A actually has a method that looks like
>
>     virtual void get_info(std::string &s) const = 0;
>
> in order to preserve the virtual function table layout (I found that if I omit this function completely in the D declaration, and try to use a virtual member function originally defined in C++ after this function, the result is core dump), even if I don't use this function, in the D file I have to put in line like this
>
>     abstract void get_info(basic_string!(char) s) const;

Yes, ABI implies that. If you don't use it at all you can just put a dummy entry like in the following code, otherwise it tries to call wrong method.

In many cases it will just crash, but this also could introduce very hard to find bugs when vtable is changed due to class changes, and the method that you were trying to call landed in vtable on another method with same signature - Imagine having API like Database class and instead of dump database it will drop all tables... phew.

 ...
 // other methods
 void vtable_dummy01() {} // or abstract if it's abstract
 // other methods
 ...


> When I try this on Linux (Ubuntu 18.04), the compiler (both dmd and ldc2) will complain about "std::__cxx11::basic_string is not yet supported", but otherwise the code compiles and links correctly, and can run without problem.

STL bindings is unfinished, and don't expect it to be done any time soon. Tiny fractions of it might be present in Phobos (D standard library), but this work was done by few people who really needed that feature and it seems they are now too busy to continue that work.

>
> So does this mean that there is no way I can interface to this C++ API in Windows? Thanks.

Same here, STL bindings is not yet finished. If you don't need that method specifically, just replace it with a dummy. Or make your own bindings.
March 29, 2020
On Sunday, 29 March 2020 at 01:50:24 UTC, evilrat wrote:
> ...
>
> Same here, STL bindings is not yet finished. If you don't need that method specifically, just replace it with a dummy. Or make your own bindings.

Thanks, dummy placeholder works. But there is a new problem on Windows, let's say there are two classes in C++:

    class Y {
        ...
    };
    class X {
    public:
	void call(Y const * y) const;
    };

on Windows, the library file (compiled with Visual C++) contains an entry like this:

    16C 00000020 SECT4  notype ()    External     | ?call@X@@QBEXPBVY@@@Z (public: void __thiscall X::call(class Y const *)const )

Then I declare it in D like this:

    extern(C++) {
	class Y {
	    ...
	}
	class X {
	    final void call(const(Y) y) const;
	}
    }

The object file for this D code (compiled with ldc2) will contain an entry like this:

    0A7 00000000 UNDEF  notype       External     | ?call@X@@QBEXQBVY@@@Z (public: void __thiscall X::call(class Y const * const)const )

So there is a subtle difference in the signature, and the linker refuses to resolve the symbol.

I tried "const Y y", "const(Y*) y", and "ref const(Y) y", but none of them manages to match the library file entry.

(This problem somehow does not appear on Linux where the library file is compiled with gcc, though)

So what do I need to declare in the D file for it to match the library entry? Thanks!
March 29, 2020
On Sunday, 29 March 2020 at 15:20:52 UTC, YD wrote:
> On Sunday, 29 March 2020 at 01:50:24 UTC, evilrat wrote:
>> [...]
>
> Thanks, dummy placeholder works. But there is a new problem on Windows, let's say there are two classes in C++:
>
> [...]

Actually I found that if I create a C wrapper like

    extern "C" void X_call(X const *, Y const *);

then it will work. So I have a workaround now. However, out of curiosity, is there a way to do this directly without C wrapper? Thanks.
March 29, 2020
On Sunday, 29 March 2020 at 15:20:52 UTC, YD wrote:
> So what do I need to declare in the D file for it to match the library entry? Thanks!

This is similar to https://issues.dlang.org/show_bug.cgi?id=19260, and can be worked around the same way by messing manually with the mangled name, if you can't adapt the C++ side. The actual problem is that you can't express a mutable reference to a const class object (as opposed to a struct) in D (`const Y` is a const reference to a const Y object).

> (This problem somehow does not appear on Linux where the library file is compiled with gcc, though)

The Itanium C++ mangling doesn't differentiate between:

void foo(const Y *);       // what you have on the C++ side
void foo(const Y * const); // corresponds to D `void foo(const Y)`

March 29, 2020
On Sunday, 29 March 2020 at 17:32:59 UTC, kinke wrote:
> On Sunday, 29 March 2020 at 15:20:52 UTC, YD wrote:
>> So what do I need to declare in the D file for it to match the library entry? Thanks!
>
> This is similar to https://issues.dlang.org/show_bug.cgi?id=19260, and can be worked around the same way by messing manually with the mangled name, if you can't adapt the C++ side. The actual problem is that you can't express a mutable reference to a const class object (as opposed to a struct) in D (`const Y` is a const reference to a const Y object).
>
>> (This problem somehow does not appear on Linux where the library file is compiled with gcc, though)
>
> The Itanium C++ mangling doesn't differentiate between:
>
> void foo(const Y *);       // what you have on the C++ side
> void foo(const Y * const); // corresponds to D `void foo(const Y)`

Thanks! I tried this:

    class X {
        version(Windows) {
            pragma(mangle, X.call.mangleof.replace("QBV","PBV"))
            final void call(const(Y)) const;
        } else {
            final void call(const(Y)) const;
        }
    }

and it worked. Thanks very much again!