COM is many things, but at its core, it’s all about consuming and exposing functionality using C++ virtual function tables derived from the IUnknown struct.

IUnknown
{
public:
    virtual HRESULT __stdcall QueryInterface( const GUID& riid, void **ppvObject) = 0;
    virtual unsigned long __stdcall AddRef( ) = 0;
    virtual unsigned long __stdcall Release( ) = 0;
};

IUnknown can easily be implemented in C:

typedef struct IUnknownVtbl
{
  HRESULT ( __stdcall *QueryInterface )( IUnknown * This, 
                                        const GUID* riid, void **ppvObject);
  unsigned long ( __stdcall *AddRef )( IUnknown * This );
  unsigned long ( __stdcall *Release )( IUnknown * This );
} IUnknownVtbl;

typedef struct tagIUnknown
{
    struct IUnknownVtbl *lpVtbl;
}IUnknown;

It’s just easier to write in C++, but a the end of the day, it’s just a definition of a structure containing pointers to functions.

With COM the calling app is responsible for releasing resources back to the implementation, and every instance of a COM interface must be released using the Release member function of the virtual function table. COM objects are reference counted, or at least they are expected to behave as if they are. Calling Release decrements the reference count of the object, and destroys the object when the reference count becomes 0.

AddRef increments the reference count of the implementation, and apps should usually call AddRef when they make copies of the interface pointer.

COM object implements one, or more interfaces, and the role of QueryInterface is to provide access to the interfaces implemented by the object. Interfaces are identified by a GUID, which is a 16 byte structure.

All COM objects supports the IUnknown interface, and a query for the IUnknown interface on any of the object’s interfaces must always return the same pointer value. This allows clients to determine if two pointers point to the same object by calling QueryInterface with IID_IUnknown and comparing the results. Pointers to other interfaces do not have this restriction.

There are three requirements for the implementations of QueryInterface:

  1. The set of interfaces that can be accessed using QueryInterface must be static. If a call to QueryInterface for a pointer to an interface succeeds the first time, then it must succeed again, and when it fails the first time, it must fail on all subsequent calls.
  2. If a client calls QueryInterface successfully for another interface implemented by an object, then a query through the obtained interface for the original interface must succeed.
  3. If a client calls QueryInterface successfully for a second interface, and calls QueryInterface successfully for a third interface, then a call to QueryInterface for the initial interface through the third interface must succeed.

Error handling in COM

Almost all COM functions and interface methods return a value of the type HRESULT. The HRESULT is a way of returning a success, warning, or error value.

According to the COM specification, a result of zero indicates success, and a nonzero result indicates failure. Unfortunately this is not always true, and even some core COM interfaces does not follow this rule. When this is the case, it’s usually documented.

The COM API provides two macros that is commonly used to detect if an operation succeeded or failed, appropriately called SUCCEEDED and FAILED.

IXMLDOMDocument3* ptr = ...
IXMLDOMNode* node = nullptr;
auto hr = ptr->QueryInterface( __uuidof( IXMLDOMNode ), ( void** )&node );
if ( FAILED( hr ) )
{

QueryInterface failed, perform error handling. Do not call node->Release( ), or any other function of the IXMLDOMNode interface since node is not a valid interface pointer.

}
else
{
    // QueryInterface succeeded, and node points to an IXMLDOMNode interface  
    BSTR prefix = nullptr;
    hr = node->get_prefix( &prefix );
    if ( SUCCEEDED( hr ) )
    {
        wprintf( L"Prefix:%s\n", prefix );

Caller is responsible for freeing prefix using SysFreeString:

        SysFreeString( prefix );
    }
    node->Release( );
}
ptr->Release( );