www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - How to pass a class by (const) reference to C++

reply Jan <Jan Krassnigg.de> writes:
In D I have an extern(C++) class:

```cpp
extern(C++) class A
{
     ~this();

     // other stuff
}
```

An a function that takes A by const reference:

```cpp
void CppFunc(const A& arg);
```

But how do I bind this in D ?

```cpp
extern(C++) void CppFunc(A arg); // tries to pass as 'A*'
extern(C++) void CppFunc(ref const(A) arg); // tries to pass as 
'A const * const &'
```

I have solved similar problems with other classes by declaring 
them as struct in D, but that only works for classes that have no 
virtual functions. I now have a class where I do need to use a 
class on the D side, and now I have problems passing these 
objects to C++.
Dec 12 2021
parent reply evilrat <evilrat666 gmail.com> writes:
On Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote:
 In D I have an extern(C++) class:

 ```cpp
 extern(C++) class A
 {
     ~this();

     // other stuff
 }
 ```

 An a function that takes A by const reference:

 ```cpp
 void CppFunc(const A& arg);
 ```

 But how do I bind this in D ?

 ```cpp
 extern(C++) void CppFunc(A arg); // tries to pass as 'A*'
 extern(C++) void CppFunc(ref const(A) arg); // tries to pass as 
 'A const * const &'
 ```

 I have solved similar problems with other classes by declaring 
 them as struct in D, but that only works for classes that have 
 no virtual functions. I now have a class where I do need to use 
 a class on the D side, and now I have problems passing these 
 objects to C++.
You can tell compiler to mangle it as struct/class using extern(C++, struct). ```d extern (C++, struct) // will use struct mangling even though it's a class class SomeDClass { ... } ```
Dec 12 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Monday, 13 December 2021 at 07:48:34 UTC, evilrat wrote:
 On Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote:
 In D I have an extern(C++) class:

 ```cpp
 extern(C++) class A
 {
     ~this();

     // other stuff
 }
 ```

 An a function that takes A by const reference:

 ```cpp
 void CppFunc(const A& arg);
 ```

 But how do I bind this in D ?

 ```cpp
 extern(C++) void CppFunc(A arg); // tries to pass as 'A*'
 extern(C++) void CppFunc(ref const(A) arg); // tries to pass 
 as 'A const * const &'
 ```

 I have solved similar problems with other classes by declaring 
 them as struct in D, but that only works for classes that have 
 no virtual functions. I now have a class where I do need to 
 use a class on the D side, and now I have problems passing 
 these objects to C++.
You can tell compiler to mangle it as struct/class using extern(C++, struct). ```d extern (C++, struct) // will use struct mangling even though it's a class class SomeDClass { ... } ```
I tried this, but it doesn't work, because it seems D decides how to pass the object by whether it is a class or struct in D, not in C++. So even with the change as you suggested it, it still tries to pass the object as a pointer to begin with.
Dec 13 2021
parent reply Tejas <notrealemail gmail.com> writes:
On Monday, 13 December 2021 at 09:21:26 UTC, Jan wrote:
 On Monday, 13 December 2021 at 07:48:34 UTC, evilrat wrote:
 On Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote:
 In D I have an extern(C++) class:

 ```cpp
 extern(C++) class A
 {
     ~this();

     // other stuff
 }
 ```

 An a function that takes A by const reference:

 ```cpp
 void CppFunc(const A& arg);
 ```

 But how do I bind this in D ?

 ```cpp
 extern(C++) void CppFunc(A arg); // tries to pass as 'A*'
 extern(C++) void CppFunc(ref const(A) arg); // tries to pass 
 as 'A const * const &'
 ```

 I have solved similar problems with other classes by 
 declaring them as struct in D, but that only works for 
 classes that have no virtual functions. I now have a class 
 where I do need to use a class on the D side, and now I have 
 problems passing these objects to C++.
