Component Object Model (COM)

Introduction

The component object model (COM) is an API specification introduced by Microsoft®, which provides a standard way for interfacing component objects and their programming language-independent implementation.

The component object model API is provided by a DLL called COMPOBJ.DLL. When you use this API to create an instance of a component object, you receive a pointer to a method table, also known as interface method table (IMT). You can use these methods as if they were methods of any object type.

A component object can be implemented by an EXE or DLL, although the implementation is transparent to you, because of an isolation process, provided by OLE 2, called marshaling. OLE 2's marshaling mechanism handles all the intricacies of calling methods across process boundaries, so it is safe for example to use a 64-bit component object from a 32-bit application.

Interfaces

COM defines how OLE object methods are mapped in memory. Method pointers are arranged in tables. The description of a table in any programming language is called an interface.

ObjAsm introduces a DMT that is fully compatible with the COM specification.

When you create an object instance, the first member in the allocated memory, is a pointer to the IMT.

This way, it is possible to call a COM method following the standard established in the specification:

ObjAsm introduces a new invocation macro called ICall, which calls a COM method following the specification illustrated above. ICall uses solely the structure definitions provided by the Windows® APIs or the object interface definition. The interface name is not mangled. The members are!

Defining a COM interface of an object unrelated interface can be done using the BEGIN_INTERFACE / END_INTERFACE macros. No more information is required!

The following example shows two of the primer COM interface layouts.

BEGIN_INTERFACE IUnknown,, <00000000-0000-0000-C000-000000000046>
	STDMETHOD	QueryInterface,	POINTER, POINTER
	STDMETHOD	AddRef
	STDMETHOD	Release
END_INTERFACE

BEGIN_INTERFACE IErrorInfo, IUnknown, <1CF2B120-547D-101B-8E65-08002B2BD119>
	STDMETHOD	GetGUID,	POINTER
	STDMETHOD	GetSource,	POINTER
	STDMETHOD	GetDescription,	POINTER
	STDMETHOD	GetHelpFile,	POINTER
	STDMETHOD	GetHelpContext,	POINTER
END_INTERFACE

The first argument of the interface header is the name, followed by an optional parent interface from which it inherits the first methods. This is the case of the second layout, which inherits all methods from the first and adds 2 more. The last optional argument is the associated IID, which is defined as a symbol like sIID_InterfaceName. In this way, the IID, if needed more than once, can be easily stored in the .const or .data segment using the following macros:

.const
DefGUID IID_IUnknown, %sIID_IUnknown
DefGUID IID_IErrorInfo, %sIID_IErrorInfo

Other GUIDs, like Class IDs (CLSID) can be defined in the same way

Component

ObjAsm uses objects to implement any interface that belongs to a component. Since many of them are used in more than one component, it makes sense to write them in a form that can be reused with little customization.

A graphical representation of the ObjAsm COM model looks like:

The boundary represents the component object. The circles are the interfaces that are implemented by the interface objects that are inside the component object. All interface objects are grouped together in a collection.

Each of these interface objects has a unique IID that is used to identify them. The component object is identified by its CLSID.

Note that a component never has less than one interface, which must always be present. This interface is IUnknown.

Implementations

Interface implementation

Each interface should derive directly or indirectly from the IUnknown interface object. The variables that are necessary for the functionality of the interface can be stored in the object itself or in the component object. The component object can be reached using the Owner pointer or the Owner's Owner pointer, depending on the implementation.

The implementation of the IUnknown interface object holds the basic three interface methods: QueryInterface, AddRef and Release and 2 variables: a pointer to a GUID and the reference count. Each interface is responsible to hold its own reference count!

Two additional static methods are added to provide the full functionality of the component: CanNotRelease and GetInterface. Both are iterator methods and are called from a FirstThat collection method. Do not call them directly!

Component implementation

To guarantee a full functionality, each component object has to inherit directly or indirectly from the Component object.

It contains three variables: a pointer to a GUID that identifies the component class, a collection to append all supported interfaces, and a pointer to an external IUnknown interface for aggregation purposes.

The component object has two new methods besides Init and Done. There is CanNotDestroyNow, which iterates through all interfaces to check if all reference counts are zero, and GetInterface to check if a particular IID is supported by that component. This last method is used by the QueryInterface interface method.

In Process Server implementation

A component object can be implemented as an In Process Server. This means a DLL that can be used by all applications that follow the COM standard.

All the code required to register and unregister the server, the instantiation of the Class Factory, and the internal DLL reference Count are grouped together in a file called COM_DLL.inc, which is required to create an In Process Server. Another required file that is responsible for assembling the DLL is the definition file COM_DLL.def. This file declares all public procedures contained in the DLL.

The component (server) retrieval mechanism uses another component called "Class Factory" provided by the DLL. It is responsible for the instantiation of the requested components.

Class Factory implementation

Like all other components, the Class Factory component has at least two interfaces, IUnknown and IClassFactory.

Registration

A DLL requires two registrations.

Since more than a server can be contained in a single DLL, a registration-chained structure is used when the RegisterServerInfo macro is invoked. This macro can be found in the COM.inc file. Its arguments are:

  1. The object name of the component that will be instantiated

  2. Its type library GUID

  3. Server major version

  4. Server minor version

  5. Server Prog ID string

  6. Server description

  7. Verb Map pointer

The second registration to perform is the registration of the required type libraries. This is done with the RegisterTypeLibrary macro, which is also included in the COM.inc file. Its arguments are:

  1. Resource ID

  2. Type Library GUID

  3. Major version

  4. Minor version

Aggregation

Aggregation in the COM context is a strategy that presents an interface to a foreign component as if it were the own. To accomplish this goal, several tricks must be performed to convince the component client that it is talking to a single component. Special care should be taken with the IUnknown interface that was divided into two interfaces, the delegating and the non-delegating IUnknown interface.

The strategy proposed by the COM designers is to redirect the information flow from the inner component non-delegating IUnkown to the outer non-delegating IUnkown. By this way, proper interface counting can be guaranteed and the IUnknown methods of the inner component are replaced by those of the outer component. The client never sees the implementation of the IUnknown methods of the inner component.

Last updated