The ID3D12GraphicsCommandList
interface inherits from ID3D12CommandList
. So, if I have a ID3D12GraphicsCommandList
object, how do I get get the corresponding ID3D12CommandList
object?
ID3D12GraphicsCommandList *gcl = ...;
ID3D12CommandList *cl = (ID3D12CommandList*)gcl;
ID3D12GraphicsCommandList *gcl = ...;
ID3D12CommandList *cl;
HRESULT result = ID3D12GraphicsCommandList_QueryInterface(gcl,
&IID_ID3D12CommandList,
(void**)&cl);
Thanks.
- 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.
- Will QueryInterface work?
Yes. It's the correct approach to request a different interface through an interface pointer. The code you provided is correct.
- 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
.
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.
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.