You can tell compiler to mangle it as struct/class using extern(C++, struct). ```d extern (C++, struct) // will use struct mangling even though it's a class class SomeDClass { ... } ```
I tried this, but it doesn't work, because it seems D decides how to pass the object by whether it is a class or struct in D, not in C++. So even with the change as you suggested it, it still tries to pass the object as a pointer to begin with.
You'll have to use something called a [shim](https://en.wikipedia.org/wiki/Shim_(computing)), it seems. For example: `main.d` : ```d extern(C++) class A{} extern(C++) void cppFunc_shim(A arg); void main(){ A a = new A(); cppFunc_shim(a); } ``` `cppShim.cpp` : ```c++ class A{}; extern void cppFunc(A const &arg); void cppFunc_shim(A *param){ const A forwardingVar = A(*param); cppFunc(forwardingVar); } ``` `cppFunc.cpp` : ```c++ #include "iostream" class A{}; void cppFunc(A const &arg){ //std::cout << arg << std::endl; std::cout << "Called cppFunc :D" << std::endl; } ``` Then pass the following on the command line(assuming all files are in the same directory): `ldmd2 main.d cppFunc.o cppShim.o -L-lstdc++` That's what it took to make it work for me, dunno if more convenient methods exist. Hope it helps :D
Dec 13 2021
parent reply evilrat <evilrat666 gmail.com> writes:
On Monday, 13 December 2021 at 11:13:12 UTC, Tejas wrote:
 On Monday, 13 December 2021 at 09:21:26 UTC, Jan wrote:
 On Monday, 13 December 2021 at 07:48:34 UTC, evilrat wrote:
 On Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote:
 In D I have an extern(C++) class:

 ```cpp
 extern(C++) class A
 {
     ~this();

     // other stuff
 }
 ```

 An a function that takes A by const reference:

 ```cpp
 void CppFunc(const A& arg);
 ```

 But how do I bind this in D ?

 ```cpp
 extern(C++) void CppFunc(A arg); // tries to pass as 'A*'
 extern(C++) void CppFunc(ref const(A) arg); // tries to pass 
 as 'A const * const &'
 ```

 I have solved similar problems with other classes by 
 declaring them as struct in D, but that only works for 
 classes that have no virtual functions. I now have a class 
 where I do need to use a class on the D side, and now I have 
 problems passing these objects to C++.
You can tell compiler to mangle it as struct/class using extern(C++, struct). ```d extern (C++, struct) // will use struct mangling even though it's a class class SomeDClass { ... } ```
I tried this, but it doesn't work, because it seems D decides how to pass the object by whether it is a class or struct in D, not in C++. So even with the change as you suggested it, it still tries to pass the object as a pointer to begin with.
You'll have to use something called a [shim](https://en.wikipedia.org/wiki/Shim_(computing)), it seems. For example: `main.d` : ```d extern(C++) class A{} extern(C++) void cppFunc_shim(A arg); void main(){ A a = new A(); cppFunc_shim(a); } ``` `cppShim.cpp` : ```c++ class A{}; extern void cppFunc(A const &arg); void cppFunc_shim(A *param){ const A forwardingVar = A(*param); cppFunc(forwardingVar); } ``` `cppFunc.cpp` : ```c++ #include "iostream" class A{}; void cppFunc(A const &arg){ //std::cout << arg << std::endl; std::cout << "Called cppFunc :D" << std::endl; } ``` Then pass the following on the command line(assuming all files are in the same directory): `ldmd2 main.d cppFunc.o cppShim.o -L-lstdc++` That's what it took to make it work for me, dunno if more convenient methods exist. Hope it helps :D
Yeah but it sucks to have making C++ wrapper just for this. I think either pragma mangle to hammer it in place or helper dummy struct with class layout that mimics this shim logic is a better solution in such cases. Literally anything but building C++ code twice for a project.
Dec 13 2021
next sibling parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 13 December 2021 at 12:08:30 UTC, evilrat wrote:
 Yeah but it sucks to have making C++ wrapper just for this. I 
 think either pragma mangle to hammer it in place or helper 
 dummy struct with class layout that mimics this shim logic is a 
 better solution in such cases.
 Literally anything but building C++ code twice for a project.
Does something like this work? ``` class _A {} struct A {} extern(C++) void CppFunc(ref const(A) arg); void func(_A a){ CppFunc(*cast(A*)a); } ```
Dec 13 2021
next sibling parent Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 13 December 2021 at 12:16:03 UTC, Ola Fosheim Grøstad 
wrote:
 class _A {}
 struct A {}
With ```extern(C++)``` on these…
Dec 13 2021
prev sibling next sibling parent reply evilrat <evilrat666 gmail.com> writes:
On Monday, 13 December 2021 at 12:16:03 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 13 December 2021 at 12:08:30 UTC, evilrat wrote:
 Yeah but it sucks to have making C++ wrapper just for this. I 
 think either pragma mangle to hammer it in place or helper 
 dummy struct with class layout that mimics this shim logic is 
 a better solution in such cases.
 Literally anything but building C++ code twice for a project.
