2

I am interested in comparing two IDirect3DDevice9 COM pointers, which inherit IUnknown, for equality. Based on a similar topic, I understand you can QI IUnknown for both pointers, and compare the result for equality. However, I am curious if we can accomplish the same thing via an IsEqual method that directly takes two IUnknown pointers, let inheritance determine the relevant IUnknown pointers, and use that for the equality check instead. Based on my experiment, that seems to work and doesn't require the QI and release operation (or maybe that's done implicitly). If there's any caveats that warrant this suggestion invalid, please let me know.

BOOL IsEqual(IUnknown *pA, IUnknown *pB)
{
    return (pA == pB);
}

BOOL IsEqual (IDirect3DDevice9 *pDevice1, IDirect3DDevice9 *pDevice2)
{
    IUnknown *u1, *u2;

    pDevice1->QueryInterface(IID_IUnknown, &u1);
    pDevice2->QueryInterface(IID_IUnknown, &u2);

    BOOL areSame = u1 == u2;
    u1->Release();
    u2->Release();

    return areSame;
}
Community
  • 1
  • 1
lancery
  • 658
  • 1
  • 6
  • 18
  • Actually, I considered the cases more, and I think the first IsEqual check isn't guaranteed to work as intended because the IUnknown pointers passed into the methods merely represent the same IDirect3DDevice9 pointers casted. Therefore, if the two pointers were different to begin with, the equality check would have failed. – lancery Feb 22 '16 at 09:24
  • 1
    No, your compiler does not know how to call QueryInterface(). It will assume the pointer is already compatible, since IUnknown is the base interface, and will not attempt to convert it. Which produces the wrong result, QI is required. You could use the CComQIPtr class in C++ to take care of both the QI call and the Release() call. – Hans Passant Feb 22 '16 at 10:04
  • That could *possibly* work if all COM components were implemented as C++ classes with multiple inheritance and aggregation didn't exist, but it's not the case. In fact, the very *COM Tutorial Samples* recommend implementing interfaces in nested classes, which makes aggregation support easier. Which makes querying for IUnknown mandatory. – Medinoc Feb 22 '16 at 11:04
  • What question do you really want answered: "are these two pointers equal" or "are these two pointers implemented by the same object"? If the former, then yes, just go ahead and compare them with `==`; but it's not clear what this answer would be useful for. If the latter, then you need to compare COM identities (defined as "`IUnknown` pointer obtained by querying specifically for `IID_IUnknown`), because a single object typically implements more than one interface and thus has more than one `IUnknown` implementation. See also: `CComPtr::IsEqualObject` – Igor Tandetnik Feb 22 '16 at 19:29

1 Answers1

2

Here's some example code with multiple inheritance. Here IUnknown happens twice in the hierarchy but any other interface could happen twice too.

I'm using features that aren't specific to Visual C++ so that this code is easier to try on online compiler services.

#include <stdio.h>

class IUnknown {};

class IInterface1 : public IUnknown {};

class IInterface2 : public IUnknown {};

class Class : public IInterface1, public IInterface2 {};

int main() {
    Class object;

    // Please note that only implicit conversions are used - 
    // exactly as if you were passing Derived* pointers into
    // a function that accepts Base* pointers      
    IInterface1* interface1 = &object;
    IUnknown* unknown1 = interface1;
    IInterface2* interface2 = &object;
    IUnknown* unknown2 = interface2;

    printf( "%p %p %s", unknown1, unknown2,
        (unknown1 == unknown2) ? "true" : "false");
}

This code outputs this:

0xbfc8e9de 0xbfc8e9df false

So no, you cannot rely on upcasts to check indentity - depending where you start the walk you can get to different destinations for the same object. This is exactly why this code won't compile:

 IUnknown* unknown = &object;//WON'T COMPILE

because the conversion is ambiguous. Only once you've told which inheritance path to use will that code compile. And once you've chosen the path you may end up with distinct subobjects of the same base class which will of course be located at distinct addresses.

sharptooth
  • 167,383
  • 100
  • 513
  • 979