March 20
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?
March 20
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?

March 20
On 20/03/2024 6:56 PM, Carl Sturtivant wrote:
> 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?

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.
March 20
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.

March 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.
March 20

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

#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;
};
March 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++).

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.

March 20

Here's the test of that extern(C++) ComObject.

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.

March 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?

March 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 :-(