Does something like this work? ``` class _A {} struct A {} extern(C++) void CppFunc(ref const(A) arg); void func(_A a){ CppFunc(*cast(A*)a); } ```
Only if this struct matches class memory layout, the only potential problem is ctor on C++ side. Also C++ class will likely be NOT zero initialized and have byte gaps due to alignment, this can mess up many things including (default) equality operators and such. That example is still looks very conspicuous because it is very likely does nothing on the caller side in C++ as it passes a copy. Such things may indicate that the library author have no idea what he is doing, and it works on occasion. All this will require extra care or it will explode in your face, and is quite hard to debug without such low-level knowledge of details.
Dec 13 2021
parent reply Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= <ola.fosheim.grostad gmail.com> writes:
On Monday, 13 December 2021 at 12:51:17 UTC, evilrat wrote:
 That example is still looks very conspicuous because it is very 
 likely does nothing on the caller side in C++ as it passes a 
 copy.
Yes, I wouldn't want to use it, maybe manual mangling is better, but still painful. ```const A&``` is so common in C++ API's that it really should be supported out-of-the-box. All it takes is adding a deref-type-constructor to the D language spec, e.g. ```ref const( deref(A))```
Dec 13 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Monday, 13 December 2021 at 13:02:50 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 13 December 2021 at 12:51:17 UTC, evilrat wrote:
 That example is still looks very conspicuous because it is 
 very likely does nothing on the caller side in C++ as it 
 passes a copy.
Yes, I wouldn't want to use it, maybe manual mangling is better, but still painful. ```const A&``` is so common in C++ API's that it really should be supported out-of-the-box. All it takes is adding a deref-type-constructor to the D language spec, e.g. ```ref const( deref(A))```
I fully agree. This pattern is so common in C++, that I am surprised D doesn't have a way to do this already. The whole idea of linking against C++ is to interop easily, with little friction and high performance. Needing to build any shims or redesign the C++ side is very much contrary to this goal. Does anyone know whether such issues have been discussed before? I can't imagine I'm the first one to run into this.
Dec 13 2021
parent reply Tim <tim.dlang t-online.de> writes:
On Monday, 13 December 2021 at 15:21:19 UTC, Jan wrote:
 On Monday, 13 December 2021 at 13:02:50 UTC, Ola Fosheim 
 Grøstad wrote:
 Yes, I wouldn't want to use it, maybe manual mangling is 
 better, but still painful. ```const A&``` is so common in C++ 
 API's that it really should be supported out-of-the-box.  All 
 it takes is adding a deref-type-constructor to the D language 
 spec, e.g. ```ref const( deref(A))```
I fully agree. This pattern is so common in C++, that I am surprised D doesn't have a way to do this already. The whole idea of linking against C++ is to interop easily, with little friction and high performance. Needing to build any shims or redesign the C++ side is very much contrary to this goal. Does anyone know whether such issues have been discussed before? I can't imagine I'm the first one to run into this.
A similar issue about tail const classes has already been discussed: https://digitalmars.com/d/archives/digitalmars/D/const_Class_is_mangled_as_Class_const_const_299139.html I made a pull request, which changes the mangling to tail const for classes passed directly as parameter or return type, but now think this would break too much code: https://github.com/dlang/dmd/pull/13369 The proposal to add a deref-type-constructor, would also allow to have tail const classes.
Dec 13 2021
parent Jan <Jan Krassnigg.de> writes:
On Monday, 13 December 2021 at 16:29:12 UTC, Tim wrote:
 I made a pull request, which changes the mangling to tail const 
 for classes passed directly as parameter or return type, but 
 now think this would break too much code: 
 https://github.com/dlang/dmd/pull/13369
 The proposal to add a deref-type-constructor, would also allow 
 to have tail const classes.
