Basic COM   «Prev  Next»
Lesson 1

COM Fundamentals and Basic Programming Introduction

This course explores the Component Object Model (COM), Microsoft's foundational technology for component-based software development that dominated Windows programming from the 1990s through the early 2000s. While modern development has largely moved to .NET and other platforms, understanding COM remains valuable for maintaining legacy systems, working with Windows internals, and appreciating the evolution of component-based architecture. This module introduces COM servers, COM objects, and COM interfaces before progressing to practical development using the Active Template Library (ATL). Through hands-on examples including a phone book COM object and client, you'll learn the principles that influenced modern component architectures while gaining skills applicable to real-world legacy codebases still running in production today.

What is COM

The Component Object Model (COM) represents Microsoft's binary standard for creating reusable software components that can communicate across process and language boundaries. Unlike object-oriented programming frameworks tied to specific languages, COM defines a language-neutral, platform-independent binary interface specification that enables components written in C++, Visual Basic, Delphi, or other languages to interoperate seamlessly. COM objects encapsulate functionality behind well-defined interfaces, exposing only methods and properties while hiding implementation details—a principle that prefigured modern microservices and API design patterns.
// COM interface definition in IDL (Interface Definition Language)
[
   object,
   uuid(12345678-1234-1234-1234-123456789ABC),
   pointer_default(unique)
]
interface IPhoneBook : IUnknown
{
   HRESULT AddEntry([in] BSTR name, [in] BSTR number);
   HRESULT GetEntry([in] BSTR name, [out, retval] BSTR* number);
   HRESULT RemoveEntry([in] BSTR name);
   HRESULT GetCount([out, retval] long* count);
};

// COM client usage in C++
void use_phone_book() {
   IPhoneBook* phoneBook = nullptr;
   
   // Create COM object instance
   HRESULT hr = CoCreateInstance(
      CLSID_PhoneBook,
      nullptr,
      CLSCTX_INPROC_SERVER,
      IID_IPhoneBook,
      (void**)&phoneBook
   );
   
   if (SUCCEEDED(hr)) {
      // Use interface methods
      phoneBook->AddEntry(L"Alice", L"555-1234");
      
      BSTR number = nullptr;
      phoneBook->GetEntry(L"Alice", &number);
      
      // Release when done
      phoneBook->Release();
      SysFreeString(number);
   }
}


COM Architecture: Objects, Interfaces, and Servers

COM architecture consists of three fundamental concepts working together. A COM server is a binary module (DLL or EXE) that hosts one or more COM classes. A COM object is an instance of a COM class implementing one or more interfaces. A COM interface is a contract—a table of function pointers called a vtable—that defines what operations an object supports without specifying how those operations are implemented. This separation between interface and implementation enables versioning, language independence, and the substitutability that makes component reuse practical.
// Conceptual COM object structure
class com_phone_book : public IPhoneBook {
private:
   ULONG ref_count;  // Reference counting for lifetime management
   std::map<std::wstring, std::wstring> entries;

public:
   // IUnknown methods (required for all COM interfaces)
   STDMETHODIMP QueryInterface(REFIID riid, void** ppv);
   STDMETHODIMP_(ULONG) AddRef();
   STDMETHODIMP_(ULONG) Release();

   // IPhoneBook methods
   STDMETHODIMP AddEntry(BSTR name, BSTR number);
   STDMETHODIMP GetEntry(BSTR name, BSTR* number);
   STDMETHODIMP RemoveEntry(BSTR name);
   STDMETHODIMP GetCount(long* count);
};

// AddRef implementation - reference counting
STDMETHODIMP_(ULONG) com_phone_book::AddRef() {
   return InterlockedIncrement(&ref_count);
}

// Release implementation - destroys when count reaches zero
STDMETHODIMP_(ULONG) com_phone_book::Release() {
   ULONG count = InterlockedDecrement(&ref_count);
   if (count == 0) {
      delete this;
   }
   return count;
}

IUnknown: The Foundation Interface

Every COM interface derives from IUnknown, the foundational interface providing three essential methods: QueryInterface for discovering additional interfaces, AddRef for incrementing the reference count, and Release for decrementing the reference count and destroying the object when it reaches zero. This reference counting mechanism replaces garbage collection, giving clients explicit control over object lifetime while avoiding memory leaks through disciplined AddRef/Release pairing. QueryInterface enables runtime interface discovery, allowing clients to probe objects for capabilities without compile-time knowledge of all interfaces an object might support.
// IUnknown interface definition
interface IUnknown {
   // Query for additional interfaces
   HRESULT QueryInterface(REFIID riid, void** ppvObject);
   
   // Reference counting
   ULONG AddRef();
   ULONG Release();
};

// QueryInterface implementation pattern
STDMETHODIMP com_phone_book::QueryInterface(REFIID riid, void** ppv) {
   if (riid == IID_IUnknown) {
      *ppv = static_cast<IUnknown*>(this);
   }
   else if (riid == IID_IPhoneBook) {
      *ppv = static_cast<IPhoneBook*>(this);
   }
   else {
      *ppv = nullptr;
      return E_NOINTERFACE;
   }
   
   AddRef();  // Client receives a new reference
   return S_OK;
}

