COM Programming  «Prev  Next»

Lesson 5 Course project
Objective Describe the course project.

COM Fundamentals Course Project Description

In COM Fundamentals II (Advanced COM), you will develop two new COM objects that demonstrate the two primary COM reuse mechanisms: containment/delegation and aggregation. Both objects reuse an existing COM component from COM Fundamentals I, allowing you to compare the two techniques side by side using the same underlying inner object.

The course project consists of three components:

  1. Memo in server InfoMgr: reuses PhBookObj from the PhBook project in COM Fundamentals I through containment and delegation
  2. MemoAgg in server InfoMgrAgg: reuses PhBookObj from the same PhBook project via aggregation
  3. InfoMgrCli: a COM client that consumes both Memo and MemoAgg, demonstrating the difference between containment and aggregation from the client's perspective

COM Fundamentals II builds on the project you developed in Basic COM (COM Fundamentals I). If you do not have the PhBook project from that course, it is available from the Resources page. You will receive instructions on how to download and install the files when you need them.



What You Learn from Each Project Component

Each component in the course project teaches a distinct set of COM development skills.

Building Memo in InfoMgr teaches containment and delegation in practice. You will implement an outer COM object that holds a private PhBookObj member, forwards selected IPhBook interface calls to it via delegation, and handles other calls with its own implementation. The outer object maintains its own IUnknown identity throughout: clients always interact with Memo's own QueryInterface, AddRef, and Release. The key skill is deciding which calls to delegate and which to handle directly, and implementing the forwarding code correctly without leaking the inner object's IUnknown to the client.

Building MemoAgg in InfoMgrAgg teaches aggregation and the controlling unknown mechanism. You will implement an outer object that passes its own controlling IUnknown to PhBookObj at construction time, allowing PhBookObj's IPhBook interface to be returned directly to clients via MemoAgg's QueryInterface. You will implement the non-delegating IUnknown that MemoAgg uses for its own reference counting during construction and teardown, before the aggregation relationship is fully established. In Visual Studio 2022 with ATL, the DECLARE_AGGREGATABLE macro generates the controlling unknown plumbing automatically when aggregation support is selected in the ATL COM wizard.

Building InfoMgrCli teaches how a COM client consumes objects from multiple servers, issues QueryInterface calls to discover interface support, and manages reference counting using CComPtr to avoid manual AddRef and Release calls. The client demonstrates that from its perspective, both Memo and MemoAgg provide access to the same IPhBook interface. The difference between containment and aggregation is entirely in how the outer object is implemented, not in how the client consumes it.



Project Tooling and Visual Studio 2022

The course examples were originally written for Visual C++ 6 (1998). The code compiles in Visual Studio 2022 with minor adjustments that are noted in each lesson and exercise where they apply. Install Visual Studio 2022 with the Desktop development with C++ workload:

visualstudio.microsoft.com/vs/features/cplusplus

During installation, ensure the following components are selected under Individual Components:

  • MSVC C++ compiler and libraries (v143 build tools)
  • C++ ATL for latest v143 build tools
  • Windows SDK matching your Windows 11 installation

ATL is required. The ATL COM wizard generates the COM server project scaffolding, IDL file, registration code, and the IUnknown and IClassFactory infrastructure for both InfoMgr and InfoMgrAgg. Without ATL selected in the installer, the wizard is not available and the ATL headers are not present.



COM Object Identification and Creation

Every COM component and every COM interface is identified by a globally unique identifier (GUID): a 128-bit value that is statistically unique across all machines and all time. The CLSID (Class Identifier) identifies a coclass and maps to the server DLL or EXE that implements it via the Windows registry. The IID (Interface Identifier) identifies a specific interface and maps to its vtable layout definition. GUIDs are generated by the MIDL compiler during IDL compilation or by the guidgen tool in the Windows SDK.

The standard COM object creation function is CoCreateInstance(). It takes the CLSID of the component to create, the context in which to create it (in-process DLL, out-of-process EXE, or remote via DCOM), and the IID of the interface to return. Internally, CoCreateInstance() locates the registered server for the CLSID, loads it if necessary, asks the server's Class Factory for a new instance via IClassFactory::CreateInstance, and returns the requested interface pointer. CoCreateInstanceEx() extends this for scenarios involving multiple interfaces or remote activation.

