1

The ID3D12GraphicsCommandList interface inherits from ID3D12CommandList. So, if I have a ID3D12GraphicsCommandList object, how do I get get the corresponding ID3D12CommandList object?

  1. Will typecasting work?
    ID3D12GraphicsCommandList *gcl = ...;
    ID3D12CommandList *cl = (ID3D12CommandList*)gcl;
  1. Will QueryInterface work?
    ID3D12GraphicsCommandList *gcl = ...;
    ID3D12CommandList *cl;
    HRESULT result = ID3D12GraphicsCommandList_QueryInterface(gcl,
                                                              &IID_ID3D12CommandList,
                                                              (void**)&cl);
  1. Do I need to do something else?

Thanks.

  • you already have it. nothing need todo, – RbMm Jul 01 '21 at 06:55
  • @RemyLebeau - even `cl = gcl` not need. `gcl` already `ID3D12CommandList*` – RbMm Jul 01 '21 at 06:58
  • @RbMm I need to store `gcl` inside a `ID3D12CommandList []` (an array), thus I need to convert into `cl`. – marked-off-topic Jul 01 '21 at 07:13
  • @RbMm since the question is tagged C and not C++, the issue is a bit more complicated. What you say is true in C++, but C doesn't have classes or inheritance, so accessing a "base" interface from a pointer to a "derived" interface is a little bit more work. So, I would stick with using `Queryinterface()` to keep it simple and not worry about the details. – Remy Lebeau Jul 01 '21 at 07:15
  • @RemyLebeau Just to confirm, `QueryInterface` can be used to access parents too? (Because I have only ever used it to access children) – marked-off-topic Jul 01 '21 at 07:22
  • @marked-off-topic `Queryinterface()` can be used to access *any* interface that the underlying object implements. – Remy Lebeau Jul 01 '21 at 07:25
  • `QueryInterface` is to be taken literally: It asks an object behind an interface pointer whether it implements any given interface. That interface can be up or down the inheritance chain, or not be part of the inheritance altogether. A common example are [dual interfaces](https://learn.microsoft.com/en-us/windows/win32/com/dual-interfaces), with `IDispatch` usually implemented as a sibling to the VTable-based interface(s). So yes, using `QueryInterface` is the correct way. – IInspectable Jul 01 '21 at 07:42
  • not note that this is *c* (not *c++*) in this case simply use cast `a[i] =(ID3D12CommandList*)gcl`. not need call `QueryInterface` because you already have pointer to `ID3D12CommandList*` – RbMm Jul 01 '21 at 07:57
  • @rbm That makes assumptions about object layout that aren't backed by COM's rules (or "nano-COM"'s as used by DirectX). – IInspectable Jul 01 '21 at 08:07
  • @IInspectable _ no, no any assumptions about object layout here. because `ID3D12GraphicsCommandList` inherited from `ID3D12CommandList` – RbMm Jul 01 '21 at 08:11
  • @rbm COM's interface inheritance does not mandate any particular language-level implementation. Any given interface implementation is free to use any given object layout, including an object layout that - by coincidence - will not require pointer adjustments. Though really, you've been wrong in the past, provably wrong even, and wouldn't even consider that you are. Why should it be any different today? – IInspectable Jul 01 '21 at 08:14
  • @IInspectable - vtable for interface `ID3D12GraphicsCommandList` containing vtable of `ID3D12CommandList` at begin and any methods of `ID3D12CommandList` can be called by this `vtable`. – RbMm Jul 01 '21 at 08:17
  • @marked-off-topic this is C part of SO. Not C++. And not "Direct 3D". COM implementation is in C. Can you please give C code COM question? Supported with IDL, please. – Chef Gladiator Jul 01 '21 at 10:16
  • @che This *is* C code. There is no IDL required either, since the Windows SDK provides pre-generated headers. If you are curious to see the IDL, *d3d12.idl* ships with the SDK as well. This question is DirectX-specific, too, in that DirectX uses a COM derivative, sometimes called "lightweight COM" or "nano-COM". It follows COM's semantic rules, but doesn't use (most of) the COM infrastructure provided by the OS. – IInspectable Jul 01 '21 at 10:32
  • Just stunningly, of course, the elementary thing is not clear - if we can call any method of `I2` (inherited from `I1`) with pointer `p` - we can call any method of `I1` with the same pointer p - simply because any method of `I1` - will be method of `I2`.. – RbMm Jul 01 '21 at 11:06

4 Answers4

5
  1. Will typecasting work?

No, not in C. Requesting a different interface through an interface pointer may require pointer adjustments. Simply reinterpreting a pointer to one interface as a pointer to another interface will break in those circumstances (see below for a more in-depth exploration).

In C++ this can be made to work by supplying a user-defined conversion function, though it is extremely brittle, and can spectacularly break in subtle and not-so-subtle ways.

  1. Will QueryInterface work?

Yes. It's the correct approach to request a different interface through an interface pointer. The code you provided is correct.

  1. Do I need to do something else?

No, not really, as long as you follow COM rules. One detail is frequently overlooked: A successful call to QueryInterface increases the reference count on the interface, so you will have to Release every interface that was returned from a call to QueryInterface.


Why casting isn't safe

So if IDerived inherits from IBase then why isn't the obvious choice, a pointer cast from IDerived* to IBase*, valid? The TL;DR is, because COM doesn't provide the guarantees that would make this valid.

COM is mind-numbingly minimalistic in its requirements. In fact,

The only language requirement for COM is that code is generated in a language that can create structures of pointers and, either explicitly or implicitly, call functions through pointers.

This allows for a wide range of programming languages to be used in implementing COM interfaces. The flip side of this is that COM offers very few guarantees in how the ABI maps to language-level constructs. This is particularly true for interface inheritance:

Inheritance in COM does not mean code reuse.

An implementation of IDerived can choose to reuse IBase's implementation, or provide its own implementation. It also allows for calls to IBase's interface to have different behavior depending on which interface (IDerived or IBase) it is invoked on. That's flexible but with the pitfall that navigating an interface hierarchy through pointer casts is not guaranteed to work.

But there's more! COM has another rule that's trivially easy to understand, yet frequently overlooked:

From a COM client's perspective, reference counting is always done for each interface. Clients should never assume that an object uses the same counter for all interfaces.

Again, this gives implementations lots of flexibility but requires clients to meticulously manage their interface pointers. QueryInterface is the tool used by an implementation to track outstanding interface references. Casting pointers sidesteps this crucial management task, creating the opportunity to wind up with an interface pointer whose reference count is zero.

Those are the rules and derived guarantees at play. Now, in reality, pointer casts will surprisingly often appear to work. So if you're a developer that doesn't much differentiate between code that's correct, and code that hasn't failed yet, then by all means go ahead and cast pointers to your heart's delight.

If, on the other hand, you're a developer that takes pride in delivering software that works by virtue of being correct, then QueryInterface is always required to navigate an implementation's interface surface.


Ok, but DirectX doesn't actually use COM!

True. DirectX uses a small subset of COM, frequently referred to as Nano-COM. While much of COM doesn't apply, the ABI aspects of COM do. Since this answer talks about the ABI aspects only, it applies to COM and DirectX alike.

See Microsoft Docs.

Chuck Walbourn
  • 38,259
  • 2
  • 58
  • 81
IInspectable
  • 46,945
  • 8
  • 85
  • 181
  • not need any pointer adjustments in this case – RbMm Jul 01 '21 at 08:08
  • @rbm Based on which rule? I'm not asking whether you can find an implementation where this doesn't break. I'm asking for a specification that asserts that this **will always work**. – IInspectable Jul 01 '21 at 08:12
  • Based on understanding. if object implement 2 interfaces - *I1* and *I2* which not inherit one from another - always will be different vtables and pointer adjustment. if `I2 : I1` - we can use common vtable - never need pointer adjustment – RbMm Jul 01 '21 at 08:16
  • 1
    @rbm Right, we **can** use a common vtable. Now, COM doesn't **require** vtable re-use to implement interface inheritance. The only strict requirement that COM makes is that an object that inherits an interface must implement that interface's API. And that's the entirety of the assumptions we can safely make. – IInspectable Jul 01 '21 at 08:26
  • not need any assuption here - any method of `I1` can be called via `I2` pointer, if `I2` inherit from `I1`. and object which implement `I1`, `I2` must implement this behavior. even if exist 2 or more `vtables` to `I1` inside object. if you not understand this - this is only your problem – RbMm Jul 01 '21 at 08:31
  • 2
    @RbMm You seem to be confusing between API and ABI. `I2` will support `I1` API, but might not be ABI compatible (hence the question). If that is so, then typecasting would not work. Now, it is possible that the current MSVC toolchain implements it in a ABI compatible fashion, but the question was about COM in general and not one particular implementation of it. – marked-off-topic Jul 01 '21 at 09:28
  • @marked-off-topic - no, i am not confused and my answer is correct. this concrete answer is wrong. try ask yourself - are you can call **any** methods of `ID3D12GraphicsCommandList`with *gcl* pointer ? – RbMm Jul 01 '21 at 09:31
  • @marked-off-topic `I2` of course full abi compatible wit `I1`. this is not implementation details – RbMm Jul 01 '21 at 09:32
  • @marked-off-topic - you have `ID3D12GraphicsCommandList* gcl` - you can call `gcl->GetType` for instance ? with **exactly** *gcl* as is ? – RbMm Jul 01 '21 at 09:35
  • @RbMm this is C part of SO. Not C++. And not "Direct 3D". – Chef Gladiator Jul 01 '21 at 10:12
  • @ChefGladiator - and how language *c* here related ? com interface at all designed for use from different languages. i also can ask you key question - are are you can call any methods of *ID3D12GraphicsCommandListwith* pointer to this interface - yes or no ? – RbMm Jul 01 '21 at 10:14
  • @rbm Asking the wrong question. The answer to that question is simple: Yes, of course! The question you should be asking is: Is it *guaranteed* to be safe to cast from an interface pointer to an interface pointer it inherits? The answer to that question is also quite simple: Hell, no, that is nowhere near guaranteed. And yes, the OP is correct, you *are* confusing the API for the ABI (or one specific implementation of the ABI for The ABI). The ABI makes none of the promises you assess were to be true. – IInspectable Jul 01 '21 at 10:38
  • @IInspectable - *Is it guaranteed to be safe to cast from an interface pointer to an interface pointer it inherits?* - yes, 100%. this is *guaranteed* by sense which you can not or not want understand. if we can call any methods of *I2* with pointer *p* and *I1* base class for *I2* - we can call any methods of *I1* with *p* too - because it and *I2* method also, which we can – RbMm Jul 01 '21 at 10:41
  • you look like confuse cast *I2* from *I1* which can be require change binary value of pointer. and not via `static_cast` bit only *QueryInterface*. but cast *I1* from *I2* always ok and for this not need use `QI` call. the `QI` can even return another binary pointer - in this case - **both** pointers will be ok and can be used. how minimum 1 of it will be adjustor thunk. may be and both. but this is already implementation details. – RbMm Jul 01 '21 at 10:45
  • @rbm Show me the **specific COM rule** that makes that statement correct. You seem to be struggling to find it. Take that as a hint. – IInspectable Jul 01 '21 at 10:46
  • are you can for instance eat ? or you need some rules and instructions for this ? i again describe you sence - if we have pointer to `I2* p` and `I2 : I1` (hope you undertand this notation) - we can call any methods of `I1` with **excatly** *p* pointers as is. simply because this is the `I2` methods too. all what i can advice you - ask for instance Raymond Chen. may be he can better explain this for you. why we can cast `I2` to `I1` but can not cast `I1` to `I2` without *QueryInterface* call. – RbMm Jul 01 '21 at 10:50
  • @rbm *"`I2 : I1` (hope you undertand this notation)"* - Oh yeah, **I** understand what you were trying to express. It's **you** that's missing what it means. Let me clue you in: [Inheritance in COM does not mean code reuse.](https://learn.microsoft.com/en-us/windows/win32/com/iunknown-and-interface-inheritance) Give it some time to digest that information. It may appear trivial, but is much more subtle than you'd expect. **If** you give it enough time to digest. – IInspectable Jul 01 '21 at 10:55
  • valid pointer to `I2` will be also valid pointer to `I1` too (if `I2 : I1`) by direct description. here not need any rules. `QI` from `I2` to `I1` can even return to you another binary value for pointer. in this case **both** pointers will be correct and can be used (despite different binary values). because will be different vtables and functions in this case - inside function object which implement interface already adjust pointer value, usually to object *this* pointer – RbMm Jul 01 '21 at 10:56
  • Just stunningly, of course, the elementary thing is not clear - if we can call any method of `I2` with pointer `p` - we can call any method of `I1` with **the same** pointer `p` - simply because any method of `I1` - will be method of `I2`.. – RbMm Jul 01 '21 at 11:05
  • @IInspectable - look like you have very weak knowledge in this topic. yes - `QI` can return **another** binary value for `I1`. but in this case **both** pointers will be ok. why ? because it will point do **different** tables of functions. and will be different pointers in this table. are you listen about [adjustor thunk](https://devblogs.microsoft.com/oldnewthing/20040206-00/?p=40723) ? may be you try ask Raymond Chen about this question ? and i never say that `QI` simply do reinterpret cast. i insist - can be different binary pointers after `QI` convert `I2` to `I1` - but **both** is ok – RbMm Jul 01 '21 at 11:46
  • @IInspectable and my answer about guid compare also correct - count of upvote or downvote - not a proof – RbMm Jul 01 '21 at 11:48
  • if `I2* p` and `I2 : I1` we can call any `I1` methods with *p* pointer. as result *p* valid pointer to `I1` interface. this is enough. for more understanding - even if can be another `I1* q` and `q != p`(on binary level) - in this case simply it pointer to another vtable with another functions. in one (or both) table will be pointers to "adjustor thunks". and both p and q will be correct. and note - i not say that reverse cast (I1 -> I2) is correct. reverse is wrong – RbMm Jul 01 '21 at 11:53
  • *Inheritance in COM does not mean code reuse.* and how this is related to cast(assign) `IDerived* p;` pointer to `IBase* q = p;` ? use after this q (`q->method(..)`) will have the exactly same binary (ABI) effect as call `p->method(..)`. this is equal to call methods of `IBase` by `IDerived` pointer, which is of course absolute ok and legal. – RbMm Jul 02 '21 at 21:56
  • 1
    It is neither ok nor legal. I explicitly explained this in my answer. Inheritance in COM means interface reuse. Interface reuse does not mean behavior reuse. It is perfectly legal for `IBase::method()` to exhibit different behavior from `IDerived::method()`. Thus, if you cast an `IDerived* p` to `IBase*` and invoke `p->method()` you get `IDerived`'s behavior, but you wanted `IBase`'s behavior. – IInspectable Jul 02 '21 at 22:43
  • @IInspectable Emily's recent [answer](https://stackoverflow.com/a/68702077/12862673) is interesting, I haven't read the linked spec yet. – marked-off-topic Aug 09 '21 at 18:18
  • I haven't read the linked spec yet either, but the answer derives the same wrong assumptions all over again. I still have a pretty lengthy write-up somewhere on my local file system, that needs a fair amount of polish before it can be published. When I find a bit of time I will make sure to do that, and hopefully explain some of the minutiae in a more approachable fashion. – IInspectable Aug 10 '21 at 08:48
2

It is always okay to upcast a COM interface pointer

The actual spec can be found here, in MS-Word format (Microsoft no longer hosts it to my understanding):

http://web.archive.org/web/20030201093710/http://www.microsoft.com/Com/resources/comdocs.asp

It is very easy to read, and clarifies a lot of details about COM which aren't made obvious on MS-docs currently.

Per the spec, a COM interface pointer is defined as a pointer to a pointer to an array of function pointers (called the VTable). The first argument to every entry in the VTable is the interface pointer itself. An interface "Inherits" from another interface by listing the other interface's functions first, followed by its own. Note, that this requirement means that only single-inheritance is supported, as explained in The COM Specification, chapter 2 part 1.2 (emphasis mine):

Interfaces and Inheritance

COM separates class hierarchy (or indeed any other implementation technology) from interface hierarchy and both of those from any implementation hierarchy. Therefore, interface inheritance is only applied to reuse the definition of the contract associated with the base interface. There is no selective inheritance in COM: if one interface inherits from another, it includes all the functions that the other interface defines, for the same reason than an object must implement all interface functions it inherits. Inheritance is used sparingly in the COM interfaces. Most of the pre-defined interfaces inherit directly from IUnknown (to receive the fundamental functions like QueryInterface), rather than inheriting from another interface to add more functionality. Because COM interfaces are inherited from IUnknown, they tend to be small and distinct from one another. This keeps functionality in separate groups that can be independently updated from the other interfaces, and can be recombined with other interfaces in semantically useful ways. In addition, interfaces only use single inheritance, never multiple inheritance, to obtain functions from a base interface. Providing otherwise would significantly complicate the interface method call sequence, which is just an indirect function call, and, further, the utility of multiple inheritance is subsumed within the capabilities provided by QueryInterface.

Note that last portion: The COM design uses single-inheritance specifically so that it is simple to call the base interface from a child interface, i.e. specifically so that you can just cast the interface pointer.

The structure of the interface VTable (and therefore, the viability of pointer casting) is confirmed in several other places as well. See the description of inheritance in the description of the IDL, chapter 13, part 1.4 (emphasis mine):

Interface Inheritance

Single inheritance of interfaces is supported, using the C++ notation for same. Referring again to [CAE RPC], page 238:

<interface_header> ::= 
  <[> <interface_attributes> <]> interface <Identifier> [ <:> <Identifier> ]

For example:

[object, uuid(b5483f00-4f6c-101b-a1c7-00aa00389acb)]
  interface IBar : IWazoo {
      HRESULT Bar([in] short i, [in] IFoo * pIF);
      };

cases the first methods in IBar to be the methods of IWazoo.

Pointer casting is even explicitly called out as okay when describing inheritance from IUknown in Chapter 3, part 1.3 (emphasis mine):

The IUnknown Interface

This specification has already mentioned the IUnknown interface many times. It is the fundamental interface in COM that contains basic operations of not only all objects, but all interfaces as well: reference counting and QueryInterface. All interfaces in COM are polymorphic with IUnknown, that is, if you look at the first three functions in any interface you see QueryInterface, AddRef, and Release. In other words, IUnknown is base interface from which all other interfaces inherit. Any single object usually only requires a single implementation of the IUnknown member functions. This means that by virtue of implementing any interface on an object you completely implement the IUnknown functions. You do not generally need to explicitly inherit from nor implement IUnknown as its own interface: when queried for it, simply typecast another interface pointer into an IUnknown* which is entirely legal with polymorphism.

We can also confirm the interface layout by examining the header file definitions for those interfaces, which all include a vTable which contains the base vTable as a first element (to use d2d1.h as an example):

typedef struct ID2D1ResourceVtbl {
    IUnknownVtbl Base;

    STDMETHOD_(void, GetFactory)(ID2D1Resource *This, ID2D1Factory **factory) PURE;
} ID2D1ResourceVtbl;

In fact, the very functions one might use to avoid casting when calling base interface methods are actually macros which perform casts!

#define ID2D1Resource_QueryInterface(this,A,B) (this)->lpVtbl->Base.QueryInterface((IUnknown*)(this),A,B)
                                                                                    ^^^^^^^^^

Pointer upcasts don't just work by coincidence, it's both guaranteed by the spec, and verifiable in your header files.

Addressing concerns raised by others:

What about pointer adjustments?

Because COM only supports single, pure-virtual inheritance, there is never a need for pointer adjustments when performing a cast. This is deliberate, as pointer adjustments when casting are very C++-specific behavior and COM tries to be language-independent. You can confirm this by looking at the machine-code output by your C++ compiler when casting COM interface pointers. There will be no adjustments.

What about different interface method implementations based on which interface the pointer actually points to?

That's why you always call functions using the vTable! This is the magic of polymorphism: that a function can be dispatched dynamically based on the type of the object without the caller knowing the type of the object. An object theoretically could do something differently based on which interface pointer you used to call a given interface method, but that's its business, not the client's. (edit: I've added some more details about both this and reference counting below)

But what about separate reference counts for different interfaces?

Again, that's why you always call functions using the vTable. The call is guaranteed to be dispatched to the correct place. There would be no point in having a vTable otherwise (we could just use static dispatch). Note that you should call AddRef whenever you make a copy of an interface pointer, regardless of whether or not that copy was made using a cast.

Final Notes/Warnings:

  • Just because you can always cast to a base interface does not mean that the pointer you get is the same one that would have been returned had you called QueryInterface. This detail isn't important most of the time but comes into play if you ever have check whether two interfaces belong to the same COM object. You can't just compare the interface pointers directly; you have to compare the values returned by calling QueryInterface(IID_IUnknown, ...) on both.
  • "Up"-casting (implicit casting) is also perfectly valid to do in C++, since C++ compilers are required to conform to similar ABI requirements to what COM lays out (by no coincidence).
  • Pointer-casting is not okay for "cross"-casting or "down"-casting, only "up"-casting (an implicit cast in C++). If I1 does not inherit from I2, you cannot cast to an I2.
  • The first argument to an interface method should only ever be the interface pointer from which you got lpVtbl. Such a mistake should be pretty easy to spot in C, and is not even possible in C++.
  • Notably, the current documentation on COM as hosted on MS-docs is edited slightly from the original spec to remove alot of the concrete details and real-life examples. Specifically, it is difficult to find examples/explanations which involve C, which might mislead the reader into believing that the rules surrounding inheritance simply follow C++ inheritance rules, rather than a very strict subset of them.

In Short:

The example you gave with typecasting will work, because ID3D12GraphicsCommandList inherits from ID3D12CommandList.


Edit: More Notes on interface vs implementation:

As stated above, it is perfectly legal for a COM object to return different pointer from QueryInterface() to the one you would get by upcasting. Some have pointed out that this opens the door for the pointers in the interface vTable to point to completely different functions, and therefore result in different object behaviors when called. While it would be incredibly confusing and awful for a COM object to do this in a way that's visible to the API consumer, it is nevertheless legal per the COM spec. This does not mean that "always use QueryInterface() and never upcast the pointer" is good advice, though.

Note: To reduce confusion, I'm going to call the behavior that you get from the child interface (same as the behavior you get when casting a pointer) the "inherited" behavior, while the behavior of the interface returned by QueryInterface will be the "queried" behavior.

Firstly: different "inherited" and "queried" behaviors would be such a strange thing to do, that any reasonable designer would have to put that information in the object's documentation. It doesn't make sense for the API consumer to always try to account for the possibility of such weirdness, in the same way that it wouldn't make sense to only call COM methods on a Tuesday just because it's not illegal per the spec for a COM object to change its behavior based on the day of the week.

Secondly: There's no guarantee that the "queried" behavior is what you want vs the "inherited" behavior. In fact, if our theoretical object were deliberately designed to have different per-interface behaviors, you would probably want the "inherited" behavior, since it might be more likely to play nice with whatever you were doing with the child interface to begin with.

Finally: Reference counting will continue to work just fine, even if different interfaces correspond to different reference counts. I discussed this somewhat above, but I'll be more specific here. You should always call Release on the same pointer you used to call AddRef, regardless of its type (same type != same pointer, even if they are from the same object). Because the calls to AddRef and Release are dispatched dynamically (the whole point of having a vTable!), calling Release on the correct pointer will decrement the correct reference count. Avoiding pointer upcasting will not save you if you handle this incorrectly.

As an aside: your code should not use the exact value of the reference count for any reason. COM exposes this value for debugging purposes only (say, for when you're tracking down a memory leak), and it is not intended for general program use. Client code's only concern is calling "AddRef" and "Release" at the right times, and the server is responsible for the rest.

Here is some info on MS-Docs about reference counts which might be helpful:

https://learn.microsoft.com/en-us/windows/win32/learnwin32/managing-the-lifetime-of-an-object

Emily
  • 21
  • 2
  • Thanks for both the answer and the link to spec, MSDN seems to have been scrubbed clean of any mention of using COM from C. If it wasn't for Dr. Dobbs, I'll never would have gotten started. – marked-off-topic Aug 09 '21 at 18:22
  • One question though: what is the purpose of QueryInterface then? Is it just for languages that do not have the concept of type casting? – marked-off-topic Aug 09 '21 at 18:24
  • QueryInterface is still necessary when getting interfaces from which your current interface does not inherit, i.e. "down"-casting or "cross"-casting. It is also useful if you do not know in advance which interfaces an object supports (which is why you should always check the return value for success/failure). See: https://learn.microsoft.com/en-us/windows/win32/learnwin32/asking-an-object-for-an-interface – Emily Aug 09 '21 at 19:05
  • This is failing to acknowledge the single-most important detail in understanding COM: Inheritance in COM does not mean code re-use. It boils down purely to interface re-use. The latter makes pointer casts follow the ABI protocol. It's the liberty in implementation that the former allows, which makes pointer casts violate COM's semantic rules. An example: Reference counting is strictly per interface. If you hold an `IFoo* p`, then calling `(IUnknown*)p->AddRef()` will increment the ref-count on the `IFoo` interface, not the `IUnknow` it 'derives' from. – IInspectable Aug 10 '21 at 08:28
  • This subtle detail frequently doesn't manifest itself in any observable fashion, with many COM objects maintaining a single ref-count per object. Though when you *do* encounter an implementation that goes out of its way and maintains per-interface reference counts, you're in for a fun surprise if you insist that `(IUnknown*)p->AddRef()` and the sequence of `QueryInterface` for `IUnknown` and an `AddRef()` call on the returned interface pointer were to have identical behavior. They don't: One increments the reference count on the `IFoo` interface, the other on the `IUnknown` interface. – IInspectable Aug 10 '21 at 08:44
  • Yes, calling QueryInterface on a COM object will do a different thing than casting your interface pointer (as my answer states), but the question was whether or not it's okay to cast an interface pointer to the pointer of a base interface, to which the answer is 100% yes. Yes, calling (IUnknown *)p->AddRef will do the same thing as calling p->AddRef, but that is the behavior you _want_ when calling AddRef on p. Later calling Release on p will also behave as expected, regardless of what it was cast to. – Emily Aug 10 '21 at 10:11
  • Either way, it's not possible to call AddRef _without_ a cast to (IUnknown *), as demonstrated above. – Emily Aug 10 '21 at 10:11
  • @IInspectable: I believe I have found what might be the sticking point based on your comments here and elsewhere, and have edited the answer with some extra information that I hope addresses your concerns. – Emily Aug 11 '21 at 10:52
0

Will QueryInterface work?

Yes.

Do I need to do something else?

No.

The code you have is OK. You can also do like this:

ID3D12GraphicsCommandList *gcl = ...;
ID3D12CommandList *cl;
HRESULT result = gcl->lpVtbl->QueryInterface(gcl,
                                             &IID_ID3D12CommandList,
                                             (void**)&cl);
fpiette
  • 11,983
  • 1
  • 24
  • 46
  • 1
    It's *exactly* what the OP is doing already (except, they are using the macros that are available when `COBJMACROS` is defined). Plus, as always, an answer that doesn't offer rationale is not actually useful. – IInspectable Jul 01 '21 at 07:47
  • 1
    The [`IID_PPV_ARGS()`](https://learn.microsoft.com/en-us/windows/win32/api/combaseapi/nf-combaseapi-iid_ppv_args) macro would be safer, if available: `HRESULT result = gcl->lpVtbl->QueryInterface(gcl, IID_PPV_ARGS(&cl));` – Remy Lebeau Jul 01 '21 at 16:12
-4

in case c++ you already have pointer to ID3D12CommandList because ID3D12GraphicsCommandList inherited from ID3D12CommandList. in case c you need formal cast (ID3D12CommandList*)gcl for use this as ID3D12CommandList* pointer. use QueryInterface not need in concrete case - it can be used of course but no sense do this. again - pointer to ID3D12GraphicsCommandList - already valid pointer to ID3D12CommandList too. because pointer to ID3D12GraphicsCommandList - this is pointer to pointer to table of function (ID3D12GraphicsCommandListVtbl) which is layout compatible (containing at the begin) with ID3D12CommandListVtbl. so any method of ID3D12CommandList can be called via ID3D12GraphicsCommandList pointer as is


if we have pointer to some interface - we can call any method of this interface pointer.

in particular - if we have pointer (gcl) to ID3D12GraphicsCommandList interface we can call any method of ID3D12GraphicsCommandList with this (gcl) pointer.

as result we can call any method of ID3D12CommandList with exactly binary value gcl as is - because method of ID3D12CommandList also method of ID3D12GraphicsCommandList too.

in general if some object implement 2 interfaces I2 and I1 and I2 inherit from I1 ( I2 : I1 in c++ terms) we always can cast I2* pointer to I1* pointer.

Do this in the opposite direction is wrong - if we have pointer to I1* we can not cast it I2*. this already require use QueryInterface. for example - let exist I3 : I1 and object implement all 3 interfaces - I1, I2 and I3. both I2 and I3 inherit from I1 but I2 and I3 not inherit one from another, have different layout. in this case I2 and I3 always will be have different pointers. 2 different vtables. and I1* can point to I3* vtable instead I2* vtable.

so

I2* p2;
I1* p1 = (I1*)p2;// cast need only for c, not need for c++

always ok. but next code is wrong

I1* p1;
I2* p2 = (I2*)p1;// wrong !!

but question was about I2 -> I1 cast, not about I1 -> I2 cast

RbMm
  • 31,280
  • 3
  • 35
  • 56
  • @IInspectable here no any assumptions at all. we can call any methods of `ID3D12CommandList` by using pointer to `ID3D12GraphicsCommandList` as is. because `ID3D12GraphicsCommandList` containing all this methods – RbMm Jul 01 '21 at 08:33
  • Sure, `ID3D12GraphicsCommandList` implements all functions that `ID3D12CommandList` implements. After all, that's COM's definition for interface inheritance. Deriving from that that it were safe to simply reinterpret an interface pointer as another interface pointer is an assumption. Unless you provide the rule(s) in COM's specification that guarantee this assumption, that assumption is unfounded and unbacked. – IInspectable Jul 01 '21 at 08:43
  • You are making different statements. `1` `ID3D12GraphicsCommandList` inherits the `ID3D12CommandList` interface. That's undoubtedly correct. `2` It is thus safe to cast an `ID3D12GraphicsCommandList*` into an `ID3D12CommandList*`. This is wrong, but your answer insists that this were guaranteed to be safe. Again, either provide a link to COM's specification that guarantees this to be safe, or evaluate the possibility that you might actually be wrong. – IInspectable Jul 01 '21 at 08:58
  • @IInspectable - if we have `ID3D12GraphicsCommandList *gcl` - we can call **any** methods of `ID3D12GraphicsCommandList` with this pointer. as result and any methods of `ID3D12CommandList` with **exactly this** pointer - because any methods of `ID3D12CommandList ` - method of `ID3D12GraphicsCommandList` too and we can call it with *gcl*. if `I2 : I1` we **always** can cast `I2` to `I1`. in another direction - cast `I1` to `I2` - this is already may be wrong (always wrong by design) because pointer to `I1` vtable can be not pointer to `I2` vtable. here already need `QI` – RbMm Jul 01 '21 at 09:04
  • *"if `I2 : I1` we **always** can cast `I2` to `I1`"* - Point me to the specific COM rule that makes that assumption a fact and we're talking. Until then this is no more than an optimistic extrapolation of a *specific* implementation detail into a *general* rule that doesn't stand a snowflake's chance in Hell to survive. – IInspectable Jul 01 '21 at 09:19
  • @RbMm this is C part of SO. Not C++. And not "Direct 3D". – Chef Gladiator Jul 01 '21 at 10:13
  • @ChefGladiator - and ? what concrete incorrect from your look ? – RbMm Jul 01 '21 at 10:15
  • Please use only C and in the case of COM, the use of IDL clarifies things greatly. – Chef Gladiator Jul 01 '21 at 10:19
  • @ChefGladiator - i think this is primary *com* question. *com* based on some binary abi, layout, etc. It is not attached to a specific language. already task of language find descriptions corresponding to essence. i describe essence that we can call **any** methods of `ID3D12GraphicsCommandList` with pointer to this interface. as result we can call and any methods of `ID3D12CommandList` with exactly this pointer (without binary modification of pointer) so `QueryInterface` here not need.it can be used, but not need. what here is wrong ? – RbMm Jul 01 '21 at 10:24
  • That is not the essence of COM. The essence of COM implementation is IDL which is compiled into C code. No C++ and certainly no "Direct 3D". ID3D... API is obfuscating the issue here. And a proposed solution. – Chef Gladiator Jul 01 '21 at 10:33
  • @che IDL doesn't get compiled to C code. It is a **language-agnostic** interface description, that tools can convert into C or C++ header files, or something else altogether. And most certainly, IDL **is not** related to a COM object's implementation. At all. It describes the interface. And that's all. It's a mystery to me why you believe that seeing the IDL were enlightening in any way. This question is about COM's guarantees, as they relate to DirectX specifically. – IInspectable Jul 01 '21 at 10:45
  • @IInspectable "provide a link to COM's specification " ? There is no COM specification document, it's always been "whatever MSVC does is the 'specification'". – M.M Jul 01 '21 at 23:35
  • @m.m COM's rules are [spelled out](https://learn.microsoft.com/en-us/windows/win32/com/component-object-model--com--portal). I'm looking for the specific rule that mandates that interface inheritance requires code re-use in the implementation. – IInspectable Jul 02 '21 at 07:27
  • @IInspectable - and what concrete you try say ? simply paste links no sense – RbMm Jul 02 '21 at 07:31
  • @IInspectable - and you can downvote or post self answer, or mark as duplictate also here - https://stackoverflow.com/questions/68217819/casting-com-interface-to-interface-from-which-it-inherits – RbMm Jul 02 '21 at 07:43
  • @ChefGladiator - how midl, idl related to topic. i based on very simply fact - pointer to `I2` interface is already valid and the **binary same**pointer to `I1` interface too, if `I2` inherit from `I1` and from [what is interface internal](https://learn.microsoft.com/en-us/windows/win32/com/interface-pointers-and-interfaces). – RbMm Jul 02 '21 at 08:57
  • @RbMm as I have commented to your twin question that looks like an answer: Please publish a Github repo where this works. And then please ask for a code review on the "Code Review" part of the SO. – Chef Gladiator Jul 02 '21 at 09:02
  • 1
    @ChefGladiator - what work ?? this cast of course work in **any** example. you can try it by self. but you can create example where this is wrong ?! – RbMm Jul 02 '21 at 09:09