// Proper COM client usage with reference counting
void demonstrate_reference_counting() {
   IPhoneBook* pb1 = nullptr;
   
   // Create increases ref count to 1
   CoCreateInstance(CLSID_PhoneBook, nullptr, CLSCTX_ALL,
                    IID_IPhoneBook, (void**)&pb1);
   
   IPhoneBook* pb2 = pb1;
   pb2->AddRef();  // Explicit AddRef for second pointer
   
   // Both pointers must Release
   pb1->Release();  // Count: 2 → 1
   pb2->Release();  // Count: 1 → 0, object destroyed
}


Marshaling and Cross-Process Communication

COM's marshaling mechanism handles the complexity of calling methods across process and machine boundaries. When a client in one process calls a method on a COM object in another process, COM automatically creates proxy objects in the client process and stub objects in the server process. The proxy packages method parameters into a message (marshaling), sends it across the process boundary, where the stub unpacks parameters (unmarshaling) and calls the actual object. This infrastructure makes distributed computing transparent—clients call methods as if the object were in-process, with COM handling all remote communication details.

Interface Definition Language (IDL) and MIDL

The Microsoft Interface Definition Language (IDL) provides a language-neutral syntax for describing COM interfaces, derived from DCE RPC IDL. IDL files specify interfaces, methods, parameters, data types, and unique identifiers (GUIDs) that enable interface identity independent of programming language. The Microsoft IDL Compiler (MIDL) processes these .idl files, generating C/C++ headers, type libraries, and proxy/stub code for marshaling. This compilation step creates the contract that both clients and servers implement, ensuring binary compatibility regardless of implementation language.
// Example IDL file: PhoneBook.idl
import "oaidl.idl";
import "ocidl.idl";

[
   uuid(AAAAAAAA-BBBB-CCCC-DDDD-EEEEEEEEEEEE),
   version(1.0),
   helpstring("PhoneBook Type Library")
]
library PhoneBookLib
{
   importlib("stdole2.tlb");

   [
      object,
      uuid(12345678-1234-1234-1234-123456789ABC),
      dual,
      helpstring("IPhoneBook Interface"),
      pointer_default(unique)
   ]
   interface IPhoneBook : IDispatch
   {
      [id(1), helpstring("Add phone book entry")]
      HRESULT AddEntry([in] BSTR name, [in] BSTR number);
      
      [id(2), helpstring("Get phone number for name")]
      HRESULT GetEntry([in] BSTR name, [out, retval] BSTR* number);
      
      [id(3), helpstring("Remove entry")]
      HRESULT RemoveEntry([in] BSTR name);
      
      [id(4), helpstring("Get total entries")]
      HRESULT GetCount([out, retval] long* count);
   };

   [
      uuid(87654321-4321-4321-4321-210987654321),
      helpstring("PhoneBook Class")
   ]
   coclass PhoneBook
   {
      [default] interface IPhoneBook;
   };
};

// MIDL generates C++ header from this IDL:
// - Interface declarations
// - GUID definitions (IID_IPhoneBook, CLSID_PhoneBook)
// - Proxy/stub code for marshaling
// - Type library (.tlb) for runtime type information

Active Template Library (ATL)

The Active Template Library (ATL) simplifies COM development by providing C++ template classes that handle the boilerplate code required for implementing COM objects. ATL manages reference counting, QueryInterface implementation, class factory registration, and marshaling infrastructure through template instantiation and smart macros. Rather than manually implementing IUnknown methods and registration code for every COM class, developers inherit from ATL base classes and use macros to declare interfaces and COM maps, dramatically reducing code volume and error potential while maintaining the performance of hand-written COM code.
// ATL-based COM object implementation
#include <atlbase.h>
#include <atlcom.h>

class ATL_NO_VTABLE phone_book_atl :
   public CComObjectRootEx<CComSingleThreadModel>,
   public CComCoClass<phone_book_atl, &CLSID_PhoneBook>,
   public IPhoneBook
{
private:
   std::map<CComBSTR, CComBSTR> entries;

public:
   phone_book_atl() {}

   // ATL COM map - declares supported interfaces
   BEGIN_COM_MAP(phone_book_atl)
      COM_INTERFACE_ENTRY(IPhoneBook)
   END_COM_MAP()

   // ATL handles IUnknown automatically via templates
   
   // Implement IPhoneBook methods
   STDMETHOD(AddEntry)(BSTR name, BSTR number) {
      entries[name] = number;
      return S_OK;
   }

   STDMETHOD(GetEntry)(BSTR name, BSTR* number) {
      auto it = entries.find(name);
      if (it == entries.end()) return E_INVALIDARG;
      
      *number = it->second.Copy();
      return S_OK;
   }

   STDMETHOD(RemoveEntry)(BSTR name) {
      entries.erase(name);
      return S_OK;
   }

   STDMETHOD(GetCount)(long* count) {
      *count = static_cast<long>(entries.size());
      return S_OK;
   }
};

// Registry script for COM registration
OBJECT_ENTRY_AUTO(__uuidof(PhoneBook), phone_book_atl)

