1

In this C++/COM shell extension tutorial, the programmer demonstrates (for purposes of edification) that you can forego calling QueryInterface() and simply pass around a general object instead. At least that works when implementing DllGetClassObject(). He says the purpose of QueryInterface() is just to have each object speak for itself as to whether it supports a given interface.

Microsoft, meanwhile, seems to say that QueryInterface() is necessary to get a pointer to a specific interface on an object.

So to what extent is QueryInterface() necessary? Is there any time when calling QueryInterface() is absolutely essential, and without it the code wouldn't work? Or is getting the object itself technically sufficient, as the video tutorial suggests?

  • 2
    It is not "technically sufficient", it is UB. Undefined behavior can work by accident. Casting to the interface type is required in C++, the job of QI. – Hans Passant Jun 04 '19 at 00:40
  • 1
    It's absolutely essential if you're not 100% certain that a certain interface you want to use is supported, and it's a good idea to use it every time. The tutorial is teaching wrong information and improper coding techniques. Find a better one. – Ken White Jun 04 '19 at 01:12
  • on the same object different interfaces can have different binary pointers. so call QI is absolutely necessary, if you dont know layout of object – RbMm Jun 04 '19 at 01:32
  • 1
    *He says the purpose of QueryInterface() is just to have each object speak for itself as to whether it supports a given interface.* this is absolute false. QI easy can return another binary pointer, not the same on which it called: p->QI(iid, &q) and (void*)p != (void*)q can be – RbMm Jun 04 '19 at 01:36
  • So does each binary pointer point to the same object, but to different interfaces within that object? I always thought a pointer to an object gives you access to all of that object's methods, but I guess I was wrong? –  Jun 04 '19 at 01:40
  • @amt528 of course you wrong. if object implement 2 interfaces which not is subset each other - pointer of this 2 interfaces always will be different. – RbMm Jun 04 '19 at 01:44
  • @amt528 that is not a requirement, no. QueryInterface is allowed to return a pointer to a completely different object that implements the requested interface on the queried object's behalf – Remy Lebeau Jun 04 '19 at 01:44
  • @RemyLebeau really "object" in com sense always will be the same. but pointers to interfeces (vtables inside object) will be different. – RbMm Jun 04 '19 at 01:46
  • if 2 interfaces have different method on n-place in vtable - they basically can not be binary equal. – RbMm Jun 04 '19 at 01:56
  • So is an object in a COM sense different from an object in a C++ sense? And if so, how? –  Jun 04 '19 at 01:56
  • @amt528 - are you not understand that different interfaces (if one not subset of another) can not have the same binary value ?! – RbMm Jun 04 '19 at 01:57
  • 1
    @RbMm I meant what I said. A queried object can allocate and return an interface pointer to a completely different object in memory. For instance, in ATL, by using [tear-off classes](https://learn.microsoft.com/en-us/cpp/atl/tear-off-interfaces-classes). There is no requirement that `QueryInterface()` has to return an interface for the same object that is being queried, *except* when the queried interface is `IUnknown`, since COM uses that for identity checks. If two interfaces are queried for `IUnknown` and they return the same pointer, then they are implemented by the same object – Remy Lebeau Jun 04 '19 at 01:58
  • @RemyLebeau - yes, tear-off is possible. but in general sense - this is part of the same object. but if object implement multiple interfaceses - all it have own and different vtables inside object body. so even without tear-off we all time will be got different pointers to different interfaceses which object implement – RbMm Jun 04 '19 at 02:01
  • @RemyLebeau i be not even look for tear-off (so complex) here. even basic objects, which implement 2 intefaceses (not subset each other) - always will be different interfaceses (pointer to vtable pointer) pointer – RbMm Jun 04 '19 at 02:03
  • @RbMm I don't think we are talking about the same thing. Different interfaces DO NOT need to be implemented by the same object, or the same vtable. I've done this many times, with and without tear-off classes. I'm done arguing about this. – Remy Lebeau Jun 04 '19 at 02:03
  • @RemyLebeau - yes, agree. about different. but i think can more easy example be, without tear-off – RbMm Jun 04 '19 at 02:05
  • @RemyLebeau *Different interfaces DO NOT need to be implemented by the same the same vtable.* - of course they CAN NOT have the same vtable. this i say all time. but may be on very bad english – RbMm Jun 04 '19 at 02:07
  • Raymond describes it quite well here: https://devblogs.microsoft.com/oldnewthing/20040205-00/?p=40733 – Jonathan Potter Jun 04 '19 at 07:40

1 Answers1

5

No, as a general rule you cannot skip calling QueryInterface unless you know the interface pointer you have is already correct.

If we imagine a object that implements IFoo and IBar the layout might look something like this:

VT
  IFoo_QueryInterface(...)
  IFoo_AddRef()
  IFoo_Release()
  IFoo_FooFight(int, int)
VT
  IBar_QueryInterface(...)
  IBar_AddRef()
  IBar_Release()
  IBar_BarBarBar(int)

A instance of the object might point to IFoos v-table pointer or IBars v-table pointer. Calling the 4th method without knowing which one it really is will crash because the parameter count is not the same. And even if the signature was the same, calling arbitrary methods is not a good idea.

The video you are referring to gets away with it only because callers of DllGetClassObject usually only ask for IClassFactory. But even there it is not safe because somebody might ask for IClassFactory2 instead. Correct DllGetClassObject implementations should therefore also call QueryInterface.

I would recommend trying to code in C instead of C++ when learning COM fundamentals, this forces you to handle all v-table indirection yourself. Take a look at this series for details.

Anders
  • 97,548
  • 12
  • 110
  • 164
  • the vtable (array of pointers to functions) is never placed in object memory (not part of it layout) . this will be very not efficient have all this pointers in object body. really object containing single pointer to vtable in self body. interface - this is pointer to pointer to vtable. however because vtable for bar and foo different - the pointers to pointer to it also always different. example with `IClassFactory2` unfortunate because it extension of `IClassFactory` - as result pointer to both interfacess will be always the same in practic – RbMm Jun 04 '19 at 06:39
  • 1) Whether the table is part of the object or "global" is an implementation detail and not really relevant for this discussion. 2) Yes CF2 is an extension and the pointers are most likely the same but it would still crash if somebody calls a CF2 method when you only really implemented CF1. – Anders Jun 04 '19 at 10:15
  • *Whether the table is part of the object or "global" is an implementation detail* yes (despite doubt that exist implementation where this table placed to object). but this is really not relevant, because interface point not to this table but to pointer to this table. say for `class Demo : IFoo, IBar` interfaces will be `static_cast(this)` and `static_cast(this)` - always binary different pointers. – RbMm Jun 04 '19 at 10:57
  • 1
    I did sort-of try to display the vtbl pointer in the layout with indentation but ASCII art is not my strong suit. Did we go complete circle now? The OPs question is basically whether the pointers are the same. – Anders Jun 04 '19 at 11:08
  • So is casting essentially repositioning the pointer to the object such that's directed toward another specific interface? –  Jun 04 '19 at 15:49
  • @amt528 depends on the cast type, not C casts. But only the object implementer can cast **this**, as the user of a interface you cannot, you must use QueryInterface. – Anders Jun 04 '19 at 16:40