Basic COM  «Prev  Next»
Lesson 10IUnknown and Interface Navigation
ObjectiveThis page explains interface navigation via IUnknown's QueryInterface method.

Introduction to COM Interfaces and IUnknown

A single Component Object Model (COM) component is not very useful in isolation. To enable communication between objects, components include interfaces. An interface is a class that defines one or more methods (along with their associated parameters). As a naming convention, every COM interface is prefixed with "I". For example, as shown in Figure 2-12, a component may implement interfaces such as IUnknown, IReservation, and IAuction.
Interfaces are IUnknown, IReservation, and IAuction.
Figure 2-12: Interfaces are IUnknown, IReservation, and IAuction.

IUnknown

Every COM component implements the IUnknown interface, which is the foundation of all COM interfaces. In schematic representations, like Figure 2-12, the IUnknown interface is depicted as a lollipop extending from the top of the component. This interface makes the component self-describing, allowing a client to query whether a component supports specific interfaces. For example, a COM client needing an IReservation interface can query all components through their IUnknown interfaces with a request like, "Do you have an IReservation interface?" The first component that responds affirmatively handles the client's request.

Other Interfaces

In addition to IUnknown, a COM component typically implements one or more custom interfaces, such as IReservation or IAuction. In schematic diagrams (e.g., Figure 2-12), these interfaces are shown as lollipops extending from the left side of the component. For comparison, Visual Basic (VB) clients may use the IDispatch interface, which is similar to IUnknown but less efficient and tailored for VB-specific automation.

Creating COM Classes

To make a component functional, it is compiled into machine code, creating a class. In COM, a class is an object-oriented term describing a collection of methods and attributes. Classes are defined using the Microsoft Interface Definition Language (MIDL) and assigned a globally unique identifier (GUID) to ensure uniqueness across systems.

IUnknown and Interface Navigation

Every COM interface inherits from IUnknown, which provides the foundation for interface navigation through its QueryInterface method. Interface navigation allows a client with a pointer to one interface of a COM object to acquire pointers to any other interfaces implemented by that object. The QueryInterface method is key to this process, with the following equivalent prototypes:
virtual HRESULT __stdcall QueryInterface
  (const IID& riid, void **ppv);

virtual HRESULT __stdcall QueryInterface
  (REFIID riid, VOID **ppv);

REFIID Macro

The second prototype, using REFIID, is more commonly used. REFIID is a macro defined as const IID &, and VOID is a Windows type alias for void. Since every COM interface implements QueryInterface, all interfaces support navigation to other interfaces within the same object. The QueryInterface method checks the riid (interface ID) parameter against the IIDs of all interfaces supported by the COM object. If a match is found, the ppv parameter is set to the requested interface pointer.

Implementing QueryInterface

Consider the following example of a COM object, CMyComObject, which implements two interfaces, IMyComInterface and IYourComInterface:
class CMyComObject :
  public IMyComInterface,
  public IYourComInterface {
private:
  ULONG m_refcnt; // Reference count variable

public:
  HRESULT __stdcall QueryInterface
    (const IID& iid, void **ppv);

  ULONG __stdcall AddRef();
  ULONG __stdcall Release();

  // Methods for IMyComInterface
  HRESULT __stdcall Fx1(char *buf);
  HRESULT __stdcall Fx2();

  // Methods for IYourComInterface
  HRESULT __stdcall Zx1(int ix);
};

The implementation of QueryInterface for CMyComObject is as follows:
QueryInterface Implementation
HRESULT __stdcall CMyComObject::QueryInterface
(REFIID riid, VOID **ppv)
{
    if (riid == IID_IMyComInterface ||
        riid == IID_IUnknown)
    {
        *ppv = (IMyComInterface *) this;
    }
    else if (riid == IID_IYourComInterface)
    {
        *ppv = (IYourComInterface *) this;
    }
    else return E_NOINTERFACE;

    (*ppv)->AddRef();
    return S_OK;
}
    

Lines 4 and 5: Checks if the caller is requesting a pointer to IMyComInterface or IUnknown. All COM interfaces must support navigation to IUnknown and other interfaces within the object. If a match is found, the ppv output parameter is assigned the this pointer, cast to IMyComInterface *.
Line 9: Checks if the caller is requesting a pointer to IYourComInterface. If a match is found, the ppv output parameter is assigned the this pointer, cast to IYourComInterface *.
Line 13: Returns the error code E_NOINTERFACE to indicate that the COM object does not implement the requested interface.
Line 15: Calls AddRef on the interface pointer assigned to ppv. This is a COM requirement: every time an interface pointer is provided to a caller, its reference count must be incremented. Reference counting is discussed in more detail in Lesson 12.
Line 16: Returns S_OK to indicate a successful call.

IUnknown Reference Counting

AddRef and Release Implementation
ULONG __stdcall CMyComObject::AddRef()
{
    return ++m_refcnt;
}

ULONG __stdcall CMyComObject::Release()
{
    if (--m_refcnt == 0)
    {
        delete this;
        return 0;
    }
    return m_refcnt;
}
    

This is a classic implementation of the IUnknown interface methods (QueryInterface, AddRef, and Release) in COM. The AddRef method increments the reference count (m_refcnt), while Release decrements it. If the reference count reaches zero, the object is deleted, indicating it is no longer in use.
  Line 14: AddRef, called from QueryInterface, increments m_refcnt.
  Line 23: Release decrements m_refcnt. If the count reaches zero, Release deletes the instance of CMyComObject and returns 0, indicating the object and its interfaces are no longer in use.

SEMrush Software