core.sys.windows.com.ComObject apparently has wrongly laid out Vtable

Carl Sturtivant sturtivant at gmail.com
Tue Mar 19 04:40:01 UTC 2024


Any insight appreciated. Details:
```
>dmd --version
DMD64 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.



More information about the Digitalmars-d mailing list