digitalmars.D.learn - How to pass a class by (const) reference to C++
- Jan (23/23) Dec 12 2021 In D I have an extern(C++) class:
- evilrat (11/34) Dec 12 2021 You can tell compiler to mangle it as struct/class using
- Jan (5/46) Dec 13 2021 I tried this, but it doesn't work, because it seems D decides how
- Tejas (37/85) Dec 13 2021 You'll have to use something called a
- evilrat (6/93) Dec 13 2021 Yeah but it sucks to have making C++ wrapper just for this. I
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (10/15) Dec 13 2021 Does something like this work?
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (3/5) Dec 13 2021 With ```extern(C++)``` on these…
- evilrat (14/29) Dec 13 2021 Only if this struct matches class memory layout, the only
- Ola Fosheim =?UTF-8?B?R3LDuHN0YWQ=?= (6/9) Dec 13 2021 Yes, I wouldn't want to use it, maybe manual mangling is better,
- Jan (8/23) Dec 13 2021 Unfortunately no. Maybe the cast would even make it work, but I
- Tim (6/12) Dec 13 2021 The easiest workaround is probably to set the mangling manually:
- Tejas (7/75) Dec 13 2021 Hey, evilrat, I've seen people make claims that our C++ interop
- evilrat (17/23) Dec 13 2021 Unfortunately it is mostly true.
- Jan (32/35) Dec 15 2021 As far as I can tell from my limited experience, the way D
- Jan (11/13) Dec 15 2021 Just another example: I just learned that linking against C++
- rikki cattermole (6/20) Dec 15 2021 Are you sure that on the shared library side it was marked as exported?
- Jan (9/32) Dec 15 2021 Yep, checked that. Even checked the .exp file and compared the
- evilrat (12/37) Dec 15 2021 You probably know this but just in case - unlike C++ in D
- Jan (9/20) Dec 15 2021 Yeah, did that. As I said the mangled name that DMD chose is
- Tim (9/15) Dec 15 2021 I have used global variables from DLLs successfully. DMD now also
- Jan (28/29) Dec 15 2021 C++
- Tim (13/40) Dec 15 2021 I can reproduce your problem. It seems to work if var is
- Jan (10/11) Dec 15 2021 Ha, it works indeed!
- H. S. Teoh (13/19) Dec 15 2021 [...]
- Adam Ruppe (4/7) Dec 15 2021 it is just normally __gshared implies static automatically. it
- Tim (8/17) Dec 15 2021 I agree that __gshared should imply static. That's probably a bug
- Adam D Ruppe (7/10) Dec 15 2021 gdc and ldc have the same full support you'd expect coming from
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
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
On Monday, 13 December 2021 at 07:48:34 UTC, evilrat wrote:On Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote: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.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 13 2021
On Monday, 13 December 2021 at 09:21:26 UTC, Jan wrote:On Monday, 13 December 2021 at 07:48:34 UTC, evilrat 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 :DOn Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote: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.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 13 2021
On Monday, 13 December 2021 at 11:13:12 UTC, Tejas wrote:On Monday, 13 December 2021 at 09:21:26 UTC, Jan 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.On Monday, 13 December 2021 at 07:48:34 UTC, evilrat 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 :DOn Sunday, 12 December 2021 at 21:24:39 UTC, Jan wrote: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.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 13 2021
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
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
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: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.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
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
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: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.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
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: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.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
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
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: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".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
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
On Monday, 13 December 2021 at 12:08:30 UTC, evilrat wrote:On Monday, 13 December 2021 at 11:13:12 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 :(On Monday, 13 December 2021 at 09:21:26 UTC, Jan 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.[...]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
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
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
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
On 15/12/2021 11:54 PM, Jan wrote:On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote: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.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
On Wednesday, 15 December 2021 at 11:03:27 UTC, rikki cattermole wrote:On 15/12/2021 11:54 PM, Jan wrote: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 :(On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote: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.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
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: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; ```On 15/12/2021 11:54 PM, Jan wrote: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.On Wednesday, 15 December 2021 at 09:36:54 UTC, Jan wrote: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.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
Dec 15 2021
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
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
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
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
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
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
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
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
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
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
On Wednesday, 15 December 2021 at 22:50:38 UTC, Tim wrote:On Wednesday, 15 December 2021 at 22:46:01 UTC, Jan wrote:https://issues.dlang.org/show_bug.cgi?id=22603Btw. 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
On Wednesday, 15 December 2021 at 22:50:38 UTC, Tim wrote:On Wednesday, 15 December 2021 at 22:46:01 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?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 16 2021
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
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
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