A 'deref' keyword sounds interesting. I'm not an expert on compilers, but thinking about this a bit more, to me it looks like the fundamental problem is, that D tries to apply its class pointer/reference semantics to C++, even though it could do this differently. 'Deref' would only solve one (common) issue. However, in C++ it is also very common to treat classes as value types. Maybe one could give such a hint to the D compiler instead. If I have this C++ code: ```cpp class A { ... }; void AsValue(A value); void AsPtr(A* value); void AsConstPtr(const A* value); void AsRef(A& value); void AsConstRef(const A& value); ``` Afaik today I can really only bind to functions of the form 'AsPtr' and 'AsConstPtr'. And if I declare A in D as a struct, I could also bind to all the others, but as mentioned above that's not always possible. How about in D I could declare that A should be used like a value type to pass it to a function, using the 'struct' keyword: ```cpp extern(C++) class A { ... } extern(C++) void AsValue(struct A value); extern(C++) void AsPtr(A value); // could stay as previously, OR extern(C++) void AsPtr(struct A* value); // same as above extern(C++) void AsConstPtr(const(A) value); extern(C++) void AsConstPtr(const(struct A*) value); // same as above extern(C++) void AsRef(ref struct A value); extern(C++) void AsConstRef(const(ref struct A) value); // same as above ``` So here the 'struct' keyword would tell the compiler to treat A like a value type, just as in C++ and thus apply pointer, const and reference semantics like in C++. Additionally, if a pure 'struct A' is encountered, the compiler would need to create a copy of the object on the stack, just as it would do for structs, to pass it to C++ (which might modify the temporary). I guess this would be trickier to implement but then you would be able to pass classes to C++ under all circumstances. The added benefit would be, that this shouldn't change existing behavior and thus not break anything. Unfortunately I have neither the time nor expertise to change DMD myself.
Dec 13 2021
prev sibling parent reply Jan <Jan Krassnigg.de> writes:
On Monday, 13 December 2021 at 12:16:03 UTC, Ola Fosheim Grøstad 
wrote:
 On Monday, 13 December 2021 at 12:08:30 UTC, evilrat wrote:
 Yeah but it sucks to have making C++ wrapper just for this. I 
 think either pragma mangle to hammer it in place or helper 
 dummy struct with class layout that mimics this shim logic is 
 a better solution in such cases.
 Literally anything but building C++ code twice for a project.
Does something like this work? ``` class _A {} struct A {} extern(C++) void CppFunc(ref const(A) arg); void func(_A a){ CppFunc(*cast(A*)a); } ```
Unfortunately no. Maybe the cast would even make it work, but I can't have "A" and "_A" in D as separate types, because the class is supposed to link to C++ functions and thus renaming it from A to _A breaks that. On the other hand I can't give the struct another name either, because that's how it is linked to in "CppFunc".
Dec 13 2021
parent Tim <tim.dlang t-online.de> writes:
On Monday, 13 December 2021 at 21:17:49 UTC, Jan wrote:
 Unfortunately no. Maybe the cast would even make it work, but I 
 can't have "A" and "_A" in D as separate types, because the 
 class is supposed to link to C++ functions and thus renaming it 
 from A to _A breaks that. On the other hand I can't give the 
 struct another name either, because that's how it is linked to 
 in "CppFunc".
The easiest workaround is probably to set the mangling manually: pragma(mangle, "_Z7CppFuncRK1A") extern(C++) void CppFunc(const A arg); This mangling is for the Itanium ABI, which is used by Linux and other operating systems. It needs to be different for Windows.
Dec 13 2021
prev sibling parent reply Tejas <notrealemail gmail.com> writes:
On Monday, 13 December 2021 at 12:08:30 UTC, evilrat wrote:
 On Monday, 13 December 2021 at 11:13:12 UTC, Tejas wrote:
 On Monday, 13 December 2021 at 09:21:26 UTC, Jan wrote:
 [...]
You'll have to use something called a [shim](https://en.wikipedia.org/wiki/Shim_(computing)), it seems. For example: `main.d` : ```d extern(C++) class A{} extern(C++) void cppFunc_shim(A arg); void main(){ A a = new A(); cppFunc_shim(a); } ``` `cppShim.cpp` : ```c++ class A{}; extern void cppFunc(A const &arg); void cppFunc_shim(A *param){ const A forwardingVar = A(*param); cppFunc(forwardingVar); } ``` `cppFunc.cpp` : ```c++ #include "iostream" class A{}; void cppFunc(A const &arg){ //std::cout << arg << std::endl; std::cout << "Called cppFunc :D" << std::endl; } ``` Then pass the following on the command line(assuming all files are in the same directory): `ldmd2 main.d cppFunc.o cppShim.o -L-lstdc++` That's what it took to make it work for me, dunno if more convenient methods exist. Hope it helps :D
Yeah but it sucks to have making C++ wrapper just for this. I think either pragma mangle to hammer it in place or helper dummy struct with class layout that mimics this shim logic is a better solution in such cases. Literally anything but building C++ code twice for a project.
Hey, evilrat, I've seen people make claims that our C++ interop has reached phenomenal levels and that going any further would basically require a C++ compiler ala ImportC++, the issue is just that the docs haven't been updated yet to reflect it. What do you think about this? Is this really true? Because it sure doesn't look that way to me :(
Dec 13 2021
parent reply evilrat <evilrat666 gmail.com> writes:
On Tuesday, 14 December 2021 at 06:21:39 UTC, Tejas wrote:
 Hey, evilrat, I've seen people make claims that our C++ interop 
 has reached phenomenal levels and that going any further would 
 basically require a C++ compiler ala ImportC++, the issue is 
 just that the docs haven't been updated yet to reflect it.

 What do you think about this? Is this really true? Because it 
 sure doesn't look that way to me :(
