March 21
On Thursday, 21 March 2024 at 16:10:25 UTC, Walter Bright wrote:
> On 3/21/2024 7:30 AM, Carl Sturtivant wrote:
>> 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.

Thank you for clarifying!

March 21

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.

March 23

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.

March 23
On Thursday, 21 March 2024 at 16:10:25 UTC, Walter Bright wrote:
> On 3/21/2024 7:30 AM, Carl Sturtivant wrote:
>> 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.

I don't agree: this works in a proof of concept compiled at both 32 and 64 bits. Details below.
March 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.

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. With a 64-bit compilation there's only one calling 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 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.

March 23

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:

>

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

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.

March 24
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.
March 25
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:
>> 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.

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.
1 2 3 4
Next ›   Last »