100% Free Forever
AI-Powered Learning
Industry Expert Content
Certificates & Badges
Learn At Your Own Pace
C++

MFC and COM Integration

How MFC exposes COM support through CCmdTarget, interface maps, and dispatch maps, letting an MFC application act as an automation server or client without hand-rolling IUnknown.

Advanced MFCAdvanced11 min readJul 10, 2026
Analogies

COM Foundations Inside MFC

Every COM-aware MFC class ultimately derives from CCmdTarget, which supplies the plumbing for OLE_MAP entries the way CWnd supplies plumbing for message maps. Instead of writing QueryInterface, AddRef, and Release by hand for each interface a class supports, MFC generates them from a BEGIN_INTERFACE_PART/END_INTERFACE_PART block combined with a DECLARE_INTERFACE_MAP and BEGIN_INTERFACE_MAP in the .cpp file, and the framework's nested-class trick (an 'XInterfaceName' inner class) implements the interface's vtable while forwarding calls back to the outer CCmdTarget-derived object. Before any COM work can run, the application must call AfxOleInit() (which wraps CoInitialize) in InitInstance, or COM calls will fail with CO_E_NOTINITIALIZED.

🏏

Cricket analogy: AfxOleInit is like a match not being able to start until the toss and pitch inspection are formally completed - just as no COM call can proceed until CoInitialize (wrapped by AfxOleInit) has run for that thread.

Automation Servers with IDispatch

For late-bound Automation (the kind VBA or a scripting host uses), a CCmdTarget-derived class adds a dispatch map with DECLARE_DISPATCH_MAP and DISP_FUNCTION/DISP_PROPERTY entries, each assigned a DISPID that matches the .odl or .idl type library definition; MFC's built-in IDispatch implementation (via ColeDispatchImpl internally) then handles GetIDsOfNames and Invoke automatically by walking that map. A dual interface - exposing both IDispatch and a custom vtable-based interface - lets early-bound C++ clients call methods directly through the vtable for speed while late-bound scripting clients still go through Invoke, and the ClassWizard/Class View 'Automation' tab historically generated most of this boilerplate. Exposed properties and methods declared this way automatically show up correctly typed in the type library, which is what lets Excel VBA, for example, get IntelliSense against a custom MFC automation server.

🏏

Cricket analogy: A dual interface is like a commentator who can be understood both by hardcore fans reading detailed scorecards (early-bound vtable calls) and casual viewers just hearing simplified spoken updates (late-bound Invoke calls), from the same underlying data.

Consuming COM Objects from MFC

An MFC client consumes another COM server either through the ClassWizard-generated COleDispatchDriver-derived wrapper (built by pointing 'Add Class > From a Type Library' at the server's .tlb, producing typed InvokeHelper calls) or through the Visual C++ #import directive, which generates smart-pointer wrapper classes (like CComPtr-compatible _com_ptr_t types with a .tlh/.tli pair) directly from the type library at compile time. The #import approach is generally preferred in modern MFC code because the generated smart pointers throw _com_error exceptions on HRESULT failures and handle AddRef/Release automatically, whereas raw COleDispatchDriver code requires manually checking return codes and calling ReleaseDispatch. Regardless of the approach, every apartment-threaded COM object obtained on a given thread must only be used on that same thread unless properly marshaled, which is a frequent source of bugs when COM objects created on the UI thread are later touched from a worker thread.

🏏

Cricket analogy: #import's smart pointers auto-managing AddRef/Release is like a well-drilled fielding unit automatically covering each other's positions without needing the captain to shout instructions for every single ball, unlike manual COleDispatchDriver bookkeeping.

cpp
// Exposing an Automation method via a dispatch map (server side)
BEGIN_DISPATCH_MAP(CReportServer, CCmdTarget)
    DISP_FUNCTION(CReportServer, "GenerateReport", GenerateReport, VT_BOOL, VTS_BSTR VTS_I4)
    DISP_PROPERTY(CReportServer, "LastError", m_lastError, VT_BSTR)
END_DISPATCH_MAP()

BOOL CReportServer::GenerateReport(LPCTSTR lpszPath, long nFormat)
{
    // ... build the report ...
    return TRUE;
}

// Consuming it from an MFC client using #import
#import "reportserver.tlb" no_namespace

void CClientDlg::OnGenerate()
{
    try
    {
        IReportServerPtr pServer;
        pServer.CreateInstance(__uuidof(ReportServer));
        pServer->GenerateReport(L"C:\\out\\report.pdf", 1);
    }
    catch (_com_error& e)
    {
        AfxMessageBox(e.ErrorMessage());
    }
}

Forgetting to call AfxOleInit() in CWinApp::InitInstance is one of the most common causes of mysterious early-startup COM failures in MFC apps; CoCreateInstance and similar calls will return CO_E_NOTINITIALIZED (or crash) until it has run on that thread.

A dual interface is generally preferred over pure IDispatch when designing a new MFC automation server, because it gives early-bound C++/#import clients direct vtable-speed calls while still supporting VBA/VBScript clients through the same object's IDispatch::Invoke.

  • CCmdTarget is the base for all COM-aware MFC classes, supplying interface maps analogous to how CWnd supplies message maps.
  • AfxOleInit() must run (wrapping CoInitialize) before any COM calls succeed on that thread; forgetting it causes CO_E_NOTINITIALIZED errors.
  • BEGIN_INTERFACE_MAP/BEGIN_INTERFACE_PART generate nested XInterfaceName classes implementing each supported interface's vtable.
  • Dispatch maps (DECLARE_DISPATCH_MAP, DISP_FUNCTION/DISP_PROPERTY) implement IDispatch automatically for late-bound Automation clients like VBA.
  • A dual interface exposes both a custom vtable and IDispatch, giving early-bound speed and late-bound scripting compatibility from one object.
  • #import generates typed smart-pointer wrapper classes with automatic AddRef/Release and _com_error exceptions, preferred over manual COleDispatchDriver code.
  • COM objects are apartment-threaded; using one from a thread other than the one that created it requires proper marshaling.

Practice what you learned

Was this page helpful?

Topics covered

#MFCMicrosoftFoundationClassesStudyNotes#MicrosoftTechnologies#MFCAndCOMIntegration#MFC#COM#Integration#Foundations#StudyNotes#SkillVeris#ExamPrep