Unfortunately it is mostly true. There is some missing features like above tail ref and const, there is minor mangling issues that requires pragma mangle sometimes, and other annoying little details. Aside from that there is things that requires actual C++ compiler OR at least part of it to enable certain C++ features - like the example above with pass-by-value for classes certainly requires real C++ copy constructor, some operator overloads, use of SFINAE instead of CTFE, and I'm sure there is more of such nuances. All this not going to happen, D spec clearly states it allows limited C++ interop by relying on linker mechanics rather than being C++ compatible language. It is now abandoned but there was LDC fork called "Calypso", it was a mixed clang/LDC compiler that aimed to achieve seamless D/C++ interop and from what I've heard it was working just fine without all this hiccups as above.
Dec 13 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Tuesday, 14 December 2021 at 07:50:48 UTC, evilrat wrote:
 There is some missing features like above tail ref and const, 
 there is minor mangling issues that requires pragma mangle 
 sometimes, and other annoying little details.
As far as I can tell from my limited experience, the way D approaches interacting with C++ is sound and extremely useful. Link compatibility goes a very long way. Unfortunately it's the "annoying little details" that I immediately bumped into. Things that should be no problem at all with just link compatibility, like incorrectly mangled functions (forgetting about 'const' in return types on Windows) and this very, very annoying issue that I can't pass a class by reference but only by pointer. I can understand that passing a class by value might be out of scope (though it would be possible, since it works for structs just as well), but with pass by reference it's really just D's boneheadedness to try to have its own way. D is a great language, and I want to use it, but as a stand-alone language it just doesn't have the ecosystem that I need (gamedev). Using it as an integrated scripting language is for me the next best thing to benefit from it in a project that is otherwise C++. With link compatibility and auto-generating some bindings, I can get there eventually, but it's still a lot of work with many manually crafted shims. I am currently willing to invest a lot of time to try to solve this, but I won't rewrite a huge C++ code base just because D can't pass classes by reference. Other projects will have even more constraints and even less willingness to invest time into such an undertaking and just scrap the idea early on. C++ link compatibility was a great idea, but if D wants to expand it's user base further, in my opinion it has to polish the interop and be willing to make a few sacrifices on it's end, because that can save everyone else hundreds of hours of tedious work (and maintenance) and thus be the deciding factor for or against using D in a C++ project.
Dec 15 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote:
 Unfortunately it's the "annoying little details" that I 
 immediately bumped into.
Just another example: I just learned that linking against C++ DLLs is quite limited. I ran into the issue that linking in an external variable doesn't work (even though the mangled name that D chooses is correct), because DLLs work differently than static linking does. Someone with more in-depth knowledge told me, that Windows support in D and specifically DLL support is lacking quite a bit. Having *only* link compatibility is totally fine, D currently just doesn't fulfill that promise, especially not on Windows and especially not with DLLs.
Dec 15 2021
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 15/12/2021 11:54 PM, Jan wrote:
 On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote:
 Unfortunately it's the "annoying little details" that I immediately 
 bumped into.
Just another example: I just learned that linking against C++ DLLs is quite limited. I ran into the issue that linking in an external variable doesn't work (even though the mangled name that D chooses is correct), because DLLs work differently than static linking does. Someone with more in-depth knowledge told me, that Windows support in D and specifically DLL support is lacking quite a bit. Having *only* link compatibility is totally fine, D currently just doesn't fulfill that promise, especially not on Windows and especially not with DLLs.
Are you sure that on the shared library side it was marked as exported? If a symbol is not exported, there is no guarantee (nor reason to think) that it will be visible during runtime linking to said shared library/executable. This isn't unique to D, its just how linkers work.
Dec 15 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 11:03:27 UTC, rikki cattermole 
wrote:
 On 15/12/2021 11:54 PM, Jan wrote:
 On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote:
 Unfortunately it's the "annoying little details" that I 
 immediately bumped into.
