List the binary layout requirements of a COM interface.
COM Interface Properties
The "binary layout requirements" for a Microsoft Component Object Model (COM) interface refer to the specific way in which a COM interface must be structured in memory to ensure interoperability between different components and programming languages. These requirements are critical because COM is designed to enable communication between objects in a language-agnostic and binary-compatible manner. Below are the key binary layout requirements for a COM interface:
Inheritance from IUnknown:
Base Interface: Every COM interface must inherit, directly or indirectly, from the IUnknown interface.
First Three Methods: The first three entries in the virtual function table (vtable) must be the methods of IUnknown in the following order:
QueryInterface
AddRef
Release
Virtual Function Table (vtable) Layout:
Sequential Ordering: Methods in the vtable must appear in the order they are declared in the interface definition.
Inheritance Hierarchy: If the interface inherits from other interfaces (other than IUnknown), the methods from the base interfaces must appear first, maintaining their original order.
Single Inheritance: COM interfaces typically use single inheritance to avoid complications with multiple inheritance in the vtable layout.
Calling Convention:
Standard Calling Convention: All methods must use the __stdcall calling convention (on 32-bit systems). This ensures that the callee cleans the stack, which is important for language interoperability.
Consistent Across Platforms: On 64-bit Windows systems, the default calling convention is used, which is consistent across all functions.
Pure Virtual Methods:
Abstract Methods: All methods in the COM interface must be pure virtual functions. This is represented in C++ by assigning = 0 at the end of the method declaration.
No Instance Data: The interface should not contain any member variables. This ensures that the object's memory layout consists only of the vtable pointer.
Binary Compatibility:
Compiler Compatibility: The binary layout must be compatible with Microsoft's compiler conventions for virtual function tables to ensure that components compiled with different compilers can interoperate.
No Name Mangling: Method names should not be mangled (a process compilers use to encode additional information in function names), which is avoided by using extern "C" linkage or by adhering to COM's standard naming.
GUID Association:
Unique Identifier: Each COM interface must have a globally unique identifier (GUID), known as an Interface Identifier (IID).
Consistent Identification: This GUID is used at runtime to identify and query interfaces through QueryInterface.
Reference Counting:
Lifetime Management: The AddRef and Release methods are used for reference counting to manage the lifetime of COM objects.
Consistent Behavior: All COM objects must implement these methods consistently to ensure proper memory management across components.
Threading Model Compliance:
Apartment Model: The COM interface must comply with the threading model specified for the COM object (e.g., Single-Threaded Apartment, Multi-Threaded Apartment).
Synchronization: Any necessary synchronization must be handled within the methods to ensure thread safety.
Example Layout in C++
Here's how a typical COM interface would be declared in C++:
Object Memory: The COM object's memory consists of a pointer to the vtable.
vtable Memory: The vtable is an array of pointers to the methods, starting with IUnknown methods.
Importance of the Binary Layout
Language Interoperability: Ensures that COM objects can be used across different programming languages that may have different class and object models.
Binary Compatibility: Allows COM components to interact at the binary level without source code compatibility, enabling components to be updated independently.
Additional Considerations
Error Handling: Methods should return HRESULT to indicate success or failure, following COM's standard error handling mechanism.
Marshalling Requirements: If the interface is to be used across process or network boundaries, it must adhere to COM's marshalling requirements.
By adhering to these binary layout requirements, developers ensure that their COM interfaces are compatible with the COM infrastructure, enabling seamless interaction between components, regardless of the languages or tools used to create them.
vtable or virtual table
A COM interface contains a group of related COM methods. COM interfaces have the following properties. The runtime binary layout of a COM interface contains vtable. A vtable is a table of pointers to interface methods. A COM interface pointer is a pointer to a vtable.
Methods in a vtable are accessed by table position, not by method name.
The placement of functions in a vtable is called vtable order.
IUnknown COM Interface
IUnknown is an interface defined by Microsoft for use in the Component Object Model (COM). It is the fundamental base interface from which all other COM interfaces must derive. `IUnknown` provides the means for managing the lifetimes of COM objects and for allowing clients of those objects to access other interfaces implemented by those objects. It includes three essential methods:
QueryInterface:Allows clients to obtain pointers to other interfaces on an object. This method is used for interface navigation and to ensure type safety,
AddRef:Increments the reference count of an object. This method helps in managing the lifetime of the object by keeping track of how many clients are using it. , and
Release:Decrements the reference count of an object. When the reference count reaches zero, the object is destroyed..
Collectively, these three methods are called IUnknown. The first three pointers in every vtable associated with a COM interface must point to the IUnknown methods (i.e. QueryInterface, AddRef, and Release) in order.
These methods provide the necessary mechanisms for reference counting and interface querying that are central to COM's architecture for object management and interface-based programming.
Besides providing application-specific functionality, a COM interface must also provide methods to support interface navigation and lifetime management.
Inheritance: COM does not support implementation inheritance. Unlike C++, in COM there is no way to inherit member functions[1] and member variables from a base class. However, the standard convention used in COM is to say, "All COM interfaces inherit from IUnknown." What we really mean is that all COM interfaces implement the methods of IUnknown in vtable order, i.e., QueryInterface, AddRef, and Release are the first three functions in every COM interface.
C++ and virtual member functions
C++ is the language of choice for developing COM applications. One of the reasons for this is that C++ compilers place
virtual member functions of a class into a vtable.
Defining COM interface:
The following code declares a C++ class that, when instantiated, implements a COM interface called IMyComInterface
class CIMyComInterface {
//For C++ we usually add
//a 'C' in front of the
//interface name
virtual HRESULT __stdcall QueryInterface
(const IID& iid, void **ppv);
virtual unsigned long __stdcall AddRef();
virtual unsigned long __stdcall Release();
virtual HRESULT __stdcall Fx1(CHAR *buf);
virtual HRESULT __stdcall Fx2();
};
When a pointer to this class is passed to a COM client, the client can't see any part of the object other than the vtable.
Data and nonvirtual member functions are not visible or accessible.
Recall from the previous lesson that the first parameter to a COM interface method must be a pointer back to the interface that contains it. C++ provides automatic support for this requirement by passing in the
this pointer as an implicit first parameter.
this pointer as a COM interface pointer:
The this pointer of a C++ class instance points to the internal class object built by the C++ compiler. A C++ class with virtual functions contains a vtable to access those functions. Because the vtable is the first object in the C++ class object, the this pointer is in effect a pointer to a pointer to a vtable. That is, it has the same binary layout as a COM interface.
Why we have to use the corresponding interface pointer as the this parameter.
There are of course other means to do that, as long as you are the implementer of the component. But this is not the case for the clients of our component.
When the component is built using the COM way, clients of our component know nothing about the internals of our component. Clients can only take hold of the interface pointer, and this is the very pointer that will be passed into the interface
method as the this parameter. Under this expectation, the compiler has no choice but to generate the interface method's code based on this specific this pointer. So the above reasoning leads to the result that: It must be assured that each function in a vtable must recieve the corresponding interface pointer as its "this" parameter.
A predefined macro called DEFINE_GUID is used to define IIDs as variables in your code.
Microsoft provides a tool called guidgen to generate IIDs and DEFINE_GUID macros.
Guidgen will generate a DEFINE_GUI macro for you. Click the New GUID button to generate a new GUID.
Click the Copy button to copy the DEFINE_GUID macro with the generated IID copied into the clipboard.
Paste the DEFINE_GUID macro into your code. Replace <<name>> with the interface name. For example:
An IID (interface ID) is a 128-bit number. IIDs are named after the interface they identify.
For example, IUnknown has a predefined IID called IID_IUnknown.
IIDs are also called GUIDs or UUIDs.
[1]member functions:A C++ member function is a function that is declared within a class definition. It operates on objects of that class and has access to all the data members (variables) and other member functions within that class.