In the course project, InfoMgrCli uses CoCreateInstance() to create instances of both Memo and MemoAgg, then issues QueryInterface calls to obtain the specific interface pointers it needs from each object.


CComPtr and RAII Reference Counting

CComPtr is the ATL smart pointer template for COM interface management. It wraps a raw COM interface pointer, calls AddRef when initialized or assigned, and calls Release when it goes out of scope or is reassigned. Using CComPtr throughout InfoMgrCli eliminates the most common source of COM memory leaks: forgetting to call Release on an interface pointer before it goes out of scope or on an error return path.

The following example shows the pattern used in the course project to create a Memo instance and obtain its IMemo interface using CComPtr:

#include <windows.h>
#include <comdef.h>
#include <atlbase.h>

int main() {
    CoInitialize(nullptr);
    {
        // Create an instance of Memo via the InfoMgr in-process server
        CComPtr<IUnknown> pUnk;
        HRESULT hr = CoCreateInstance(
            CLSID_Memo,
            nullptr,
            CLSCTX_INPROC_SERVER,
            IID_IUnknown,
            reinterpret_cast<void**>(&pUnk)
        );
        if (SUCCEEDED(hr)) {
            CComPtr<IMemo> pMemo;
            hr = pUnk->QueryInterface(IID_IMemo,
                reinterpret_cast<void**>(&pMemo));
            if (SUCCEEDED(hr)) {
                // Use pMemo here.
                // Calls to IPhBook methods are forwarded to PhBookObj
                // via containment/delegation inside Memo.
            }
            // pMemo goes out of scope here: Release called automatically
        }
        // pUnk goes out of scope here: Release called automatically
    }
    CoUninitialize();
    return 0;
}

The same pattern applies when creating a MemoAgg instance from InfoMgrAgg. The client code is identical: the difference between containment and aggregation is entirely inside the server implementation, invisible to InfoMgrCli.

COM Threading Models

COM defines a threading model that specifies how method calls are dispatched to COM objects when clients and objects reside in different threads or processes. The threading model is declared by the component and registered in the Windows registry under the component's CLSID. The three primary threading models are:

  • Single-Threaded Apartment (STA): the default for most COM objects. All calls to an STA object are serialized through a Windows message queue. The COM runtime marshals calls from other threads through the STA's message loop, so the object never receives concurrent calls. STA is appropriate for UI components and for objects that do not need to handle multiple concurrent client calls.
  • Multi-Threaded Apartment (MTA, also called Free-Threaded): the developer is responsible for all synchronization. Concurrent calls from multiple threads are delivered directly to the object without serialization. MTA is appropriate for high-throughput server components where serialization overhead is unacceptable.
  • Neutral Apartment (NTA, Windows 2000 and later): eliminates the thread-switch overhead that occurs when an STA or MTA client calls a COM+ object. Neutral apartment objects are called on the caller's thread without marshalling, making them the highest-performance threading model for COM+ components.

The InfoMgr and InfoMgrAgg servers in the course project use the STA threading model. In ATL, the threading model is declared using the DECLARE_THREADING_MODEL macro or via the threading model selection in the ATL COM wizard.

COM Evolution Timeline

The course project was designed around COM, but the binary interface model it uses has evolved through several generations of Windows component technology. Understanding this timeline provides context for the advanced topics covered in later modules:



Technology Year Purpose
OLE 1991-93 Object Linking and Embedding: the COM precursor for compound documents
COM 1993 Core binary component model: vtable interfaces, IUnknown, CLSID/IID
DCOM 1996 Distributed COM: extends COM across network boundaries via RPC
ActiveX 1996 COM-based browser-hosted controls: deprecated in modern browsers
COM+ 1997-2000 Transactional extension to COM: object pooling, queuing, security
.NET Interop 2002+ Runtime Callable Wrapper (RCW) and COM Callable Wrapper (CCW)
WinRT 2012 COM-derived ABI for Universal Windows Platform: IInspectable extends IUnknown

COM remains foundational in the Windows platform. Shell extensions, DirectX, Office automation, and WinRT all use COM conventions at their interface boundary. Modern .NET applications consume COM objects via the RCW and expose .NET objects to COM clients via the CCW. The IUnknown rules and vtable-based interface model introduced in 1993 underpin all of these technologies.


SEMrush Software