Just another example: I just learned that linking against C++ DLLs is quite limited. I ran into the issue that linking in an external variable doesn't work (even though the mangled name that D chooses is correct), because DLLs work differently than static linking does. Someone with more in-depth knowledge told me, that Windows support in D and specifically DLL support is lacking quite a bit. Having *only* link compatibility is totally fine, D currently just doesn't fulfill that promise, especially not on Windows and especially not with DLLs.
Are you sure that on the shared library side it was marked as exported? If a symbol is not exported, there is no guarantee (nor reason to think) that it will be visible during runtime linking to said shared library/executable. This isn't unique to D, its just how linkers work.
Yep, checked that. Even checked the .exp file and compared the mangled name there with the mangled name that D tries to use, they are the same. I know someone who worked on improving DLL support for D a while back and he said he had to fix this in D but didn't get that merged into DMD back then (since there were many other things as well) and now he doesn't have time to work on it further :(
Dec 15 2021
parent reply evilrat <evilrat666 gmail.com> writes:
On Wednesday, 15 December 2021 at 12:02:08 UTC, Jan wrote:
 On Wednesday, 15 December 2021 at 11:03:27 UTC, rikki 
 cattermole wrote:
 On 15/12/2021 11:54 PM, Jan wrote:
 On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote:
 Unfortunately it's the "annoying little details" that I 
 immediately bumped into.
Just another example: I just learned that linking against C++ DLLs is quite limited. I ran into the issue that linking in an external variable doesn't work (even though the mangled name that D chooses is correct), because DLLs work differently than static linking does
Are you sure that on the shared library side it was marked as exported? If a symbol is not exported, there is no guarantee (nor reason to think) that it will be visible during runtime linking to said shared library/executable. This isn't unique to D, its just how linkers work.
Yep, checked that. Even checked the .exp file and compared the mangled name there with the mangled name that D tries to use, they are the same.
You probably know this but just in case - unlike C++ in D variables by default have thread local storage, to link with C++ global variable you need to use __gshared storage modifier in D, it is similar to 'shared' variable that unlike 'shared' tells the compiler "I know how to synchronize it myself". ```d module a; struct Foo {} extern(C++) __gshared Foo globalFoo; ```
Dec 15 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 12:36:49 UTC, evilrat wrote:
 You probably know this but just in case - unlike C++ in D 
 variables by default have thread local storage, to link with 
 C++ global variable you need to use __gshared storage modifier 
 in D, it is similar to 'shared' variable that unlike 'shared' 
 tells the compiler "I know how to synchronize it myself".

 ```d
 module a;

 struct Foo {}

 extern(C++)
 __gshared Foo globalFoo;
 ```
Yeah, did that. As I said the mangled name that DMD chose is correct. As I was told linking against functions in DLLs works perfectly, because it is identical to static linking. Linking against global/static variables supposedly differs and that's not handled correctly for DLLs. As I said, I talked to someone who worked on fixing lots of those issues a while ago and he immediately knew about this limitation.
Dec 15 2021
parent reply Tim <tim.dlang t-online.de> writes:
On Wednesday, 15 December 2021 at 15:20:18 UTC, Jan wrote:
 As I was told linking against functions in DLLs works 
 perfectly, because it is identical to static linking. Linking 
 against global/static variables supposedly differs and that's 
 not handled correctly for DLLs. As I said, I talked to someone 
 who worked on fixing lots of those issues a while ago and he 
 immediately knew about this limitation.
I have used global variables from DLLs successfully. DMD now also has a test for C++ DLLs: https://github.com/dlang/dmd/blob/master/test/dshell/extra-files/dll_cxx/testdll.d The variable should be declared like this in C++: __declspec(dllexport) int value; It can then be accessed from D with this: extern (C++) export extern __gshared int value; Do you have a test case for your problem?
Dec 15 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 17:10:51 UTC, Tim wrote:
 Do you have a test case for your problem?
C++ ```cpp class Test { public: __declspec(dllexport) static int var; }; int Test::var = 42; ``` D ```cpp extern(C++, class) struct Test { extern (C++) export extern __gshared int var; } ``` (also tried without the duplicate extern(C++) and without 'export') DLL compiled with Visual Studio 2019. D DLL compiled with a 3 day old nightly build of DMD. DMD says: `error LNK2019: unresolved external symbol "public: static int Test::var" (?var Test 2HA)` I've checked the .exp file of the DLL where the symbol is defined in, and it has the exact same name there. And yes, I correctly link against that DLL in general, other symbols located in that DLL are linked just fine by DMD.
Dec 15 2021
parent reply Tim <tim.dlang t-online.de> writes:
On Wednesday, 15 December 2021 at 19:28:44 UTC, Jan wrote:
 C++
 ```cpp
 class Test
 {
 public:
   __declspec(dllexport) static int var;
 };

 int Test::var = 42;
 ```

 D
 ```cpp
 extern(C++, class) struct Test
 {
   extern (C++) export extern __gshared int var;
 }
 ```

 (also tried without the duplicate extern(C++) and without 
 'export')

 DLL compiled with Visual Studio 2019. D DLL compiled with a 3 
 day old nightly build of DMD.

 DMD says:
 `error LNK2019: unresolved external symbol "public: static int 
 Test::var" (?var Test  2HA)`

 I've checked the .exp file of the DLL where the symbol is 
 defined in, and it has the exact same name there. And yes, I 
 correctly link against that DLL in general, other symbols 
 located in that DLL are linked just fine by DMD.
I can reproduce your problem. It seems to work if var is additionally static: extern(C++, class) struct Test { extern (C++) export extern static __gshared int var; } It's probably a bug, that it does not work without static. The documentation says "__gshared may also be applied to member variables and local variables. In these cases, __gshared is equivalent to static, except that the variable is shared by all threads rather than being thread local." (https://dlang.org/spec/attribute.html#gshared)
Dec 15 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 21:30:47 UTC, Tim wrote:
 It seems to work if var is additionally static:
Ha, it works indeed! ```cpp extern export __gshared static int var; ``` That's a declaration worthy of C++ ! Fewer keywords would be too usable :D Joking aside, I understood the docs such that `__gshared` actually *is* `static` in D, no? Also why the `export` when it's an extern variable?
Dec 15 2021
next sibling parent reply "H. S. Teoh" <hsteoh quickfur.ath.cx> writes:
On Wed, Dec 15, 2021 at 10:01:19PM +0000, Jan via Digitalmars-d-learn wrote:
 On Wednesday, 15 December 2021 at 21:30:47 UTC, Tim wrote:
[...]
 ```cpp
 extern export __gshared static int var;
 ```
[...]
 Joking aside, I understood the docs such that `__gshared` actually
 *is* `static` in D, no?
No. `static` in this case means the lifetime is the lifetime of the module. By default, a static variable would be in TLS and would have the lifetime of the thread that it was instantiated in (and there would be one instance per thread). `__gshared` is needed to coax the compiler into making the variable global in the C/C++ sense, i.e., only 1 instance across all threads. tl;dr: C static == D __gshared. T -- Marketing: the art of convincing people to pay for what they didn't need before which you fail to deliver after.
Dec 15 2021
parent Adam Ruppe <destructionator gmail.com> writes:
On Wednesday, 15 December 2021 at 22:24:42 UTC, H. S. Teoh wrote:
 `__gshared` is needed to coax the compiler into making the 
 variable global in the C/C++ sense, i.e., only 1 instance 
 across all threads.
it is just normally __gshared implies static automatically. it does in like every other context but i guess not in the mangler or whatever.
Dec 15 2021
prev sibling parent reply Tim <tim.dlang t-online.de> writes:
On Wednesday, 15 December 2021 at 22:01:19 UTC, Jan wrote:
 Ha, it works indeed!

 ```cpp
 extern export __gshared static int var;
 ```

 That's a declaration worthy of C++ ! Fewer keywords would be 
 too usable :D

 Joking aside, I understood the docs such that `__gshared` 
 actually *is* `static` in D, no? Also why the `export` when 
 it's an extern variable?
I agree that __gshared should imply static. That's probably a bug in the compiler. Using `extern` without `export` would only work with static linking (on Windows). `export` without `extern` would be exporting the variable for others, like `__declspec(dllexport)` in C++. `export` and `extern` combined imports the variable, like `__declspec(dllimport)`.
Dec 15 2021
parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 22:33:15 UTC, Tim wrote:
 I agree that __gshared should imply static. That's probably a 
 bug in the compiler.

 Using `extern` without `export` would only work with static 
 linking (on Windows). `export` without `extern` would be 
 exporting the variable for others, like `__declspec(dllexport)` 
 in C++. `export` and `extern` combined imports the variable, 
 like `__declspec(dllimport)`.
That's all very helpful, thanks for the explanations guys. Btw. should I report this issue somewhere? Is far as I see this isn't logged yet: <https://issues.dlang.org/buglist.cgi?quicksearch=c%2B%2B%20extern%20static>
Dec 15 2021
parent reply Tim <tim.dlang t-online.de> writes:
On Wednesday, 15 December 2021 at 22:46:01 UTC, Jan wrote:
 Btw. should I report this issue somewhere? Is far as I see this 
 isn't logged yet:
 <https://issues.dlang.org/buglist.cgi?quicksearch=c%2B%2B%20extern%20static>
Yes, it should be reported.
Dec 15 2021
next sibling parent Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 22:50:38 UTC, Tim wrote:
 On Wednesday, 15 December 2021 at 22:46:01 UTC, Jan wrote:
 Btw. should I report this issue somewhere? Is far as I see 
 this isn't logged yet:
 <https://issues.dlang.org/buglist.cgi?quicksearch=c%2B%2B%20extern%20static>
Yes, it should be reported.
https://issues.dlang.org/show_bug.cgi?id=22603
Dec 15 2021
prev sibling parent reply Jan <Jan Krassnigg.de> writes:
On Wednesday, 15 December 2021 at 22:50:38 UTC, Tim wrote:
 On Wednesday, 15 December 2021 at 22:46:01 UTC, Jan wrote:
 Btw. should I report this issue somewhere? Is far as I see 
 this isn't logged yet:
 <https://issues.dlang.org/buglist.cgi?quicksearch=c%2B%2B%20extern%20static>
Yes, it should be reported.
Ok, next problem. Maybe you know the necessary syntax for this one too. C++ ```cpp class Test { public: __declspec(dllexport) static const int const_vars[2]; __declspec(dllexport) static int nonconst_vars[2]; }; const int Test::const_vars[2] = {11, 23}; int Test::nonconst_vars[2] = {12, 24}; ``` D ```cpp extern(C++, class) struct Test { extern export static __gshared const(int)[2] const_vars; extern export static __gshared int[2] nonconst_vars; } ``` I get the nonconst_vars to link correctly, but for the const_vars the mangled name contains one less const than what MSVC generates, so I assume I have to wrap this somehow differently. I tried "const(int[2])" but that didn't make a difference. D wants to link against: `?const_vars Test 2PAHB` which is: `public: static int * const Test::const_vars" ()` MSVC exports: `?const_vars Test 2QBHB` which is: `public: static int const * const Test::const_vars` Any idea?
Dec 16 2021
parent reply Tim <tim.dlang t-online.de> writes:
On Thursday, 16 December 2021 at 08:30:14 UTC, Jan wrote:
 Ok, next problem. Maybe you know the necessary syntax for this 
 one too.


 C++
 ```cpp
 class Test
 {
 public:
   __declspec(dllexport) static const int const_vars[2];
   __declspec(dllexport) static int nonconst_vars[2];
 };

 const int Test::const_vars[2] = {11, 23};
 int Test::nonconst_vars[2] = {12, 24};
 ```

 D
 ```cpp
 extern(C++, class) struct Test
 {
     extern export static __gshared const(int)[2] const_vars;
     extern export static __gshared int[2] nonconst_vars;
 }
 ```

 I get the nonconst_vars to link correctly, but for the 
 const_vars the mangled name contains one less const than what 
 MSVC generates, so I assume I have to wrap this somehow 
 differently. I tried "const(int[2])" but that didn't make a 
 difference.

 D wants to link against:
 `?const_vars Test  2PAHB`

 which is:
 `public: static int * const Test::const_vars" ()`

 MSVC exports:
 `?const_vars Test  2QBHB`

 which is:
 `public: static int const * const Test::const_vars`


 Any idea?
That looks like another bug in the compiler. pragma(mangle, ...) should work as a workaround.
Dec 16 2021
parent Jan <Jan Krassnigg.de> writes:
On Thursday, 16 December 2021 at 16:21:30 UTC, Tim wrote:
 That looks like another bug in the compiler. pragma(mangle, 
 ...) should work as a workaround.
Another bug ticket it is then: https://issues.dlang.org/show_bug.cgi?id=22604
Dec 16 2021
prev sibling parent Adam D Ruppe <destructionator gmail.com> writes:
On Wednesday, 15 December 2021 at 10:54:45 UTC, Jan wrote:
 Someone with more in-depth knowledge told me, that Windows 
 support in D and specifically DLL support is lacking quite a 
 bit.
gdc and ldc have the same full support you'd expect coming from microsoft c++. dmd doesn't though. You can make full dlls with dmd just it is more diy than automatic. *Using* dlls of course is something that's been fully supported since day one. Either using an import lib or GetProcAddress.
Dec 15 2021