Label: QueryInterface
QueryInterface in COM: Interface-Based Design and Runtime Type Access
What is QueryInterface
In Component Object Model (COM), QueryInterface is the core mechanism that allows a client to request a specific interface from an object at runtime.
Every COM object exposes at least one interface, but internally it may implement many.
QueryInterface is how you discover and access them.
At a conceptual level:
QueryInterface answers the question: “Can you behave like this interface?”
Why QueryInterface exists
COM was designed around binary compatibility and decoupling.
Unlike C++:
- there is no direct access to class definitions
- no
dynamic_cast - no compile-time type knowledge across modules
Instead, everything is based on interfaces identified by GUIDs (IIDs).
QueryInterface provides:
- runtime type discovery
- safe casting between interfaces
- versioning support without breaking binaries
The core idea
Every COM interface inherits from IUnknown, which defines:
HRESULT QueryInterface(REFIID riid, void** ppvObject);
ULONG AddRef();
ULONG Release();
QueryInterface behavior
When you call QueryInterface:
- You pass an interface ID (IID)
- The object checks if it supports that interface
- If yes:
- returns a pointer to that interface
- increments reference count
- If not:
- returns
E_NOINTERFACE
- returns
Example usage
IMyInterface* iface = nullptr;
HRESULT hr = obj->QueryInterface(
IID_IMyInterface,
(void**)&iface
);
if (SUCCEEDED(hr))
{
// Use iface
iface->Release();
}
This is effectively a runtime-safe cast.
Identity rule
One of the most important COM rules:
QueryInterface for
IUnknownmust return the same pointer for the same object.
This guarantees:
- object identity
- correct comparison of interfaces
Without this, the system breaks.
Symmetry and consistency
COM enforces strict rules:
- If interface A can QueryInterface to B
- then B must be able to QueryInterface back to A
This ensures a consistent graph of interfaces.
QueryInterface as a design pattern
Beyond COM, QueryInterface represents a broader idea:
Interface-based polymorphism
Instead of:
- class hierarchies
- RTTI
you use:
- explicit interface contracts
- runtime querying
Capability-based design
An object does not expose “what it is”, but:
“what it can do”
QueryInterface becomes:
- a capability discovery mechanism
- not just a cast
Advantages of the QueryInterface approach
Binary compatibility
You can:
- add new interfaces
- extend functionality
without breaking existing clients.
Decoupling
Clients depend only on:
- interface definitions
- not implementation
Versioning
New versions of an object:
- implement additional interfaces
- old code keeps working
Drawbacks and complexity
Boilerplate
Manual implementation:
- interface tables
- reference counting
- GUID handling
is verbose and error-prone.
Runtime cost
QueryInterface introduces:
- lookup overhead
- pointer indirection
Usually negligible, but not free.
No compile-time safety
Errors appear:
- at runtime
- not at compile time
QueryInterface vs dynamic_cast
In C++:
dynamic_castuses RTTI- requires shared type information
In COM:
- QueryInterface uses GUIDs
- works across binary boundaries
That’s why COM can:
- cross DLLs
- support language-neutral components
Beyond COM: similar ideas
The QueryInterface concept appears in many systems:
Plugin architectures
- modules expose multiple interfaces
- queried at runtime
Game engines
- component-based systems
- entities expose capabilities
Service locators
- retrieving services by interface
When to use this approach
The QueryInterface-style design makes sense when:
- components are loosely coupled
- binary compatibility matters
- runtime extensibility is required
- plugins or modules are involved
When not to use it
It can be overkill if:
- everything is compiled together
- types are known at compile time
- performance is extremely critical
In such cases, simpler polymorphism is better.
Final thoughts
QueryInterface is not just a COM function — it’s a powerful architectural idea.
It shifts design from:
- “what type is this object?”
to:
- “what interfaces does it support?”
This makes systems:
- more flexible
- more extensible
- but also more complex
Used correctly, it enables long-lived, binary-compatible systems.
Used blindly, it turns code into a maze of interfaces.