Distributed COM (DCOM)

Distributed COM (DCOM) extends COM's marshaling infrastructure to enable communication across machine boundaries, not just process boundaries. DCOM uses RPC (Remote Procedure Call) as its transport mechanism, handling network communication, security, and protocol negotiation transparently. A client on Machine A can create and use COM objects residing on Machine B with the same API calls used for local objects—COM's location transparency makes distribution a deployment decision rather than a programming model change. DCOM includes security features for authentication and authorization, though its complexity and firewall unfriendliness led to its eventual replacement by web services and .NET Remoting.

COM+ and the Evolution Beyond COM

COM+ emerged in Windows 2000 as Microsoft's answer to enterprise application server needs, combining COM with Microsoft Transaction Server (MTS) services. COM+ added automatic transaction management, object pooling, role-based security, queued components, and event services without requiring code changes to existing COM components. These services transformed COM from a component model into an application server platform competing with Java EE. However, COM+'s complexity, steep learning curve, and Windows-specific nature limited adoption. The introduction of .NET Framework in 2002 provided a cleaner, more modern alternative with simpler programming models, automatic memory management through garbage collection, and better cross-platform aspirations through standards like SOAP and web services.

The Transition to .NET and Modern Development

The .NET Framework, announced in 2000 and released in 2002, represented Microsoft's strategic shift away from COM toward managed code and simplified development. .NET provided garbage collection eliminating reference counting complexity, unified type system across languages, rich framework class library, and simpler deployment through assemblies and XCOPY installation. For COM interoperability, .NET includes Runtime Callable Wrappers (RCW) allowing managed code to call COM objects, and COM Callable Wrappers (CCW) allowing COM clients to call .NET objects. This interoperability bridge enabled gradual migration from COM to .NET while preserving investments in existing COM components. Modern Windows development uses .NET, WinRT (Windows Runtime), or native C++ with modern frameworks, though COM remains embedded in Windows internals and legacy applications.

Why Study COM Today

Despite being legacy technology, COM knowledge remains valuable for several reasons. Millions of lines of production COM code continue running in enterprise environments requiring maintenance and enhancement. Windows itself extensively uses COM—the shell, DirectX, Windows Media, and countless system services expose COM interfaces. Interoperating with legacy systems often requires understanding COM to wrap or extend existing components. The architectural principles COM introduced—interface-based programming, reference counting, QueryInterface-style capability discovery—influenced modern systems including .NET's IUnknown-like patterns and cross-platform component models. Finally, historical understanding of COM's successes and failures informs better design decisions in modern component architectures.

Course Structure and Learning Objectives

This course progresses from COM fundamentals through practical implementation. You will learn to describe COM architecture including servers, objects, and interfaces, understanding how these elements interact. Basic COM programming techniques covered include interface pointer manipulation, IUnknown interface navigation, reference counting discipline, IClassFactory usage, and IDL/MIDL compilation workflows. You'll gain hands-on experience with ATL, Microsoft's template library that simplifies COM development. Type libraries—runtime metadata describing COM interfaces—become tools for late binding and scripting support. Culminating projects include developing PhBook, an in-process COM server implementing phone book management, and PhCliXX, a COM client consuming the phone book interface, providing end-to-end experience with COM component development.

Prerequisites and Expectations

Successful completion of this course requires solid C++ programming skills including pointers, memory management, inheritance, and virtual functions. Familiarity with Windows programming concepts helps but isn't required—COM-specific Windows knowledge will be introduced as needed. Understanding basic software architecture concepts like interfaces, abstraction, and component reuse provides context for COM's design philosophy. Development environment requirements include Visual Studio with ATL support and Windows SDK providing COM headers and tools. While the course focuses on legacy COM technology, the principles and patterns learned apply broadly to component-based architecture and distributed systems development.

Course Series Context

COM Fundamentals and Basic Programming represents the first course in a two-part COM series. This foundational course establishes core concepts and basic programming techniques essential for all COM development. The second course, Advanced COM Programming, builds on this foundation covering advanced topics including threading models, apartment threading, marshaling customization, connection points and events, containment and aggregation, structured storage, and COM security. Together, these courses provide comprehensive coverage of COM technology from fundamentals through sophisticated enterprise application patterns. While COM itself is legacy technology, the architectural patterns and distributed computing concepts remain relevant for understanding modern component systems and Windows internals.

Conclusion

The Component Object Model represents a significant chapter in software engineering history, introducing concepts that influenced component-based development for decades. From binary interface contracts through reference-counted lifetime management to transparent distributed computing, COM pioneered approaches that appear throughout modern systems in various forms. While .NET, WinRT, and web services have largely supplanted COM for new development, understanding COM remains valuable for maintaining legacy systems, working with Windows internals, and appreciating the evolution of component architectures. This course provides both historical context and practical skills, enabling you to understand COM's design principles, implement basic COM components using ATL, and navigate COM-based codebases with confidence. The knowledge gained extends beyond COM itself, informing better design decisions in modern component-oriented systems by understanding what worked, what didn't, and why Microsoft eventually moved to the .NET platform.

SEMrush Software