digitalmars.D - core.sys.windows.com.ComObject apparently has wrongly laid out Vtable
- Carl Sturtivant (156/157) Mar 18 DMD64 D Compiler v2.107.0
- Kagamin (23/23) Mar 19 You incorrectly declared the interface variable. Try this:
- Carl Sturtivant (84/89) Mar 19 **Nonsense**. Of course that doesn't work! It seems you didn't
- Richard (Rikki) Andrew Cattermole (53/53) Mar 19 I have gone ahead and ported the C style interface over to D and
- Carl Sturtivant (99/99) Mar 19 Very interesting, and thank you.
- Richard (Rikki) Andrew Cattermole (10/10) Mar 19 Basically what you are seeing is normal C++ class behavior.
- Carl Sturtivant (48/52) Mar 19 Nevertheless, a given virtual method may occur with the same
- Richard (Rikki) Andrew Cattermole (85/95) Mar 19 I suspect you have misunderstood another aspect of classes in general.
- Carl Sturtivant (23/29) Mar 19 A useful reference! Should have factored interfaces and not just
- Carl Sturtivant (11/18) Mar 19 Ah, I am jumping to conclusions above. The code worked. So a call
- Carl Sturtivant (8/11) Mar 19 I mean this of course in the context of the code in my original
- Richard (Rikki) Andrew Cattermole (7/18) Mar 19 The only difference I'm aware of between a class that inherits from
- Carl Sturtivant (18/21) Mar 20 Here's the interesting situation I mentioned, isolated, but with
- Carl Sturtivant (48/48) Mar 20 Now here's the new ComObject class, identical to that in druntime
- Carl Sturtivant (63/63) Mar 20 Here's the test of that extern(C++) ComObject.
- Carl Sturtivant (12/18) Mar 20 So ComObject in core.sys.windows.com (in druntime) is actually
- Dom DiSc (7/8) Mar 20 Maybe because that was not available at the time druntime was
- Carl Sturtivant (4/12) Mar 23 Perhaps you already knew what's in my proof-of-concept, but I've
- Walter Bright (5/6) Mar 20 Because COM objects need to follow the extern(Windows) calling conventio...
- Carl Sturtivant (13/19) Mar 21 I was hoping that the special treatment by the D language of
- Walter Bright (4/28) Mar 21 The C++ and Windows calling convention are different. COM code will not ...
- Carl Sturtivant (2/6) Mar 21 OK, thanks for the clarification.
- Walter Bright (4/6) Mar 21 There are two vtables in that example - one for the C++ class, and one f...
- Carl Sturtivant (2/8) Mar 21 Good to know! Thank you.
- Carl Sturtivant (7/10) Mar 24 I just read-read the entire thread and this jumped out at me. My
- Richard (Rikki) Andrew Cattermole (8/19) Mar 24 Have a closer look at the header file for IUnknown and compare the
- Walter Bright (5/15) Mar 20 A com object is an interface, not a class. All com objects must derive f...
- Carl Sturtivant (40/46) Mar 21 It's nice that the D language itself makes this happen for
- Carl Sturtivant (1/6) Mar 21
- Walter Bright (4/50) Mar 21 You can invent your own COM-like system in D by using interfaces, but th...
- Carl Sturtivant (2/11) Mar 21 Thank you for clarifying!
- Carl Sturtivant (3/12) Mar 23 I don't agree: this works in a proof of concept compiled at both
- Carl Sturtivant (156/156) Mar 23 Here is a proof of concept that a genuine COM object can be
- Carl Sturtivant (22/24) Mar 21 I'm not sure we understand each other. I am not interested in
- Carl Sturtivant (5/9) Mar 23 It seems I spoke too soon. I have a proof of concept, not using
- Carl Sturtivant (10/11) Mar 19 Yes, C++ classes in D got it right as far as casting a D
- Zoadian (3/3) Mar 20 i've also run into problems with IUnknown.
- Carl Sturtivant (4/7) Mar 20 Yes, and it would be nice if a short very direct summary of the
Any insight appreciated. Details: ```dmd --versionDMD64 D Compiler v2.107.0 Copyright (C) 1999-2024 by The D Language Foundation, All Rights Reserved written by Walter Bright ``` References: [class ComObject](https://wiki.dlang.org/COM_Programming) [interface IUnknown](https://dlang.org/spec/interface.html#com-interfaces) I only used class ComObject and implicitly the IUnknown interface it inherits from as a test of some COM code I was writing using `comheaders.c` containing the following, compiled with ImportC. ```C #define WINVER 0x0A00 #define _WIN32_WINNT 0x0A00 #define _WIN32_DCOM #include <wtypes.h> #include <oleauto.h> #include <oaidl.h> ``` The file `main.d` compiled with it is as follows. ```D import std.stdio; import comheaders; static import com = core.sys.windows.com; pragma(lib, "onecore"); //to fix linkage of two irrelevant symbols void main() { auto COMobject = new com.ComObject(); //auto COMobject = new ComObject(); IUnknown* ip = cast(IUnknown*)COMobject; writeln(COMobject.count); writeln(" ip vtable: ", ip.lpVtbl); auto vtable = COMobject.__vptr; writeln("COMobject vtable: ", vtable); writeln("ip &AddRef: ", &ip.lpVtbl.AddRef); writeln("ip offset: ", cast(void*)&ip.lpVtbl.AddRef - cast(void*)ip.lpVtbl); auto ipaddref = cast(void*)ip.lpVtbl.AddRef; writeln(" ip AddRef: ", ipaddref); auto addref = cast(void*)(&COMobject.AddRef).funcptr; writeln("COMobject AddRef: ", addref); writeln("COMobject AddRef : ip AddRef offset: ", addref - ipaddref); COMobject.AddRef(); writeln(COMobject.count); ip.lpVtbl.AddRef(ip); writeln(COMobject.count); } ``` Here I make a `ComObject` from the statically imported `core.sys.windows.com` but avoid using anything else from the D windows libs. The object contains a reference count, that should be incremented with a call of AddRef. The output was as follows. ``` 0 ip vtable: 7FF756091A30 COMobject vtable: 7FF756091A30 ip &AddRef: 7FF756091A38 ip offset: 8 ip AddRef: 7FF756027970 COMobject AddRef: 7FF756022EC0 COMobject AddRef : ip AddRef offset: -19120 1 1 ``` This shows that the call of AddRef with the correct offset does nothing, and is a different function pointer to that of AddRef in the com.ComObject. So that object will apparently not work correctly with outside world code as claimed (see the above reference links). This was compiled with ``` dmd main.d comheaders.c vcintrinsics.lib -P/wd5105 ``` where vcintrinsics.lib is a library I constructed to fix a problem with DMD not knowing of a series of MSVC intrinsics, i.e. to satify the linker as per [here](https://forum.dlang.org/post/lbvjpwqgcgducgoxwsxe forum.dlang.org), and -P/wd5105 is to suppress a warning from MSVC when it is used by ImportC for as a C preprocessor. I copied the source of the inconveniently named `interface IUnknown` in `unknwn.d` and of `class ComObject` from `com.d`, both in `C:\D\dmd2\src\druntime\src\core\sys\windows\` into the bottom of main.d and experimented. I made a local ComObject with the commented out line above active. One change fixed the problem: using extern(C++) --- no other linking attribute worked. Here's the working code. I had to edit `IUnknown` which was an interface but is now a struct in `comheaders.c`, into `IUnknown*` inside QueryInterface, and `E_NOINTERFACE` into `com.E_NOINTERFACE` and define the D interface with a name different to the struct `IUnknown` so I made it `_IUnknown_` but otherwise the source is unchanged apart from not using extern(Windows) and prefixing it all with extern(C++). Here is the rest of `main.d`. ```D import core.atomic; extern(C++): interface _IUnknown_ { HRESULT QueryInterface(IID* riid, void** pvObject); ULONG AddRef(); ULONG Release(); } class ComObject : _IUnknown_ { HRESULT QueryInterface(const(IID)* riid, void** ppv) { if (*riid == IID_IUnknown) { *ppv = cast(void*)cast(IUnknown*)this; AddRef(); return S_OK; } else { *ppv = null; return com.E_NOINTERFACE; } } ULONG AddRef() { return atomicOp!"+="(*cast(shared)&count, 1); } ULONG Release() { LONG lRef = atomicOp!"-="(*cast(shared)&count, 1); if (lRef == 0) { // free object // If we delete this object, then the postinvariant called upon // return from Release() will fail. // Just let the GC reap it. //delete this; return 0; } return cast(ULONG)lRef; } LONG count = 0; // object reference count } ``` The output is now as follows. ``` 0 ip vtable: 7FF76B9C0360 COMobject vtable: 7FF76B9C0360 ip &AddRef: 7FF76B9C0368 ip offset: 8 ip AddRef: 7FF76B951580 COMobject AddRef: 7FF76B951580 COMobject AddRef : ip AddRef offset: 0 1 2 ``` showing that finding `AddRef` in the `ComObject`s Vtable produces the same function pointer as that through the COM Interface, and both work. My working hypothesis: both `ComObject` and `IUnknown` brought in by importing `core.sys.windows.com` are broken which is all of the support for COM in Phobos. Please confirm or deny.
Mar 18
You incorrectly declared the interface variable. Try this: ``` auto COMobject = new com.ComObject(); //auto COMobject = new ComObject(); IUnknown ip = COMobject; writeln(COMobject.count); writeln(" ip vtable: ", ip.lpVtbl); auto vtable = COMobject.__vptr; writeln("COMobject vtable: ", vtable); writeln("ip &AddRef: ", &ip.lpVtbl.AddRef); writeln("ip offset: ", cast(void*)&ip.lpVtbl.AddRef - cast(void*)ip.lpVtbl); auto ipaddref = cast(void*)ip.lpVtbl.AddRef; writeln(" ip AddRef: ", ipaddref); auto addref = cast(void*)(&COMobject.AddRef).funcptr; writeln("COMobject AddRef: ", addref); writeln("COMobject AddRef : ip AddRef offset: ", addref - ipaddref); COMobject.AddRef(); writeln(COMobject.count); ip.lpVtbl.AddRef(ip); writeln(COMobject.count); ```
Mar 19
On Tuesday, 19 March 2024 at 09:22:09 UTC, Kagamin wrote:You incorrectly declared the interface variable. Try this: ``` auto COMobject = new com.ComObject(); //auto COMobject = new ComObject(); IUnknown ip = COMobject;**Nonsense**. Of course that doesn't work! It seems you didn't read or didn't understand my post. All you've done is written circular code that confirms compatibility with itself by remaining entirely within its own world of D only, and only using `core.sys.windows.com`. To make this "work" you had to `import core.sys.windows.com;` which I do not. You have NOT confirmed the compatibility of `core.sys.windows.com.IUnknown` and `core.sys.windows.com.ComObject` with the outside world of actual COM. Supposedly things defined using those can be passed through COM to outside world and will work correctly there. This is exactly what is NOT happening here. As mentioned in my original post `core.sys.windows.com` is *statically* included so `core.sys.windows.com.IUnknown` is NOT `IUnknown` in `main.d` and in fact, apart from making a `ComObject` none of `core.sys.windows.com` is used at all in the original run. `IUnknown` in my code is the usual COM struct defined in a Windows header and imported from `comheaders.c`. It's called an interface in COM terminology but it is NOT a D interface, and you can't inherit from it. So if I just change `main.d` to the code you quoted above it won't compile of course. ``` main.d(11): Error: cannot implicitly convert expression `COMobject` of type `core.sys.windows.com.ComObject` to `IUnknown` ``` The outside world in C would work with an object implementing IUnknown (or another COM interface) as a pointer to a struct of type IUnknown (or similar), and main.d works with COM in exactly that way. The documentation of the D interface IUnknown at the quoted link in my original post says that *A COM interface is defined as one that derives from the interface core.sys.windows.com.IUnknown.* and *A COM interface is designed to map directly onto a Windows COM object. Any COM object can be represented by a COM interface, and any D object with a COM interface can be used by external COM clients.* This is D's *internal* definition, how a COM interface (outside world definition independent of programming language) is represented inside D. Conflating the inside D meanings of *COM interface* and *IUnknown* with the outside world COM definitions of those terms is the source of confusion. In effect the documentation deliberately conflates the two by tacitly assuming that the D representation is correct so we need not make that distinction. The D class `core.sys.windows.com.ComObject` inherits from `core.sys.windows.com.IUnknown` and so should follow those statements. My code gets one of those and behaves as an *external COM client*, but the result does not work. If I fix the source of `core.sys.windows.com.ComObject` and `core.sys.windows.com.IUnknown` to have `extern(C++)` linkage then the self-same code works, showing that I wrote it correctly. In order to work with COM the way that C does, when given a ComObject from the world of D, I need to treat it as a pointer to an IUnknown struct. The ComObject variable COMobject is already a reference so I need to turn it into a pointer of type IUnknown* that points to the actual ComObject that the COMobject variable refers to, all without knowing the D type of the object. This is just simulating what would happen if such an object was passed by D though COM to e.g. an arbitrary C program. COM clients do not know the native type of the objects passed them, only the COM interface (NOT D interface) they are supposed to implement. All the recipient knows is that the pointer is to a struct that contains a properly laid out Vtable with `QueryInterface`, `AddRef` and `Release` as the first three entries in that order in accordance with the standard layout of an IUnknown struct. The following code correctly achieves that outcome. ``` IUnknown* ip = cast(IUnknown*)COMobject ``` Then *ip is used as the (COM not D) interface to the externally provided COM object and it doesn't work unless the D library code is repaired. Then it DOES work. So IUnknown and ComObject in druntime are broken. They do not lead to Vtables laid out according to the COM structs defined by Windows.
Mar 19
I have gone ahead and ported the C style interface over to D and rewritten your test code. Here the output it prints: ``` initial count 0 ip vtable: 7FF76EFFA240 ip AddRef: 7FF76EF9D210 iunknownObject vtable: 7FF76EFFA240 iunknownObject AddRef: 7FF76EF9D210 After D com object count: 1 After C com object count: 2 ``` The code: ```d import std.stdio; import drtcom = core.sys.windows.com; import windows = core.sys.windows.windows; void main() { auto comObject = new drtcom.ComObject; auto iunknownObject = cast(drtcom.IUnknown)comObject; writeln("initial count ", comObject.count); { C_IUnknown* ip = cast(C_IUnknown*)iunknownObject; writeln(" ip vtable: ", ip.lpVtbl); auto ipaddref = cast(void*)ip.lpVtbl.AddRef; writeln(" ip AddRef: ", ipaddref); } { auto vtable = iunknownObject.__vptr; writeln(" iunknownObject vtable: ", vtable); auto addref = cast(void*)(&iunknownObject.AddRef).funcptr; writeln(" iunknownObject AddRef: ", addref); } { comObject.AddRef(); writeln("After D com object count: ", comObject.count); C_IUnknown* ip = cast(C_IUnknown*)iunknownObject; ip.lpVtbl.AddRef(ip); writeln("After C com object count: ", comObject.count); } } struct C_IUnknown { C_IUnknownVtbl* lpVtbl; } struct C_IUnknownVtbl { extern(Windows) { windows.HRESULT function(C_IUnknown* This, windows.IID* riid, void** ppvObject) QueryInterface; windows.ULONG function(C_IUnknown* This) AddRef; windows.ULONG function(C_IUnknown* This) Release; } } ```
Mar 19
Very interesting, and thank you. It's the cast I am using! In the notation of your code, by casting comObject directly to C_IUnknown* I am getting a different pointer than you do by first casting to drtcom.IUnknown and then casting to C_IUnknown*. There are various other subtle differences going on here too. In my code the Vtables are the same despite the cast I use. But you get your Vtable from the drtcom.IUnknown variable, not the comObject variable. The results are different. Here's a modification of your main function that shows what's going on ```D void main() { auto comObject = new drtcom.ComObject; auto iunknownObject = cast(drtcom.IUnknown)comObject; writeln("initial count ", comObject.count); { C_IUnknown* ip1 = cast(C_IUnknown*)iunknownObject; writeln(" ip1 vtable: ", ip1.lpVtbl); auto ipaddref = cast(void*)ip1.lpVtbl.AddRef; writeln(" ip1 AddRef: ", ipaddref); } { C_IUnknown* ip2 = cast(C_IUnknown*)comObject; writeln(" ip2 vtable: ", ip2.lpVtbl); auto ipaddref = cast(void*)ip2.lpVtbl.AddRef; writeln(" ip2 AddRef: ", ipaddref); } { auto vtable = iunknownObject.__vptr; writeln(" iunknownObject vtable: ", vtable); auto addref = cast(void*)(&iunknownObject.AddRef).funcptr; writeln(" iunknownObject AddRef: ", addref); } { auto vtable = comObject.__vptr; writeln(" comObject vtable: ", vtable); auto addref = cast(void*)(&comObject.AddRef).funcptr; writeln(" comObject AddRef: ", addref); } { comObject.AddRef(); writeln("After D com object count: ", comObject.count); C_IUnknown* ip_old = cast(C_IUnknown*)iunknownObject; C_IUnknown* ip = cast(C_IUnknown*)comObject; writefln("ip = %s ", ip); writefln("ip_old = %s", ip_old); ip.lpVtbl.AddRef(ip); writeln("After C com object count: ", comObject.count); } } ``` The output is as follows. ``` initial count 0 ip1 vtable: 7FF7EA4318A8 ip1 AddRef: 7FF7EA3C2C90 ip2 vtable: 7FF7EA4318E0 ip2 AddRef: 7FF7EA3C7800 iunknownObject vtable: 7FF7EA4318A8 iunknownObject AddRef: 7FF7EA3C2C90 comObject vtable: 7FF7EA4318E0 comObject AddRef: 7FF7EA3C2D50 After D com object count: 1 ip = 20227481000 ip_old = 20227481010 After C com object count: 1 ``` I am confident that casting a D interface variable to a pointer works at the binary level in the expected way, especially for drtcom.IUnknown because the only reasonably simple assumption the compiler can make is that it is a valid COM object, not something that came from D. The cast of a ComObject to a pointer produces something 16 bytes earlier than the actual start of the COM object according to the working cast, suggesting that the compiler is using some trickery in it's internal representation of COM objects made from D classes that inherit from the D interface IUnknown. Intuitively, D may make a "normal" object with the "COM" object inside it offset by 16 bytes: two words at 64 bits. We might guess this is the two usual words in a class object, a Vtable pointer and a monitor. We might guess that a "COM" Vtable pointer follows that inside the "COM" part and it points to an offset inside the "normal" vtable so as to start with the necessary QueryInterface, AddRef, Release. However, when casting such an object to a pointer, a pointer to the normal object is produced, NOT a pointer to the internal "COM" object. This is possible because D knows the type of the object because it is manufactured by D. This is responsible for the bad behavior shown in my initial post. If the ComObject is created with extern(C++) we might guess that it is then behaves exactly as what it purports to be so as to be entirely compatible with C++. COM in Windows has both a C++ API and a binary compatible C API, so now everything works as expected. extern(C++) object pointers/references need to just work, so the compiler won't produce a bogus offset, whether or not the extern(C++) object is secretly embedded inside a "normal" object, which we might guess it is. There's just a different convention for pointers and references.
Mar 19
Basically what you are seeing is normal C++ class behavior. The only difference between a C++ class reference and a COM class reference, is that the methods in the vtable is stdcall for COM. It's worth noting that for every class and interface in a given class hierarchy, each have different vtables for a given object. This is why you must cast the D object to IUnknown first before inspecting its reference. Which reflects what the compiler is doing when you inherit from IUnknown, set the default calling convention and convert to a C++ class/interface.
Mar 19
On Tuesday, 19 March 2024 at 18:35:38 UTC, Richard (Rikki) Andrew Cattermole wrote:It's worth noting that for every class and interface in a given class hierarchy, each have different vtables for a given object.Nevertheless, a given virtual method may occur with the same index in the Vtable of a class and in the Vtable of a class that inherits from it and overrides that method, so dynamic lookup finds a given method in a given position in a Vtable irrespective of the exact class in the hierarchy it occurs in: statically determined code can then look it up in the Vtable by its fixed index at the point in source that a method call occurs. And the case of COM this position is determined by the COM interface being implemented, where AddRef is always in position 1 after QueryInterface for example.This is why you must cast the D object to IUnknown first before inspecting its reference.I do not think this follows, either for a regular D object or a COM object. For the first the class hierarchy can determine the order of the virtual methods and overriding doesn't change the index of a method with a given signature. For a COM object the order of the methods in the Vtable is determined by the COM interface that the object is implementing. In the case of this thread, it's determined by the definition of IUnknown in COM. Specifically, the ComObject class in D inherits from D's IUnknown interface and this is modeling implementing the COM interface IUnknown with an object with methods in positions 0,1,2 in the Vtable implementing QueryInterface, AddRef and Release. It should be possible to cast a ComObject which by definition implements the COM interface IUnknown into a pointer to its first word, which external COM expects to contain a pointer to its Vtable, which external COM expects to contain pointers to those three methods in positions 0,1,2. However this casting doesn't produce a pointer to the COM object needed in any external COM context. It produces a pointer 16 bytes short of that object, which makes no sense semantically. What is it a pointer to? Something hidden by the implementation that does not function as a COM object externally. Casting to a pointer is producing an implementation dependent pointer to an object NOT defined in source that happens to seem to contain offset down two words the actual object that we have defined in source, and the object pointed to seems to have a bogus Vtable for COM purposes that doesn't work. We have no reason whatsoever to be given access to this outer object. D does not define what it is! And D is not producing a pointer to the COM object that we defined. This is a semantic bug: it defines casting to a pointer to have silly undefined semantics. Perhaps the reference to a COM object defined by a class that inherits IUnknown is secretly represented by a pointer to this outer object. That's no reason to have casting such a reference to a pointer not be to compute a pointer to the COM object inside that outer object, which is the only cast that has any meaning.
Mar 19
On 20/03/2024 12:36 PM, Carl Sturtivant wrote:This is why you must cast the D object to IUnknown first before inspecting its reference. I do not think this follows, either for a regular D object or a COM object. For the first the class hierarchy can determine the order of the virtual methods and overriding doesn't change the index of a method with a given signature. For a COM object the order of the methods in the Vtable is determined by the COM interface that the object is implementing. In the case of this thread, it's determined by the definition of IUnknown in COM.I suspect you have misunderstood another aspect of classes in general. First an example, a class and an interface WILL have a different vtable entries even if what the vtable represents is the same thing. ```d import std.stdio : writeln; void main() { C c = new C; I i = cast(I)c; printVtable(c.__vptr, 3); printVtable(i.__vptr, 3); printMember!"query"(i, c); printMember!"add"(i, c); printMember!"sub"(i, c); } void printVtable(immutable(void)* vtable, size_t members) { writeln("vtable ", vtable); void** ptr = cast(void**)vtable; foreach(i; 0 .. members) { writeln("- ", ptr[i]); } } void printMember(string member, T, U)(T first, U second) { writeln( member, " ", "First: ", (&__traits(getMember, first, member)).funcptr, " Second: ", (&__traits(getMember, second, member)).funcptr ); } extern(C++) interface I { void query(); void add(); void sub(); } extern(C++) class C : I { override void query() {} override void add() {} override void sub() {} } ``` Will output: ``` vtable 562927D04210 - 562927C9577C - 562927C9578C - 562927C9579C vtable 562927D041E8 - 562927C9566C - 562927C9567C - 562927C9568C query First: 562927C9566C Second: 562927C9577C add First: 562927C9567C Second: 562927C9578C sub First: 562927C9568C Second: 562927C9579C ``` The reason for this is something called a thunk. ```asm .text segment assume CS:.text : _THUNK0: sub RDI,8 jmp _ZN1C5queryEv PLT32 0f1f add byte ptr [RAX],0 add [RAX],AL _THUNK1: sub RDI,8 jmp _ZN1C3addEv PLT32 0f1f add byte ptr [RAX],0 add [RAX],AL _THUNK2: sub RDI,8 jmp _ZN1C3subEv PLT32 add [RAX],AL add [RAX],AL .text ends .data segment ``` These are functions that alter in this case the this pointer to align with what the actual function expects. https://dlang.org/spec/abi.html#classes Without it the interface will not understand how to call the class and all the pointers will be a little bit off, in this case the size of a pointer aka the extra vtable entry.
Mar 19
On Wednesday, 20 March 2024 at 04:14:44 UTC, Richard (Rikki) Andrew Cattermole wrote:These are functions that alter in this case the this pointer to align with what the actual function expects. https://dlang.org/spec/abi.html#classesA useful reference! Should have factored interfaces and not just single inheritance into my speculation on inheritance and Vtables. Thanks for the detailed counterargument. However, in the world of COM the situation can be considered simpler by having COM objects implement only one COM interface. Perhaps that cannot be reflected in how COM is represented in D because of the potentially more complicated possibilities for an object while it still represents a regular COM object.Without it the interface will not understand how to call the class and all the pointers will be a little bit off, in this case the size of a pointer aka the extra vtable entry.So the layout is as I concretely suspected in this case, the 16 bytes being exactly the offset mentioned abstractly here in the concrete case of IUnknown and ComObject. I inspected the reference to a ComObject from druntime and it is indeed a pointer offset upward by those 16 bytes from a reference cast to a pointer when it is first cast to IUnknown. This being the case, how do C++ objects in D escape this constraint when cast to a pointer? Remember the top post? Redefining IUnknown and ComObject to have C++ linkage eliminated the 16 byte offset when casting directly and not via IUnknown, and the code worked. Is there some reason why this arrangement is not used for COM objects in D?
Mar 19
On Wednesday, 20 March 2024 at 05:26:19 UTC, Carl Sturtivant wrote:This being the case, how do C++ objects in D escape this constraint when cast to a pointer? Remember the top post? Redefining IUnknown and ComObject to have C++ linkage eliminated the 16 byte offset when casting directly and not via IUnknown, and the code worked.Ah, I am jumping to conclusions above. The code worked. So a call to a working AddRef was found in the ComObject Vtable. Exactly the way it wasn't for the library code of a ComObject, where the IUnknown interface Vtable had to be found in order for a working AddRef to be called. And your reply uses extern(C++) --- should have noticed that.Is there some reason why this arrangement is not used for COM objects in D?What are the Vtable differences that cause an extern(C++) ComObject to work when calling AddRef when the druntime ComObject with extern(Windows) does not?
Mar 19
On Wednesday, 20 March 2024 at 05:42:29 UTC, Carl Sturtivant wrote:What are the Vtable differences that cause an extern(C++) ComObject to work when calling AddRef when the druntime ComObject with extern(Windows) does not?I mean this of course in the context of the code in my original post in this thread. There casting a druntime ComObject to a pointer leads to a non-working AddRef, but casting a extern(C++) ComObject to a pointer leads to a working AddRef. I could speculate, but what are the actual rules operating that lead to this?
Mar 19
On 20/03/2024 6:56 PM, Carl Sturtivant wrote:On Wednesday, 20 March 2024 at 05:42:29 UTC, Carl Sturtivant wrote:The only difference I'm aware of between a class that inherits from IUnknown and extern(C++) should be the calling convention used for the functions in the vtable. But your original code had a number of assumptions in it that weren't correct, so you should probably write a replacement first and work off of that.What are the Vtable differences that cause an extern(C++) ComObject to work when calling AddRef when the druntime ComObject with extern(Windows) does not?I mean this of course in the context of the code in my original post in this thread. There casting a druntime ComObject to a pointer leads to a non-working AddRef, but casting a extern(C++) ComObject to a pointer leads to a working AddRef. I could speculate, but what are the actual rules operating that lead to this?
Mar 19
On Wednesday, 20 March 2024 at 06:16:01 UTC, Richard (Rikki) Andrew Cattermole wrote:The only difference I'm aware of between a class that inherits from IUnknown and extern(C++) should be the calling convention used for the functions in the vtable.Here's the interesting situation I mentioned, isolated, but with the C/Windows definition of the struct IUnknown placed in C source compiled with ImportC. `comdef.c` ```C #include <wtypes.h> struct C_IUnknownVtbl { HRESULT (*QueryInterface)(C_IUnknown* This, IID* riid, void** ppvObject); ULONG (*AddRef)(C_IUnknown* This); ULONG (*Release)(C_IUnknown* This); }; struct C_IUnknown { C_IUnknownVtbl* lpVtbl; }; ```
Mar 20
Now here's the new ComObject class, identical to that in druntime apart from the necessary `com.` prefix due to the static import of `core.sys.windows.com`, except that I declared the class extern(C++). ```D import com = core.sys.windows.com; import windows = core.sys.windows.windows; import core.atomic; extern(C++) class ComObject : com.IUnknown { HRESULT QueryInterface(const(com.IID)* riid, void** ppv) { if (*riid == com.IID_IUnknown) { *ppv = cast(void*)cast(com.IUnknown)this; AddRef(); return S_OK; } else { *ppv = null; return com.E_NOINTERFACE; } } ULONG AddRef() { return atomicOp!"+="(*cast(shared)&count, 1); } ULONG Release() { LONG lRef = atomicOp!"-="(*cast(shared)&count, 1); if (lRef == 0) { // free object // If we delete this object, then the postinvariant called upon // return from Release() will fail. // Just let the GC reap it. //delete this; return 0; } return cast(ULONG)lRef; } LONG count = 0; // object reference count } ``` So this ComObject inherits from the druntime interface IUnknown and according to the docs therefore has extern(System) as its default for methods, which is extern(Windows) here.
Mar 20
Here's the test of that extern(C++) ComObject. ```D import std.stdio; import com = core.sys.windows.com; import windows = core.sys.windows.windows; import comdef; pragma(lib,"onecore"); void main() { //auto COMobject = new com.ComObject(); auto COMobject = new ComObject(); IUnknown* ip = cast(IUnknown*)COMobject; writeln(COMobject.count); writeln(" ip vtable: ", ip.lpVtbl); auto vtable = COMobject.__vptr; writeln("COMobject vtable: ", vtable); writeln("ip &AddRef: ", &ip.lpVtbl.AddRef); writeln("ip offset: ", cast(void*)&ip.lpVtbl.AddRef - cast(void*)ip.lpVtbl); auto ipaddref = cast(void*)ip.lpVtbl.AddRef; writeln(" ip AddRef: ", ipaddref); auto addref = cast(void*)(&COMobject.AddRef).funcptr; writeln("COMobject AddRef: ", addref); writeln("COMobject AddRef : ip AddRef offset: ", addref - ipaddref); COMobject.AddRef(); writeln(COMobject.count); ip.lpVtbl.AddRef(ip); writeln(COMobject.count); } ``` Note that I cast COMobject (which is a new ComObject) directly to a IUnknown*. (Here IUnknown is the struct defined in `comdef.c` just the way Windows defines it.) This code works! The result: ``` 0 ip vtable: 7FF6AFEB0360 COMobject vtable: 7FF6AFEB0360 ip &AddRef: 7FF6AFEB0368 ip offset: 8 ip AddRef: 7FF6AFE31580 COMobject AddRef: 7FF6AFE31580 COMobject AddRef : ip AddRef offset: 0 1 2 ``` This shows that the Vtable of the extern(C++) ComObject has COM interface layout. However, if I remove `extern(C++)` from the definition of the new ComObject class, now this does NOT work. Result without extern(C++) is: ``` 0 ip vtable: 7FF6C74D02B0 COMobject vtable: 7FF6C74D02B0 ip &AddRef: 7FF6C74D02B8 ip offset: 8 ip AddRef: 7FF6C74619E0 COMobject AddRef: 7FF6C7451580 COMobject AddRef : ip AddRef offset: -66656 1 1 ``` This shows the Vtable of the ComObject does not have COM interface layout.
Mar 20
On Wednesday, 20 March 2024 at 14:26:24 UTC, Carl Sturtivant wrote:Note that I cast COMobject (which is a new ComObject) directly to a IUnknown*. (Here IUnknown is the struct defined in `comdef.c` just the way Windows defines it.) This code works! This shows that the Vtable of the extern(C++) ComObject has COM interface layout. However, if I remove `extern(C++)` from the definition of the new ComObject class, now this does NOT work.So ComObject in core.sys.windows.com (in druntime) is actually NOT a COM object in the external world of COM sense. But if was to be declared extern(C++) it would be a COM object in that sense, directly castable to an external IUnknown* that works externally, or so it seems. The only possible snag might be calling conventions for its methods. However, it inherits from the druntime D interface IUnknown which supposedly guarantees the correct convention for Windows/COM. So why isn't ComObject declared extern(C++) in druntime?
Mar 20
On Wednesday, 20 March 2024 at 14:38:11 UTC, Carl Sturtivant wrote:So why isn't ComObject declared extern(C++) in druntime?Maybe because that was not available at the time druntime was written? Write a bug report and suggest to change that. But be aware: There is likely code out there that relies on this bug :-(
Mar 20
On Wednesday, 20 March 2024 at 14:46:43 UTC, Dom DiSc wrote:On Wednesday, 20 March 2024 at 14:38:11 UTC, Carl Sturtivant wrote:Perhaps you already knew what's in my proof-of-concept, but I've just now made it experimentally clear just above in this thread that this may very reasonably be regarded as a bug.So why isn't ComObject declared extern(C++) in druntime?Maybe because that was not available at the time druntime was written? Write a bug report and suggest to change that. But be aware: There is likely code out there that relies on this bug :-(
Mar 23
On 3/20/2024 7:38 AM, Carl Sturtivant wrote:So why isn't ComObject declared extern(C++) in druntime?Because COM objects need to follow the extern(Windows) calling convention. It's a mistake to try to cast it to extern(C++). I didn't invent this. Microsoft did. COM was invented around 1990 or so. It's cast in concrete.
Mar 20
On Thursday, 21 March 2024 at 03:48:04 UTC, Walter Bright wrote:On 3/20/2024 7:38 AM, Carl Sturtivant wrote:I was hoping that the special treatment by the D language of IUnknown in core.sys.windows.com would apply to any class that inherited from it, even an extern(C++) class, so I could have my cake and eat it, and ```D extern(C++) class ComObject : IUnknown { /* ... */ } ``` would produce a class with extern(Windows) calling and an extern(C++) COM compatible vtable. This does compile. Does the extern(C++) remove the special treatment of IUnknown and revert the class to the wrong calling convention?So why isn't ComObject declared extern(C++) in druntime?Because COM objects need to follow the extern(Windows) calling convention. It's a mistake to try to cast it to extern(C++). I didn't invent this. Microsoft did. COM was invented around 1990 or so. It's cast in concrete.
Mar 21
On 3/21/2024 6:22 AM, Carl Sturtivant wrote:On Thursday, 21 March 2024 at 03:48:04 UTC, Walter Bright wrote:The C++ and Windows calling convention are different. COM code will not be able to successfully call C++ functions. You can derive a C++ class from a COM interface, but you can't cast a COM interface to a C++ class.On 3/20/2024 7:38 AM, Carl Sturtivant wrote:I was hoping that the special treatment by the D language of IUnknown in core.sys.windows.com would apply to any class that inherited from it, even an extern(C++) class, so I could have my cake and eat it, and ```D extern(C++) class ComObject : IUnknown { /* ... */ } ``` would produce a class with extern(Windows) calling and an extern(C++) COM compatible vtable. This does compile. Does the extern(C++) remove the special treatment of IUnknown and revert the class to the wrong calling convention?So why isn't ComObject declared extern(C++) in druntime?Because COM objects need to follow the extern(Windows) calling convention. It's a mistake to try to cast it to extern(C++). I didn't invent this. Microsoft did. COM was invented around 1990 or so. It's cast in concrete.
Mar 21
On Thursday, 21 March 2024 at 16:06:32 UTC, Walter Bright wrote:The C++ and Windows calling convention are different. COM code will not be able to successfully call C++ functions. You can derive a C++ class from a COM interface, but you can't cast a COM interface to a C++ class.OK, thanks for the clarification.
Mar 21
On 3/21/2024 6:22 AM, Carl Sturtivant wrote:would produce a class with extern(Windows) calling and an extern(C++) COM compatible vtable.There are two vtables in that example - one for the C++ class, and one for the COM interface. They are not interchangeable with a cast, because the layouts are different.
Mar 21
On Thursday, 21 March 2024 at 16:07:46 UTC, Walter Bright wrote:On 3/21/2024 6:22 AM, Carl Sturtivant wrote:Good to know! Thank you.would produce a class with extern(Windows) calling and an extern(C++) COM compatible vtable.There are two vtables in that example - one for the C++ class, and one for the COM interface. They are not interchangeable with a cast, because the layouts are different.
Mar 21
On Wednesday, 20 March 2024 at 06:16:01 UTC, Richard (Rikki) Andrew Cattermole wrote:The only difference I'm aware of between a class that inherits from IUnknown and extern(C++) should be the calling convention used for the functions in the vtable.I just read-read the entire thread and this jumped out at me. My latest proof of concept above endorses this position. It would be very helpful if you investigated and fully confirmed that the difference you stated is indeed the only difference? Then this thread will have reached its conclusion.
Mar 24
On 25/03/2024 3:39 AM, Carl Sturtivant wrote:On Wednesday, 20 March 2024 at 06:16:01 UTC, Richard (Rikki) Andrew Cattermole wrote:Have a closer look at the header file for IUnknown and compare the interface to the c-style struct. IUnknown is a regular C++ interface. The c-style struct uses ``extern(Windows)`` function pointers in a vtable struct. This doesn't mean that there are no other changes, but as far as this thread is concerned I think that confirms what you wanted to know.The only difference I'm aware of between a class that inherits from IUnknown and extern(C++) should be the calling convention used for the functions in the vtable.I just read-read the entire thread and this jumped out at me. My latest proof of concept above endorses this position. It would be very helpful if you investigated and fully confirmed that the difference you stated is indeed the only difference? Then this thread will have reached its conclusion.
Mar 24
On 3/19/2024 10:56 PM, Carl Sturtivant wrote:On Wednesday, 20 March 2024 at 05:42:29 UTC, Carl Sturtivant wrote:A com object is an interface, not a class. All com objects must derive from core.sys.windows.unknwn.IUknown, or from an interface that derives from IUnknown. IUnknown uses the extern (Windows) calling convention. For com objects, slot 0 in the vtbl[] is for QueryInterface(), not the classinfo.What are the Vtable differences that cause an extern(C++) ComObject to work when calling AddRef when the druntime ComObject with extern(Windows) does not?I mean this of course in the context of the code in my original post in this thread. There casting a druntime ComObject to a pointer leads to a non-working AddRef, but casting a extern(C++) ComObject to a pointer leads to a working AddRef. I could speculate, but what are the actual rules operating that lead to this?
Mar 20
On Thursday, 21 March 2024 at 03:38:18 UTC, Walter Bright wrote:A com object is an interface, not a class. All com objects must derive from core.sys.windows.unknwn.IUknown, or from an interface that derives from IUnknown. IUnknown uses the extern (Windows) calling convention. For com objects, slot 0 in the vtbl[] is for QueryInterface(), not the classinfo.It's nice that the D language itself makes this happen for `core.sys.windows.unknwn.IUnknown` specially so as to make writing a COM client using a class possible and having it pass on its extern(Windows) calling convention is nice too. As you say, all COM objects must inherit from this --- otherwise they don't get the twin benefits of extern(Windows) calling and a COM compatible vtbl[] for their IUnknown interface. Can we get those benefits out here without `core.sys.windows.unknwn.IUnknown` by definining a class extern(C++) to get a COM compatible vtbl[] and then qualifying each of its methods as extern(Windows) to get COM compatible calling, and writing our methods in the correct COM order, starting with QueryInterface, AddRef, Release, and relying on D to place the methods in the vtbl[] in that order? (Of course, this won't then bless the descendants of such an interface or class, the way the descendents of `core.sys.windows.unknwn.IUnknown` are blessed by a special dispensation in the D language definition, but still. It would be very nice if we could so bless an interface or class ourselves with `extern(COM)`, fixing the vtbl[], calling convention, and method ordering in the vtable rules, for self and any descendant.) I ask if we can get these benefits ourselves as above because there's an external world difficulty using `core.sys.windows.unknwn.IUnknown` with the rest of Windows programming outside of just using `core.sys.windows.windows` and suchlike. `core.sys.windows.unknwn.IUnknown` forces the definition of `core.sys.windows.basetyps.GUID` into any COM code written using it because the first parameter of `core.sys.windows.unknwn.IUnknown.QueryInterface` is defined to be of type `IID*` and `IID` is defined to be `const(GUID)`, all in `core.sys.windows.basetyps`. Now if ImportC is used to bring in Windows COM related headers so as to use the wide collection COM machinery there, a binary-identical GUID type is defined in `guidef.h` and used throughout. Inheriting from `core.sys.windows.unknwn.IUnknown` to make COM objects of type that interface means that QueryInterface will have the wrong type for its first parameter to work with all of Windows COM machinery. Working around this is awkward.
Mar 21
On Thursday, 21 March 2024 at 14:30:24 UTC, Carl Sturtivant wrote:It's nice that the D language itself makes this happen for `core.sys.windows.unknwn.IUnknown` specially so as to make writing a COM ~~client~~ **server** using a class possible and having it pass on its extern(Windows) calling convention is nice too.
Mar 21
On 3/21/2024 7:30 AM, Carl Sturtivant wrote:On Thursday, 21 March 2024 at 03:38:18 UTC, Walter Bright wrote:No, because the vtbl[] layout is different.A com object is an interface, not a class. All com objects must derive from core.sys.windows.unknwn.IUknown, or from an interface that derives from IUnknown. IUnknown uses the extern (Windows) calling convention. For com objects, slot 0 in the vtbl[] is for QueryInterface(), not the classinfo.It's nice that the D language itself makes this happen for `core.sys.windows.unknwn.IUnknown` specially so as to make writing a COM client using a class possible and having it pass on its extern(Windows) calling convention is nice too. As you say, all COM objects must inherit from this --- otherwise they don't get the twin benefits of extern(Windows) calling and a COM compatible vtbl[] for their IUnknown interface. Can we get those benefits out here without `core.sys.windows.unknwn.IUnknown` by definining a class extern(C++) to get a COM compatible vtbl[] and then qualifying each of its methods as extern(Windows) to get COM compatible calling, and writing our methods in the correct COM order, starting with QueryInterface, AddRef, Release, and relying on D to place the methods in the vtbl[] in that order?(Of course, this won't then bless the descendants of such an interface or class, the way the descendents of `core.sys.windows.unknwn.IUnknown` are blessed by a special dispensation in the D language definition, but still. It would be very nice if we could so bless an interface or class ourselves with `extern(COM)`, fixing the vtbl[], calling convention, and method ordering in the vtable rules, for self and any descendant.) I ask if we can get these benefits ourselves as above because there's an external world difficulty using `core.sys.windows.unknwn.IUnknown` with the rest of Windows programming outside of just using `core.sys.windows.windows` and suchlike. `core.sys.windows.unknwn.IUnknown` forces the definition of `core.sys.windows.basetyps.GUID` into any COM code written using it because the first parameter of `core.sys.windows.unknwn.IUnknown.QueryInterface` is defined to be of type `IID*` and `IID` is defined to be `const(GUID)`, all in `core.sys.windows.basetyps`. Now if ImportC is used to bring in Windows COM related headers so as to use the wide collection COM machinery there, a binary-identical GUID type is defined in `guidef.h` and used throughout. Inheriting from `core.sys.windows.unknwn.IUnknown` to make COM objects of type that interface means that QueryInterface will have the wrong type for its first parameter to work with all of Windows COM machinery. Working around this is awkward.You can invent your own COM-like system in D by using interfaces, but they won't work with Windows COM code.
Mar 21
On Thursday, 21 March 2024 at 16:10:25 UTC, Walter Bright wrote:On 3/21/2024 7:30 AM, Carl Sturtivant wrote:Thank you for clarifying!Can we get those benefits out here without `core.sys.windows.unknwn.IUnknown` by definining a class extern(C++) to get a COM compatible vtbl[] and then qualifying each of its methods as extern(Windows) to get COM compatible calling, and writing our methods in the correct COM order, starting with QueryInterface, AddRef, Release, and relying on D to place the methods in the vtbl[] in that order?No, because the vtbl[] layout is different.
Mar 21
On Thursday, 21 March 2024 at 16:10:25 UTC, Walter Bright wrote:On 3/21/2024 7:30 AM, Carl Sturtivant wrote:I don't agree: this works in a proof of concept compiled at both 32 and 64 bits. Details below.Can we get those benefits out here without `core.sys.windows.unknwn.IUnknown` by definining a class extern(C++) to get a COM compatible vtbl[] and then qualifying each of its methods as extern(Windows) to get COM compatible calling, and writing our methods in the correct COM order, starting with QueryInterface, AddRef, Release, and relying on D to place the methods in the vtbl[] in that order?No, because the vtbl[] layout is different.
Mar 23
Here is a proof of concept that a genuine COM object can be created in D that works in the outside word, yet NOT using `core.sys.windows.unknwn.IUnknown`, following a recipe I suggested above. The vtbl[] of the COM object is the vtbl[] of the D class object and is NOT the vtbl[] of a D interface inside it, the way it would have been had the class been implemented conventionally by inheriting from `core.sys.windows.unknwn.IUnknown`. ```D import core.sys.windows.windows; import core.sys.windows.com; import std.stdio; extern(C++) class COMclassObject { extern(Windows): //Does NOT inherit from D interface IUnknown //Specifies extern(C++) for the class: // in hope of a COM compatible vtbl[] // with QueryInterface in slot 0 //Specifies extern(Windows) for the methods: // in hope of COM compatible calling (__stdcall) //Hope that methods are added to vtbl[] for COM: // in the order they are written //Cast directly to void* : // to get a pointer to its vtbl[] as per ABI docs here: // https://dlang.org/spec/abi.html#classes HRESULT QueryInterface(REFIID riid, void** ppvObject) { writeln("Called QueryInterface."); if( *riid == IID_IUnknown ) { *ppvObject = cast(void*)this; //cast directly to void* AddRef(); return S_OK; } *ppvObject = null; return E_NOINTERFACE; } import core.atomic; ULONG AddRef() { writeln("Called AddRef."); return atomicOp!"+="(*cast(shared)&count, 1); } ULONG Release() { //e.g. GC version, does not destroy writeln("Called Release."); LONG lRef = atomicOp!"-="(*cast(shared)&count, 1); return cast(ULONG)lRef; } LONG count = 0; } void main() { //act as COM server auto comClassObject = new COMclassObject(); //cast to void* to get COM interface auto pInterface = cast(void*)comClassObject; //instead of sending pInterface to an outside client //simulate being that COM client here //test as COM client conventionally here //for convenience, using D's interface IUnknown // https://dlang.org/spec/interface.html#com-interfaces auto unknown = cast(IUnknown)pInterface; //get a COM interface using QueryInterface void* pUnk; write("Calling QueryInterface: "); HRESULT hr = unknown.QueryInterface(&IID_IUnknown, &pUnk); assert(SUCCEEDED(hr)); //use the resulting interface conventionally as test auto unk = cast(IUnknown)pUnk; write("Calling AddRef: "); unk.AddRef(); //check that the actual pointers involved are identical writefln("comClassObject: %x", pointer(comClassObject)); writefln(" pInterface: %x", pointer(pInterface)); writefln(" unknown: %x", pointer(unknown)); writefln(" pUnk: %x", pointer(comClassObject)); writefln(" unk: %x", pointer(comClassObject)); } //examine a class or interface //as its actual pointer (no cast) union vunion(REF) { REF refvar; void* ptr; this(REF r) { refvar = r; } } void* pointer(REF)(REF refvar) { return vunion!REF(refvar).ptr; } ``` Output: ``` Calling QueryInterface: Called QueryInterface. Called AddRef. Calling AddRef: Called AddRef. comClassObject: 21de9ef0010 pInterface: 21de9ef0010 unknown: 21de9ef0010 pUnk: 21de9ef0010 unk: 21de9ef0010 ``` Without `extern(C++)` this produces a crash because the vtbl[] has QueryInterface in slot 1 according to the D ABI docs linked in the above code, with type information in slot 0 where COM expects QueryInterface to be. However, I wondered whether the `extern(Windows):` qualification was operating inside an `extern(C++):` class, or were those calls accidentally successful with mismatched calling conventions, and the stack/registers were in some way silently corrupted. So I commented `extern(Windows):` out and recompiled and it still worked! So then I found out about this. [Windows x64 Calling Conventions](https://stackoverflow.com/questions/66398953/writing-a-microsoft-fastcall-64-bit-assembly-function convention and that apparently includes win32 API calls. So `extern(Windows):` is unnecessary and makes no difference! It seems that if you only care about 64-bits, the annotation `extern(C++):` fixes up a COM compatible vtbl[] and you're in business provided you take care with method order-of-declaration so you're compatible with the COM interface you're implementing. I then compiled and ran at 32-bits where there *is* a well-known difference. With `extern(Windows):` for the methods the program again worked, with output ``` Calling QueryInterface: Called QueryInterface. Called AddRef. Calling AddRef: Called AddRef. comClassObject: 2640010 pInterface: 2640010 unknown: 2640010 pUnk: 2640010 unk: 2640010 ``` whereas with `extern(Windows):` commented out, the output was ``` Calling QueryInterface: Called QueryInterface. object.Error (0): Access Violation ---------------- 0x00A91045 0x00A910ED 0x00A9FAF7 0x00A9FA57 0x00A9F8C6 0x00A9B2EC 0x00A912A7 0x76A4FCC9 in BaseThreadInitThunk 0x770E7C5E in RtlGetAppContainerNamedObjectPath 0x770E7C2E in RtlGetAppContainerNamedObjectPath ``` suggesting a calling convention mismatch. Either way, COM can apparently be implemented with classes and not interfaces. It seems this behavior of `extern(C++):` classes in D comes from the way the [C++ single inheritance interface in D](https://dlang.org/spec/abi.html#interfaces) works. We might expect that the vtbl[] of a C++ interface in D starts without D's type information which is irrelevant to C++, and in fact by the above proof-of-concept conforms to the COM standard. Apparently the vtbl[] of an `extern(C++):` class in D counts as a C++ interface in D. Is there any text in the documentation which when juxtaposed would enable me to overtly infer all of this? I can't find such. Please confirm or deny that these conclusions are correct in general.
Mar 23
On Thursday, 21 March 2024 at 16:10:25 UTC, Walter Bright wrote:You can invent your own COM-like system in D by using interfaces, but they won't work with Windows COM code.I'm not sure we understand each other. I am not interested in anything COM-like that doesn't interoperate with the outside world of COM, but perhaps you were making the point that other suggestions in this thread simply won't work. I'm trying to write some useful abstractions for COM so as to lower the high administrative friction of writing COM code. I am working in the ImportC/Use-Windows-Headers situation because that provides an environment rich in existing definitions in which to do that, and I want my machinery to work with any new COM header files in the future. I definitely will be using your D interface IUnknown from Druntime because the D language has given it special status that as you have clarified above in this thread cannot be simulated in another way with D classes. Thank you for making it clear in detail that COM can only be approached with classes by using Druntime's IUnknown. I'll just have to work around there being two GUID type definitions that are essentially identical when I do that, one from Windows' `guiddef.h` and one from Druntime's `basetyps.d`, the second used in Druntime's IUnknown.QueryInterface, the first used everywhere by Windows.
Mar 21
On Thursday, 21 March 2024 at 17:51:17 UTC, Carl Sturtivant wrote:I definitely will be using your D interface IUnknown from Druntime because the D language has given it special status that as you have clarified above in this thread cannot be simulated in another way with D classes.It seems I spoke too soon. I have a proof of concept, not using core.sys.windows.unknwn.IUnknown that works compiled at both 32 and 64 bits for Windows. I will continue with details in this thread.
Mar 23
On Tuesday, 19 March 2024 at 18:35:38 UTC, Richard (Rikki) Andrew Cattermole wrote:Basically what you are seeing is normal C++ class behavior.Yes, C++ classes in D got it right as far as casting a D reference to a pointer goes, so as to fit in neatly with the external C++ world. That's a reason for COM classes in D to do the same thing and not produce a meaningless bogus result. The same mechanism can be used as is already implemented for C++ classes. As you point out, the only difference is that the functions pointed to in the Vtable are stdcall for COM.
Mar 19
i've also run into problems with IUnknown. https://issues.dlang.org/show_bug.cgi?id=23819 there's some magic happening that's messing up the vtable.
Mar 20
On Wednesday, 20 March 2024 at 11:33:32 UTC, Zoadian wrote:i've also run into problems with IUnknown. https://issues.dlang.org/show_bug.cgi?id=23819 there's some magic happening that's messing up the vtable.Yes, and it would be nice if a short very direct summary of the situation with all of these interacting binary features was written. When I have found out everything I'm going to do that.
Mar 20