The last module discussed many of COM's core concepts. We studied the
binary requirements of COM methods and interfaces,
COM objects, and
IUnknown
. We also used IDL to define COM interfaces and COM objects in a type library.
This module continues the discussion of core COM concepts. We will examine how COM objects are created, COM servers, and COM clients.
At the end of this module you will be able to:
- Understand how a class factory creates instances of a COM objects
- List the COM requirements of an in-process COM server
- Describe how COM objects in an in-process server are registered
- Describe how COM clients create instances of COM objects and use COM interfaces
In later modules, we will put our knowledge of core concepts COM to work. We will use the active template library (ATL) to develop an in-process COM server, a COM object, and COM interfaces. We will also develop a COM client that uses our COM object.
For COM interfaces to be binary compatible, meaning that different compiled binaries (e.g., a client application and a COM server) can interoperate seamlessly without recompilation, the following must hold true at the binary level:
Key Requirements for Binary Compatibility
-
Consistent Interface Definition:
-
Method Names: The symbolic names of the methods (e.g., QueryInterface, AddRef, Release in IUnknown, or custom methods in derived interfaces) must remain unchanged. However, at the binary level, it’s the order and offset of methods in the vtable (virtual function table) that truly matter, not the names themselves, since COM operates via pointers to function tables.
-
Parameter Types: The data types of all parameters (e.g., int, float, IUnknown*, etc.) must match exactly in size, alignment, and representation. Any mismatch (e.g., changing an int to a long long) alters the stack layout and breaks compatibility.
-
Return Types: The return type (e.g., HRESULT) must be consistent in size and convention. COM typically uses HRESULT for error reporting, and any deviation (e.g., returning int instead) would misalign expectations.
-
Calling Convention: The calling convention (typically __stdcall in COM on Windows) dictates how arguments are passed (e.g., stack order) and who cleans up the stack (callee in __stdcall). A mismatch (e.g., __cdecl vs. __stdcall) corrupts the stack and crashes the program.
-
Stable vtable Layout:
-
COM relies on a virtual function table (vtable) where each method is assigned a fixed offset. For binary compatibility, the order of methods in the interface definition must not change. Adding new methods after existing ones is allowed in derived interfaces (via interface inheritance), but inserting or reordering methods within an existing interface breaks the vtable layout, rendering it incompatible with older binaries.
-
Interface Identity (IID):
-
The Interface ID (IID), a GUID (Globally Unique Identifier), must remain the same. The IID is how COM identifies an interface at runtime (via QueryInterface). Changing the IID creates a new interface, even if the method signatures look identical, breaking binary compatibility with code expecting the original IID.
-
No Changes to Binary Representation:
-
The size and layout of any structures passed as parameters (e.g., struct types) must remain unchanged. For instance, adding a new field to a struct or changing padding/alignment alters its binary footprint, breaking compatibility.
-
Pointers, handles, and other indirect types must retain their expected semantics and sizes (e.g., 32-bit vs. 64-bit consistency).
Why This Matters
Binary compatibility ensures that a client compiled against version 1.0 of a COM interface can still call a server implementing version 1.1, as long as the original interface contract is preserved. This is a cornerstone of COM’s design, enabling extensibility (via new interfaces) without breaking existing deployments.
Practical Implications
-
Adding Functionality: To extend an interface while maintaining compatibility, define a new interface (e.g., IMyInterface2) that inherits from the original (e.g., IMyInterface). Clients query for the new IID if they need the extra methods.
- Define a new interface (e.g., IMyInterface2) that inherits from the original (e.g., IMyInterface).
- Clients query for the new IID if they need the extra methods.
-
Compiler Settings: Ensure consistent compiler flags (e.g., structure packing, alignment) across all binaries to avoid subtle mismatches.
- Ensure consistent compiler flags (e.g., structure packing, alignment) across all binaries.
- Avoid subtle mismatches.
-
Testing: Validate compatibility by loading the COM object with a client compiled against the original interface definition.
- Validate compatibility.
- Load the COM object with a client compiled against the original interface definition.
COM (Component Object Model) was one of the fastest growing models in object technology prioer to COM+ and .NET technologies.
Microsoft exposes any new technology by implementing each new subsystem as a COM object. COM is the way to interact with subsystems like
- ADSI (Active Directory Services Interface),
- MTS (Microsoft Transaction Server),
- DirectX,
- Shell extensions,
- ActiveX controls,
- OLE DB (Object Linking and Embedding) and
- ActiveX scripting.
The central tenet of COM is the concept of language independence. COM based components can be built in wide variety of development environments. So it is better for windows developers to understand the core infrastructure of COM.
This understanding will enable you to easily take advantage of those new subsystems as well as make it easier for you to expose your own subsystems.