Tom's Handy Dandy MFC/COM/MIDL Recipe Book for Creating Custom Interfaces

Instructions and Guidelines for Using MFC and MIDL to Create COM Objects with Custom Interfaces

Tom Laird-McConnell

Created: November 30, 1994
Revised: August 1995
Version 2.0

A word of thanks to Steve Hiskey, whom I tricked into being my guinea pig; Dean McCrory for great feedback on MFC stuff; and all of the miscellaneous people on the e-mail aliases who patiently put up with all of my annoying questions.

Abstract

The purpose of this document is to provide a step-by-step description of how to create component object module (COM) objects that export custom interfaces from .DLLs or .EXEs by utilizing the class technology provided in the Microsoft® Foundation Class Library (MFC) classes and the Microsoft MIDL compiler. This document supposes a level of knowledge equivalent to taking the OLE Literacy course, which is highly recommended to understand this article. It also assumes that the user understands dynamic-link libraries (.DLLs), .EXEs, and C++.

Objects, Classes, Interfaces, and GUIDs

Classes

A class is defined as a data structure with a set of interfaces by which you can manipulate the data structure. C++ provides a concept of a class in a programming language. Component object modules (COM) provide a method by which a class in any language can be published with the operating system and used in a language-independent manner.

Objects

An object is an instance of a class. Whether an instance of the class was created via C++ new, or was created via COM with CoCreateInstance, the key distinction is that it is one of many possible instances of the single definition of a class.

Interfaces

An interface is a group of methods (functions) in a class. OLE is actually a set of redefined interfaces that a COM class can support. So don't get all caught up in all of the OLE interfaces and technologies when thinking about designing a COM class. If you need to do something that an OLE interface does, you should worry about what the interface looks like and what it does. Otherwise, the only interfaces that you really need to know are IUnknown and IClassFactory().

GUIDs (CLSIDS and IIDS)

A GUID is a globally unique identifier. A GUID is a 128-bit, or 8-byte, number. It is generated automatically for you by a tool called GUIDGEN. Every COM class must have a GUID, and that GUID is called a CLSID, or Class ID. Each COM class has an entry created in the registry under the HKEY_CLASSES\CLSID where the CLSID is registered, and a pointer to the DLL/EXE file that implements that CLASSID. An IID is a GUID for an interface to a COM Class. It is used to ask an object if it supports a particular interface. It also gets registered with the system in the register, under HKEY_CLASSES\Interfaces.

Tree and File Naming Standards

All of the different types of files can get very confusing. To try to minimize some of that confusion, I have come up with the following tree and file naming standards. For purposes of this discussion, let's say that we have a COM object called "Labrador". It supports two interfaces, IMammal() and IDog(), and will live in a .DLL or .EXE (it doesn't matter which).

\Source\
    Classes\
        Support\             // Common files for supporting COM
RPCHelp.c        // Interface RPC helper code
                ComHelp.h         // Support file for OLE implementation
Ifaces\
            IMammal\
                IMammal.IDL  // Interface definition in IDL language.
                IMammal.h    // Interface definition.
                IMammal_p.c  // Interface proxy code for OUT_OF_PROC.
                IMammal_s.c  // Interface stub code for OUT_OF_PROC.
                dlldata.c    // Interface DLL support for OUT_OF_PROC.
                IMammal.mak  // Project file for OUT_OF_PROC.
            IDog\
                IDog.h       // INPROC only version, (see addendum).
    
Labrador\
            Labrador.mak     // Make file for Labrador COM Class.
            Labrador.h      // C++ class definition.
            Labrador.cpp    // C++ class implementation.
        Husky\    
            Husky.mak        // Make file for Husky COM Class.
            etc....

Interface Files

Interface files are header files that define an interface to an class. They are shared among multiple classes, and so they should go into a common location, such as Interfaces. For instance, Labrador, Husky, and Newfoundland classes all share the same IDog.h interface definition, and so there should be one easy place to find the interface definition—namely, Ifaces\IDog. The name of interfaces and there files should always be IXXXX where XXX is the name of the interface. So IDog.h defines the IDog interface, and IMammal.h defines the IMammal interface. Additionally, if you want to make your custom interface supportable across a process boundary (OUT_OF_PROC), these directories also contain the marshalling code implementation that is shared by everyone who exposes this interface. This allows the marshalling code to be implemented once and shared by all classes that expose that interface.

COM Class Projects

Each COM class lives in a .DLL or .EXE, so it should exist in its own directory, as its own project. It should be in the tree under the Source\Classes directory and the name should be the name of the COM Class. For instance, our "Labrador" class ought to be implemented in a project called Labrador.mak in \Source\Classes\Labrador and the output of the project should be Labrador.dll, or Labrador.exe.

Class ID Headers

ClassID headers are essentially simple include files that define the unique GUID for a particular COM class. Because they are shared like interface files, they should go into a common directory such as \Source\Classes\ClassIds and be named XXXXID.H. So the Labrador ClassID header file would be called LabradorID.h and live in \Source\Classes\ClassIDs.

COM Class Implementation Files

The COM class implementation file is a C++ class. As such it is convenient to follow the class naming convention that MFC follows, namely using a C as a prefix (in front of the class name) to signify that this is the header or the implementation of the class. So, for instance, the Labrador classes internal definition is defined in CLabrador.h and the implementation itself is in CLabrador.cpp. Obviously, these files would go into the same directory as the project that owns them, namely \Source\Classes\Labrador.

Tools and Environment

This section describes how to add some commonly used tools to the Visual C++™ Tools menu to make it easier to work with. It is entirely optional to do the items in this section, but it is easier to have them available in this manner.

MIDL Compiler

The MIDL compiler that comes with the Win32® SDK has been modified to provide marshalling support for interfaces across compiler boundaries. Although we could do custom interface marshalling without MIDL, it's much easier if it's around. Unfortunately, this means we need to have both the Win32 SDK and Visual C++ 2.0 installed, even though we will only be using the MIDL compiler portion of the Win32 SDK. Hopefully this will change in the future.

  1. Install the Win32 SDK.

    Note: You don't need any of the samples or special tools, so to save disk space you can remove those items when installing by using the custom screen.

  2. When it asks you to register environment variables in the system, agree to do it.

Visual C++ 2.2

  1. Install Visual C++ 2.2.

  2. When it asks you to register environment variables in the system, agree to do it.

Customizations to Visual C++ Tools Menu

These are optional additions, but they do make your life a little bit easier.

GUIDGEN

This section shows you how to add a command to the Tools menu to generate a UUID, which can be used for an interface ID, a class ID, or any other 128-bit UUID. To use it, select Tools\Generate GUID, and it will create a unique UUID that can be copied and pasted into your code in four different formats.

  1. Select Tools\Customize, and click Tools.

  2. Click the New button.

  3. In Menu Text, type Generate UUID.

  4. In Command, type GUIDGEN.EXE.

  5. Clear out Arguments.

  6. Clear out Initial dir.

MIDL compiler

This section shows you how to add a command to the Tools menu to compile an IDL file using the MIDL compiler. Once this has been added, you can load the .IDL file, select Tools/Compile IDL, and the MIDL compiler will generate all of the .C source files for marshalling.

  1. Select Tools\Customize, and click Tools.

  2. Click the New button.

  3. In Menu Text, type Compile IDL file.

  4. In Command, type MIDL.EXE.

  5. In Arguments, type /ms_ext /char unsigned  /c_ext $FileName.

  6. In InitialDir, type $FileDir.

  7. Click Redirect to Output Window (turn it on).

REGEDIT

This section shows you how to add a command to the Tools menu to take a .REG file and load it into the registry. Again, to use it, load the .REG file, and then select Tools\Register .REG file, and it will create all of the entries.

  1. Select Tools\Customize, and click Tools.

  2. Click the New button.

  3. In Menu Text, type Register .REG File.

  4. In Command, type REGEDIT.EXE.

  5. In Arguments, type -s $FileName.

  6. In InitialDir, type $FileDir.

Create Support Modules

Create ComHelp.h

This file contains some support macros that make implementing interfaces a little bit easier. In particular, these macros will create the redirection back to the parent object for the IUnknown interface. This reduces the amount of duplicate code you have to create.

  1. Create file Source\Support\ComHelp.h

  2. Put the following set of macros into it:
    // ---------------------------------------------
    // IUnknown macros for implementing redirection to parent
    // IUnknown implementation
    // ---------------------------------------------
    
    #ifndef IMPLEMENT_IUNKNOWN
    
    #define IMPLEMENT_IUNKNOWN_ADDREF(ObjectClass, InterfaceClass) \
        STDMETHODIMP_(ULONG) ObjectClass::X##InterfaceClass::AddRef(void) \
        { \
            METHOD_PROLOGUE(ObjectClass, InterfaceClass); \
            return pThis->ExternalAddRef(); \
        }
    
    #define IMPLEMENT_IUNKNOWN_RELEASE(ObjectClass, InterfaceClass) \
        STDMETHODIMP_(ULONG) ObjectClass::X##InterfaceClass::Release(void) \
        { \
            METHOD_PROLOGUE(ObjectClass, InterfaceClass); \
            return pThis->ExternalRelease(); \
        }
    
    #define IMPLEMENT_IUNKNOWN_QUERYINTERFACE(ObjectClass, InterfaceClass) \
        STDMETHODIMP ObjectClass::X##InterfaceClass::QueryInterface(REFIID riid, LPVOID *ppVoid) \
        { \
            METHOD_PROLOGUE(ObjectClass, InterfaceClass); \
            return (HRESULT)pThis->ExternalQueryInterface(&riid, ppVoid); \
        }
    
    #define IMPLEMENT_IUNKNOWN(ObjectClass, InterfaceClass) \
        IMPLEMENT_IUNKNOWN_ADDREF(ObjectClass, InterfaceClass) \
        IMPLEMENT_IUNKNOWN_RELEASE(ObjectClass, InterfaceClass) \
        IMPLEMENT_IUNKNOWN_QUERYINTERFACE(ObjectClass, InterfaceClass)
    
    #endif
    

Create RPCHelp.c

This section shows you how to create interface libraries that use RPC code. When you do this, you have to tediously change your make file to add four somewhat obscure library references. By creating a file with library pragmas in it, you can reduce that step to simply adding a file to your project, which is a nicety.

  1. Create a file called Source\Classes\Support\RPCHelp.c.

  2. Edit it to look like this:
    #pragma comment(lib, "rpcndr.lib") 
    #pragma comment(lib, "rpcdce4.lib")
    #pragma comment(lib, "rpcns4.lib")
    #pragma comment(lib, "rpcrt4.lib")
    

Implementing Custom Interfaces

A custom interface is an interface that the system doesn't already support (for instance, the OLE Interfaces are defined by the operating system and have built in marshalling support.). If your interface is used only to talk to an INPROC server, you don't need to provide marshalling code to package parameters between two process spaces. On the other hand, if your interface is a custom interface that is not supported by the OS, and it might be used between two processes or two machines, you need to provide marshalling code to transfer parameter data across process boundaries. Normally, this is a really tedious, arcane process, but with the latest release of the Microsoft RPC MIDL compiler, you can automatically generate the necessary DLL code by describing your interface with the interface definition language (IDL).

Create Project File in Classes\Ifaces Directory

Each Interface should go into its own directory to provide a logical place for interface marshalling support code to live. So, first we create a Visual C++ project.

  1. Create Project \Source\Classes\Ifaces\IMammal\IMammal.mak as a dynamic-link library.

  2. When it asks for a project name, type IMammal.

  3. When it asks for a file to add, add Classes\Support\RPCHelp.c.

Create IDL File

Now we need to define our interface using the interface definition language (IDL). To do so, take the following steps:

  1. Create a file named after your interface with an IDL extension (IMammal.IDL in our case).

  2. Type in the following code:
    [
    object,
    uuid(56b8d0e0-fa8b-11cd-8a02-00608cc80b9c),
    pointer_default(unique)
    ]
    
    interface IMammal : IUnknown
    {
    import "unknwn.idl";
    
    HRESULT GetSpeciesName([out, string, size_is(255)] char *p);
    HRESULT    IsAlive([out] BOOL *pBool);
    }
    

    Note: All return values must be HRESULT (standard OLE return value). If the return value is something different, MIDL will not provide marshalling information to marshal across process boundaries (this is for network support). In the event of a network error, they want to ensure they can return a valid error code without having to throw an exception.

  3. Select Tools\Generate UUID.

  4. Copy and paste the 128-bit number into the UUID section in the .IDL file. This is the unique 128-bit number assigned to your interface. You only need to generate this once when the interface is created.

  5. Specify your interface as if it were a C++ class, following the .IDL. For more information about IDL, check out the RPC documentation in the Win32 SDK [ Ed. note: the Win32 SDK is now called the platform SDK] (MSDN Library, Platform, SDK, and DDK Documentation).

  6. Add it to the project file list by selecting Project\File, and adding IMammal.IDL.

Compile .IDL File with MIDL Compiler

Now we run the .IDL file through the MIDL compiler, which generates a bunch of C source code.

  1. Load the IMammal.IDL file.

  2. Select Tools\Compile IDL. This command will generate source code based on your interface definition.

  3. You will need to add some entries to the include path for Visual C++ so it can pick up MIDL include files. Select Tools/Options/Directories, click INCLUDE, and make sure that win32sdkdrive\mstools\h and win32sdkdrive \mstools\idl are added to the end of your include path.

  4. You will also need to add the mstools library to your path, so click LIBS and add win32sdkdrive \mstools\lib to the end of the library path.

Add .C Files to Project

The MIDL compiler has now generated three new C source files: IMammal_i.c, IMammal_p.c, and dlldata.c. These files actually implement the marshalling code for our IMammal interface in a .DLL called IMammal.dll. Now we need to add these source files to the project so Visual C++ can compile them.

  1. Select Projects\Files.

  2. Add all of the .C files to the project.

Create DEF File for Interface .DLL

The .DLL created by this project, although generated by the MIDL compiler, is just another OLE object of a special type, with an IMarshall interface defined. As such, the .DLL needs to have its DllGetClassObject and DllCanUnloadNow() entry points exported. I think that the MIDL compiler should have spit this out, but it didn't, so we have to create the .DEF file for our .DLL and link it into the project.

  1. Create IMammal.DEF as follows:
    LIBRARY     IMAMMAL
    DESCRIPTION 'IMAMMAL Interface Marshalling'
    
    EXPORTS
    DllGetClassObject
    DllCanUnloadNow
    
  2. Add to the project via Projects\Files.

Build the .DLL

Build the project. You should get a .DLL file called IMammal.DLL.

Create .REG File for Interface

We now need to build a .REG file that provides the registration information for our interface.

  1. Create IMammal.reg.

  2. Add the following keys, substituting the UUID for your interface and path and .DLL name as appropriate:
    REGEDIT
    HKEY_CLASSES_ROOT\Interface\{7cca10d0-f823-11cd-8a02-00608cc80b9c} = IMammal
    HKEY_CLASSES_ROOT\Interface\{7cca10d0-f823-11cd-8a02-00608cc80b9c}\ProxyStubClsid32 = {7cca10d0-f823-11cd-8a02-00608cc80b9c}
    HKEY_CLASSES_ROOT\CLSID\{7cca10d0-f823-11cd-8a02-00608cc80b9c} = IMammal_PSFactory
    HKEY_CLASSES_ROOT\CLSID\{7cca10d0-f823-11cd-8a02-00608cc80b9c}\InprocServer32 = c:\Source\Classes\Ifaces\IMammal\WinDebug\IMammal.dll
    
  3. With file still loaded and in front, select Tools\Register REG file. This will actually register your interface .DLL with the OLE system.

Implement IDog

For purposes of our sample code, follow the above instructions again, and create our interface for IDog, whose .IDL file looks like this:

[
    object,
    uuid(7cca10d0-f823-11cd-8a02-00608cc80b9c),
    pointer_default(unique)
]

interface IDog : IUnknown
{
    import "unknwn.idl";

    HRESULT GetPetName([out, string, size_is(255)] char *p);
    HRESULT SetPetName([in, string] char *p);
    HRESULT IsBarking([out] BOOL *pBool);
}

Implementing COM Classes

This section shows you how to create a COM object, which, by virtue of using the MFC libraries, can be compiled as either an INPROC .DLL or an OUT_OF_PROC .EXE. We do it in such a way that we are able to leverage the MFC ClassWizard support. Using ClassWizard to tack on OLE Automation in the future is a big savings when you get to that point. In addition, we are able to leverage all of the MFC libraries and save ourselves from having to implement tons of redundant code.

When we are done creating our CLabrador class, we will have a class CLabrador that has two internal classes, XMammal and XDog, each of which is derived from the interface virtual class IMammal and IDog, respectively. Therefore the implementation of IMammal that CLabrador exports is the internal class XMammal.

The details of this process are hidden by the macros that are used. The full description of what goes on under the hood is in MFC Technical Article 38 (MSDN Library Archive, Technical Articles, Visual C++).

Create a Project with AppWizard

  1. Create your Source\Classes directory.

  2. At File/New/Project in Visual C++, select MFC AppWizard (DLL).

  3. Make sure that the root directory is the Source\Classes directory.

  4. When it asks for a project name, type the name of your class. In our case, type "Labrador".

  5. Click/press Create

  6. Select “Use MFC in static library”.

  7. Select “OLE Automation”.

  8. Click/press Finish.

Use ClassWizard to Create a COM Class

We reenabled ClassWizard because we want to create a new C++ class derived from the MFC class CCmdTarget. A CCmdTarget is a base class that implements such functions as IUnknown, IDispatch for OLE Automation, and message dispatches for dealing with windows messages. It's a good thing to derive from, and by doing it from within ClassWizard, we not only get the base COM functionality, but we gain the ability to be created via COleObjectFactory, and we also get ClassWizard support for adding an OLE Automation interface to our COM object in the future, which is a very good thing. At any rate, you can ignore this description and just follow this list of commands. It's harder to read it than it is to just click and type everything in. Such is life.

  1. Go into ClassWizard, click Add Class.

  2. For the Class Name, type in CLabrador.

    Note: We put a C in front of the name because this is the definition and implementation of the internal C++ class. Therefore the name of the DLL is Labrador, which is the COM class name, and the name of the internal C++ class is CLabrador. This is a personal preference; you are free to use whatever naming conventions you like.

  3. For the Class Type, select CCmdTarget. This is the MFC class from which you are going to derive all of your cool functionality.

    Note: You might consider changing the names for the header and C++ source file at this point to follow whatever naming schemes you want, particularly if you don't like the ones generated by Visual C++.

  4. Check the OLE Automation check box, and check the OLE Creatable check box if your object can be created by itself. If it cannot, because it is dependent on another object already existing, do not click the OLE Creatable check box.

  5. The external name for the class should be “Labrador”. (That is, the class “Labrador” is implemented by Clabrador.)

  6. Click Create Class. You now have the framework for your Labrador class!

Create Class ID for Your Class

We want to have a header file that contains just the CLASSID for your Labrador COM class, without revealing the details of the implementation of it. We also want it to be in a common directory, so anyone interested in it can have a header file that easily defines it. So let's create one.

  1. Create Source\Classes\ClassIDs\LabID.h

    Note: We do not put the C on the front because this is a file referring to the COM class, not the internal C++ class. We put the ID on the end to signify that this is the class ID for the Labrador COM class. Again, this is just a style issue, so do whatever you like.

  2. You could use GUIDGEN to generate your GUID for your CLASSID, but ClassWizard has already generated a GUID for your class when it generated the code. So go to Labrador.cpp, and copy the line that looks like this:
    IMPLEMENT_OLECREATE(CLabrador, "Labrador", 0x44bed660, 0xf517, 0x11cd,
    0x8a, 0x2, 0x0, 0x60, 0x8c, 0xc8, 0xb, 0x9c) to LabradorID.h.
    
  3. Now change IMPLEMENT_OLECREATE( to be DEFINE_GUID(.

  4. Change CLabrador to be CLSID_Labrador, because this macro is defining the variable CLSID_Labrador.

  5. Remove the string "Labrador", which is not used in the DEFINE_GUID macro.

  6. Save the file. It should look as follows:
    //
    // CLASS ID for CLabrador object
    //
    #ifndef _CLSID_Labrador_
    #define _CLSID_Labrador_
    
    // {A0D0B312-F1D5-11CD-8A01-00608CC80B9C}
    DEFINE_GUID(CLSID_Labrador, 
    0xa0d0b312, 0xf1d5, 0x11cd, 0x8a, 0x1, 0x0, 0x60, 0x8c, 0xc8, 0xb, 0x9c);
    
    #endif
    

Create an Interface Definition Section for Each Supported Interface in Your Object

Each Interface exposed by our COM class is implemented using macros. These macros end up expanding to embedded classes derived from the virtual IXXXX interface definition. For an in-depth description of how all of this works, check out Technical Note 38 (MSDN Library Archive, Technical Articles, Visual C++). It gives a complete background on alternative methods of doing this, as well as on the workings of the macros that MFC provides.

  1. Edit Labrador.h (created above).

  2. Add #include "..\Ifaces\IDog\IDog.h" and #include "..\Ifaces\IMammal\IMammal.h" to the top of the header file.

  3. Define any internal variables in the Attributes section. In our case we will have two CStrings—"m_SpeciesName" and "m_PetName"—and two BOOL members, m_IsAlive and m_IsBarking.

  4. Add a DECLARE_INTERFACE_MAP() line at the bottom of your protected: section of the class.

  5. Add a BEGIN_INTERFACE_PART(Name, IName) and END_INTERFACE_PART(Name) where IName is the interface you are adding, and Name is that interface minus the 'I' character. What actually happens here is the macros are expanded to be class XName : public IName (that is to say, an internal class called XName is defined, which is derived from the virtual base class IName). If you don't understand what I just said, don't worry about it. Read Technical Note 38 (MSDN Library Archive, Technical Articles, Visual C++) if you want a real explanation of the magic here.

  6. Inside this section add a line for every method in your interface (refer back to the Objects\Interfaces files for the methods).

    Note 1: All return values must be HRESULT (standard OLE return value). If the return value is something different, MIDL will not provide marshalling information to marshal across process boundaries (this is mainly to provide network support). In the event of a network error, they want to ensure they can return a valid error code without having to throw an exception.

    Note 2: You don't have to define IUnknown methods AddRef(), Release(), and QueryInterface(), because they are done automatically.

    Note 3: Because at this point you are dealing with C++, you don't need to define the THIS parameter as you had to in the INTERFACE file.

  7. Add another BEGIN_INTERFACE_PART, END_INTERFACE_PART for each one of your interfaces.

  8. When you are done, Labrador.h should look as follows:
    // Labrador.h : main header file for the LABRADOR DLL
    //
    
    #ifndef __AFXWIN_H__
        #error include 'stdafx.h' before including this file for PCH
    #endif
    
    /////////////////////////////////////////////////////////////////////////////
    // CLabradorApp
    // See Labrador.cpp for the implementation of this class
    //
    
    class CLabradorApp : public CWinApp
    {
    public:
        CLabradorApp();
    
    // Overrides
        // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CLabradorApp)
        public:
        virtual BOOL InitInstance();
        //}}AFX_VIRTUAL
    
        //{{AFX_MSG(CLabradorApp)
            // NOTE: ClassWizard will add and remove member functions here.
            //    DO NOT EDIT what you see in these blocks of generated code !
        //}}AFX_MSG
        DECLARE_MESSAGE_MAP()
    };
    
    
    /////////////////////////////////////////////////////////////////////////////
    
    
    /////////////////////////////////////////////////////////////////////////////
    // CLabrador command target
    
    #include "..\Ifaces\IDog\IDog.h" 
    #include "..\Ifaces\IMammal\IMammal.h" 
    
    class CLabrador : public CCmdTarget
    {
        DECLARE_DYNCREATE(CLabrador)
    protected:
        CLabrador();           // protected constructor used by dynamic creation
    
    // Attributes
    public:
        CString m_SpeciesName;
        CString m_PetName;
        BOOL    m_IsAlive;
        BOOL    m_IsBarking;
    
    // Operations
    public:
    
    // Overrides
        // ClassWizard generated virtual function overrides
        //{{AFX_VIRTUAL(CLabrador)
        public:
        virtual void OnFinalRelease();
        //}}AFX_VIRTUAL
    
    // Implementation
    protected:
        virtual ~CLabrador();
    
        // Generated message map functions
        //{{AFX_MSG(CLabrador)
            // NOTE - the ClassWizard will add and remove member functions here.
        //}}AFX_MSG
    
        DECLARE_MESSAGE_MAP()
        DECLARE_OLECREATE(CLabrador)
    
        // Generated OLE dispatch map functions
        //{{AFX_DISPATCH(CLabrador)
            // NOTE - the ClassWizard will add and remove member functions here.
        //}}AFX_DISPATCH
        DECLARE_DISPATCH_MAP()
        DECLARE_INTERFACE_MAP() 
    
        BEGIN_INTERFACE_PART(Mammal, IMammal)
            STDMETHOD(GetSpeciesName)(char *pStr);
            STDMETHOD(IsAlive)(BOOL *pBool);
        END_INTERFACE_PART(Mammal)
    
        BEGIN_INTERFACE_PART(Dog, IDog)
            STDMETHOD(GetPetName)(char *pStr);
            STDMETHOD(SetPetName)(char *pStr);
            STDMETHOD(IsBarking)(BOOL *pBool);
        END_INTERFACE_PART(Dog)
    };
    

Modify Labrador.cpp to Include Header Files

We need to add a couple of include files from our support directory:

  1. Edit Labrador.cpp.

  2. Add an #include "..\support\ComHelp.h" to the top of this file. This adds some helper macros I have created.

  3. Add all of your interface files, such as “..\Ifaces\Idog\Idog.h” and “..\Ifaces\Imammal\Imammal.h”.

Add .EXE Support Sections to Your COM Object

The next step is to modify the CLabradorApp so that it can be recompiled as an executable or a .DLL (which is to say, INPROC or OUTOFPROC). We do this by adding initialization calls to InitInstance() and by adding #ifdefs around the .DLL entry points.

  1. Edit CLabrdorApp::InitInstance(). Add a section that looks like this:
    #ifndef _WINDLL
    // This section needed only if we are an .EXE.
    
    // Figure out if we are being started by OLE.
    if (!RunEmbedded() && !RunAutomated())
    {
    COleObjectFactory::UpdateRegistryAll();
    AfxMessageBox("Can't run standalone.");
    return FALSE;
    }
    
    // Initialize OLE libraries.
    if (!AfxOleInit())
    {
    AfxMessageBox("OLE Initilization failed!");
    return FALSE;
    }
    #endif
    
  2. Find the section with the comment “Special entry points required for inproc servers”, followed by DllGetClassObject() and DllCanUnloadNow().

  3. Add #ifdef _WINDLL around the DllGetClassObject and DllCanUnloadNow APIs. This removes the INPROC entry points when you are making an OUT_OF_PROC .EXE server. It should look like this:
    // Special entry points required for inproc servers
    
    #if (_MFC_VER >= 0x300)
    #ifdef _WINDLL
    STDAPI DllGetClassObject(REFCLSID rclsid, REFIID riid, LPVOID* ppv)
    {
            return AfxDllGetClassObject(rclsid, riid, ppv);
    }
    
    STDAPI DllCanUnloadNow(void)
    {
            return AfxDllCanUnloadNow();
    }
    #endif
    #endif
    

Define Interface Mapping from IID to Interface Implementation

The next step is to implement the methods we defined above using the BEGIN_INTERFACE_PART sections. Again, this is fully documented in Technical Note 38 (MSDN Library Archive, Technical Articles, Visual C++). The DYNCREATE stuff is necessary to be able to utilize a common Class Factory provided by MFC, as well as the ability to identify the lineage of a particular class. For information on DYNCREATE, check out the Visual C++ documentation.

  1. Edit Labrador.cpp.

  2. Add a section that declares your interface map. This section essentially defines a table of mappings between the Interface IIDs for each interface, and the actual internal class definition that implements that interface within your class. This allows the CCmdTarget to automatically handle QueryInterface for us.

  3. Labrador.cpp should look as follows:
    //
    // INTERFACE MAP for CLabrador
    //
    BEGIN_INTERFACE_MAP(CLabrador, CCmdTarget)
        INTERFACE_PART(CLabrador, IID_IMammal, Mammal)  // map IID_IMammal to
                                                        // m_XMammal
        INTERFACE_PART(CLabrador, IID_IDog, Dog)        // map IID_IDog to m_XDog
    END_INTERFACE_MAP()
    

Implement Interfaces Within Your Object

Now we actually implement the methods defined in our interfaces. Note that for the IUnknown case, we defer all of our IUnknown requests to the parent, allowing it to implement the details of those methods once. The parent ends up passing them to CCmdTarget, which kindly implements all of our Reference tracking and QueryInterface duties for us. The reason we use the ExternalAddRef() method is that CCmdTarget uses that as a way for the internal object to specify aggregation. This is essentially MFC's method for hiding the details of object aggregation. (If you don't know what that is, don't worry about it.)

  1. To support the IUnknown interfaces, we add a line IMPLEMENT_IUNKNOWN(CLabrador, Mammal). This uses the macros we defined in MFCComHelp.h to do the redirection that was described in step 2. This macro implements all of the IUnknown interfaces for IMammal.

  2. Now we actually implement the methods for each interface subclass, which is done with macros. Following is the IMammal definition. Notice that we are defining a method for the internal class XMammal, for the class CLabrador. Because we are an internal class, we don't have access to who our parent is, but this is what the METHOD_PROLOGUE macro does: It provides us with a pThis pointer, which is a pointer to the parent class (CLabrador in our case). Because the XMammal has friend status with CLabrador, it has access to all of the private parts of its parent, and we can access all of CLabrador's data directly via the pThis pointer.
    // *********************************************************************
    // ************ CLabrador::XMammal implmentation of IMammal ************
    // *********************************************************************
    
    // IUnknown methods for XMammal class 
    IMPLMENT_IUNKNOWN(CLabrador, Mammal)
    
    // Extra methods that are part of IMammal
    STDMETHODIMP CLabrador::XMammal::GetSpeciesName(char *pStr)
    {
    METHOD_PROLOGUE(CLabrador, Mammal);     // Creates a pThis pointer to 
    // CLabrador Parent
    TRACE("CLabrador::XMammal::GetSpeciesName()\n");
    if (pStr)
    strcpy((char *)pStr, (LPCTSTR)pThis->m_SpeciesName);
    return (HRESULT)NOERROR;    // Gave them our name
    }
    
    STDMETHODIMP CLabrador::XMammal::IsAlive(BOOL *pBool)
    {
    METHOD_PROLOGUE(CLabrador, Mammal);     // Creates a pThis pointer to 
    // CLabrador Parent.
    TRACE("CLabrador::XMammal::IsAlive()\n");
    if (pBool)
    {
    *pBool = pThis->m_IsAlive;    // Gave them our alive state.
    return S_OK;
    }
    return E_BADPOINTER;
    }
    
  3. Repeat step 2 for any additional interfaces defined, such as IDog.

Note   Each interface requires its own AddRef/Release/QueryInterface implementation.

Add Interface IID Definition Modules to Project

When the MIDL compiler generates your IID for an interface, it puts only an external reference to that IID structure in the IFoo.h header file. To get the actual definition of the IID structure, you need to link with a small module that contains the IID as a structure. The developers did this so that there would only be one definition for your application.

  1. Select Projects/Files.

  2. Add ..\Ifaces\IDog\IDog_i.c.

  3. Add ..\Ifaces\IMammal\IMammal_i.c.

Fix Precompiled Header Problem

When VC++ generates your project, it has precompiled headers up to STDAFX.H checked. Unfortunately, this doesn’t work with the files generated by the MIDL compiler, so you need to turn on Automatic Precompiled headers.

  1. Select Projects/Settings.

  2. Click C/C++ booktab.

  3. Click the “Precompiled Headers” category

  4. Turn off “Use PCH file”.

  5. Turn on Automatic use of Precompiled Headers.

Add.EXE Targets to Project to Support OUT_OF_PROC .EXE Object

So far we are set up for our object to be built as an INPROC server .DLL. Because we are using the MFC CWinApp-derived functions for our ClassFactory support code, we can change our object to be an OUTOFPROC server by simply creating a new .EXE target. All of the code stays the same, so in this section, we change our project targets so that we can build (1) Win32 debug and release DLL INPROC versions and (2) Win32 debug and release EXE OUT OF PROC versions.

  1. Click Project/Targets for the class project file.

  2. Click "Win32 Debug" and rename as "Win32 DLL Debug".

  3. Click "Win32 Release" and rename as "Win32 DLL Release".

  4. Click New.

  5. Type "Win32 EXE Debug"; click Win32 Application, Use Default Settings, and Debug Build.

  6. Click New.

  7. Type "Win32 EXE Release"; click Win32 Application, Use Default Settings; and turn off Debug Build.

  8. Exit this dialog, and go into the Project/Settings Dialog.

  9. Click the Win32 EXE Debug entry, go to the "General" section, and select "Use MFC In Shared DLL". Make the intermediate and output directory “WinEXEDebug”.

  10. Click the Win32 EXE Release entry, go to the "General" Section, and select "Use MFC In Shared DLL". Make the intermediate and output directory “WinEXERel”.

This makes the DLL target use WinDebug and WinRel, and makes the .EXE target WinExeDebug and WinExeRel. Now you can build a .DLL or .EXE by simply selecting the target. All the source code stays the same!

Build Your .DLL or .EXE

  1. Select the .DLL target.

  2. Build it.

  3. Select the .EXE Target.

  4. Build it.

You now have both an INPROC and an OUTOFPROC server for your object.

Register Your COM Object

Now we need to register with the system that your ClassID exists inside your DLL, and where your DLL resides. To do that, create a registry file. This makes it easy to define the registry entries once, and then distribute them to people who want to use it.

  1. Create a file called Labrador.reg.

  2. Add a line: REGEDIT.

  3. Add a line as follows, but substitute your ClassID string for the numbers below, and use the real path to your DLL after the equal line:
    HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-123456789ABC}\InprocServer32 = 
    \path\to\labrador.dll
    
  4. Add a line as follows, but substitute your ClassID string for the numbers below, and use the real path to your .EXE after the equal line:
    HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-123456789ABC }\LocalServer32 = 
    \path\to\labrador.exe
    
  5. Save your file as Labrador.reg. It should look like the following:
    REGEDIT
    HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-123456789ABC } = Labrador Class
    HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-123456789ABC }\InprocServer32 = 
    \source\classes\Labrador\WinDebug\Labrador.dll
    HKEY_CLASSES_ROOT\CLSID\{12345678-1234-1234-1234-123456789ABC }\LocalServer32 = 
    \source\classes\Labrador\WinExeDebug\Labrador.exe
    
  6. Run regedit -s labrador.reg That's it! You have now registered your class with the system!

You can now test your COM object with custom interfaces!

Using Your COM Object and Its Interfaces

OK, so now you have created a COM object, and you have registered it with the system. Now it is time to write a test program that actually uses the object.

Create a Test Application with AppWizard

Umm. . . push the button. If you can't figure this one out, you probably haven't really made it this far yet.

Include Interface Header File

  1. Add #include for INITGUID.H.

  2. Include all Interface definition files (for instance, IMammal.h and IDog.h).

  3. Include all ClassID definition files (such as LabID.h). For example:
    #include <afxole.h>
    #include <initguid.h>
    #include "..\Ifaces\IMammal\IMammal.h"
    #include "..\Ifaces\IDog\IDog.h"
    #include "..\ClassIDs\LabID.h"
    

Add Interface IID Files to the Project

When the MIDL compiler generates an IXXXX.H file, it generates the IID_IXXXX as a extern reference to a global variable. Then it also spits out a file called IXXXX_I.C. This file is nothing more than the global variable declaration of the IID_IXXXX. It operates this way so that there is only one definition for the whole project, as opposed to a definition that includes the header inside each object file. You need to add these interface global declaration files to your project so that they are compiled with your project.

  1. Open Projects/Files.

  2. Add Ifaces\IMammal\IMammal_i.c (this has just the 16-byte IID identifier in it).

  3. Add Ifaces\IDog\IDog_i.c (this has just the 16-byte IID identifier in it).

  4. Add any other IID definition files.

CoCreateInstance

CoCreateInstance() is the function that will create an instance of the object, as well as return a pointer to a particular interface of that object. Once you get your interface, you use the pointer to it to your heart's content, but call Release() on it when you are done. Here is some sample code:

    IMammal *pIMammal;
    IDog *pIDog;
    char szTmp[255];

    // Init OLE libraries.
    AfxOleInit();

#ifdef USE_IN_PROC    
    //
    // Create an instance of a Labrador IN PROC.
    //
    CoCreateInstance(   CLSID_Labrador,         // CLASSID for CLabrador.
                        NULL,                   // Ignore this.
                        CLSCTX_INPROC_SERVER,   // Server.
                        IID_IMammal,            // Interface you want.
                        (LPVOID *)&pIMammal);   // Place to store interface.
#else
    //
    // Create an instance of a Labrador OUT_OF_PROC.
    //
    CoCreateInstance(   CLSID_Labrador,         // CLASSID for CLabrador.
                        NULL,                   // Ignore this.
                        CLSCTX_LOCAL_SERVER,    // Server.
                        IID_IMammal,            // Interface you want.
                        (LPVOID *)&pIMammal);   // Place to store interface.
#endif    
    if (pIMammal)
    {
        pIMammal->GetSpeciesName(szTmp);
        TRACE("Species name is <%s>\n",szTmp);
        BOOL fIsAlive;
        pIMammal->IsAlive(&fIsAlive);
        if (fIsAlive)
        TRACE("And it's alive!\n");
     else
             TRACE("And it's dead!\n");

           // Hey, maybe it's a Dog; let's ask it!

if (pIMammal->QueryInterface(IID_IDog, (LPVOID *)&pIDog) == S_OK)
          {
           BOOL fIsBarking;
           pIDog->GetPetName(szTmp);
               TRACE("Dog's name is <%s>\n", szTmp);

           pIDog->IsBarking(&fIsBarking);
              if (fIsBarking)
             TRACE("BARK! BARK! BARK! BARK!\n");

           pIDog->SetPetName("KIVA");
           pIDog->GetPetName(szTmp);
             TRACE("Dog's New name is <%s>\n", szTmp);

     // Release our IDog interface
     pIDog->Release();
     }        
      // Release our IMammal interface
         pIMammal->Release();
         // Get COM to free unused libraries (a favor to COM).
         CoFreeUnusedLibraries();
      }

Appendix A. Common Problems

The following are tips that will help you avoid common problems:

Appendix B. Debugging Issues with COM Objects

Problems with Startup

One of the problems you may have with an out-of-process server is that you aren't running in the process that is being debugged. One way you can get around this is to add a DebugBreak() line as the first statement in your CWinApp:InitInstance() method. This will cause an exception, which if you press "cancel" will drop you into a debugger in the server executable. Once inside, you can set breakpoints as appropriate, running with two debuggers. This is useful for debugging situations in which you haven’t even successfully created the object yet.

OLE RPC Debugging Support

If you are not having a problem with the initial startup code, Visual C++ actually supports tracing into a OLE RPC call to an out-of-process server. If you are on a line, such as pIDog->GetPetName() from the above sample, and you press F8 to trace the method, Visual C++ will actually launch another copy of Visual C++, attach it to the server executable, and trace the method. It will then Ping-Pong back and forth as you step in and out of the different modules. This is a good thing, and someone ought to buy the VC++ team a beer.

Note   Make sure that you have the TOOLS/OPTIONS/DEBUG “Just in Time” debugging and “OLE RPC debugging” turned on.

OUT_OF_PROC Servers

The other way that you can handle testing your out-of-process server is simply to start it with the /EMBEDDING switch. This is the signal that OLE uses when starting an .EXE to let it know that it should be in OLE mode.

Appendix C. Adding OLE Automation

If you want to add OLE Automation support, there are numerous sources of VC++ information about creating OLE Automation with MFC, but here is a quick overview of how to use ClassWizard to add an OLE Automation interface to your new object.

Adding OLE Automation Methods and Properties

Now we go into ClassWizard and use it to create our OLE Automation interface. This is where you realize the advantages of using the MFC libraries.

  1. Select Project\ClassWizard.

  2. Click the OLE Automation booktab.

  3. Click the Add Method button.

  4. Type GetSpeciesName in the external name field.

  5. Select Return type of BSTR (that's a string for you and me).

  6. Click OK.

  7. Click Add Property.

  8. Type PetName as the external name, and click Get/Set Methods. Notice that it has automatically created two method names, GetPetName and SetPetName.

  9. Set the data type to BSTR as well, and click OK.

Defining OLE Automation Methods and Properties

Now we define the functions that ClassWizard has defined in our ODL file.

  1. Double Click GetSpeciesName(). Notice that it took you to an implementation of a new C++ method on our class. This method actually gets called whenever someone makes an OLE Automation request to GetSpeciesName. All details of OLE Automation are hidden and taken care of; you just need to define the function.

  2. Delete the line that says CString s. We don't want to use this string, but we want to use our class data m_SpeciesName.

  3. Change the s.AllocSysString to m_SpeciesName.AllocSysString(). This says we want to return our internal class string in a global manner to OLE Automation.

  4. Now go back into ClassWizard and double-click the GetPetName property.

  5. Define it appropriately as well. You should end up with a chunk of code that looks like this:
    BSTR CLabrador::GetSpeciesName() 
    {
        // TODO: Add your dispatch handler code here.
        return m_SpeciesName.AllocSysString();
    }
    
    BSTR CLabrador::GetPetName() 
    {
        // TODO: Add your property handler here.
        return m_PetName.AllocSysString();
    }
    
    void CLabrador::SetPetName(LPCTSTR lpszNewValue) 
    {
        // TODO: Add your property handler here.
        m_PetName = lpszNewValue;
    }
    

Using OLE Automation from C++

MFC also has outstanding support for being an OLE Automation client. ClassWizard knows how to take a TypeLib (.TLB) file and generate a C++ class that completely handles all of the details of doing IDispatch, with all the methods having real type-safe checking information on them.

Creating an interface class from TypeLib

  1. Create a test application using AppWizard.

  2. Go into ClassWizard for this application, and click the OLE Automation book tab.

  3. Click the Import OLE Automation Type Lib button.

  4. Select the ILabrador name, and tell it to create two classes called ILabrador.h and ILabrador.cpp.

  5. Add the generated .h and .cpp files to your project.

This class is now the a wrapper around the IDispatch interface.

Using DispatchClass

To use this class, find an appropriate place, and take the following steps:

  1. Declare an ILabrador function.

  2. #include <initguid.h>.

  3. #include "\Source\Classes\ClassIDs\LabradorID.h".

  4. Call CreateDispatch(), passing the CLSID_Labrador.

  5. Call the methods. Ta-da! Your code should look like the following:
        ILabrador iLab;
        CString Temp;
        
        if (iLab.CreateDispatch(CLSID_Labrador))
        {
            Temp = iLab.GetSpeciesName();
            TRACE("GetSpeciesName() = %s", (LPCTSTR)Temp);
            TRACE("IsAlive() is %d", iLab.GetIsAlive());
    
            Temp = iLab.GetPetName();
            TRACE("GetPetName() = %s", (LPCTSTR)Temp);
    
            Temp = "RILEY";
            iLab.SetPetName(Temp);
    
            Temp = iLab.GetPetName();
            TRACE("GetPetName() = %s", (LPCTSTR)Temp);
    
            iLab.ReleaseDispatch();
        }
    

For complete documentation on how this works, see the MFC COleDispatchDriver Class documentation.

Appendix D. Reference Materials

Development Library

"Technical Note 33: DLL Version of MFC" (MSDN Library Archive, Technical Articles, Visual C++ 1.5 [16-bit] Articles, MFC 2.5 Technical Notes). This basically describes the differences between AfxDLLs and standalone DLLs. This is the information that led to the weird AppWizard workaround in the beginning.

"Technical Note 38: MFC/OLE IUnknown Implementation" (MSDN Library Archive, Technical Articles, Visual C++ 1.5 [16-bit] Articles, MFC 2.5 Technical Notes). If you have any nagging questions about how the heck all of this works, you absolutely need to read this article. It describes what all of these macros are, what the alternatives are, and so forth.

Inside OLE 2 by Kraig Brockschmidt (MSDN Library, Books). A good overview of all the OLE technologies, from top to bottom.

The RPC documentation contains a complete and comprehensive description of the IDL language that is used to describe your interface to RPC.

The Win32 SDK [Ed. note: the Win32 SDK is now called the Platform SDK] describes the process of using the MIDL compiler (see Platform, SDK, and DDK Documentation).

There is also a series of articles by Nigel Thompson that should provide a lot of information as well.

MFC Class Definitions

Again, if you are curious about how all of this works:

Appendix E: INPROC Only Interface Definition Header Files

If you know that you only have an INPROC server, you don't have to do all of that marshalling rigamarole. Instead, you can use MFC-provided macros to define the interface as simply an IFoo.h file. But you should note that if you do end up going to MIDL, you are going to have to throw these files away and start over with an .IDL file.

Each IXXXX interface needs a header file that other applications can include, that tells them what a particular interface looks like. This interface header is created by using MFC macros that hide all the gory details of class definitions, virtual function tables, and so forth.

Here's what my interface definitions of IMammal and IDog look like. The main macros being used here are pretty straightforward. For more information, refer to the MFC documentation, in particular Technical Note 38 in the MSDN Library Archive.

#ifndef _IMammal_
#define _IMammal_

#undef INTERFACE
#define INTERFACE IMammal

// {B312D550-EF8E-11cd-8A01-EEEEEEEEEEEE}
DEFINE_GUID(IID_IMammal, 
0xb312d550, 0xef8e, 0x11cd, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE);

DECLARE_INTERFACE_(IMammal, IUnknown)
{
    // Definition of IUnknown.
    STDMETHOD(QueryInterface)(THIS_ REFIID, LPVOID *) PURE;
    STDMETHOD_(ULONG,AddRef)(THIS) PURE;
    STDMETHOD_(ULONG,Release)(THIS) PURE;

    // Definition of IMammal.
    STDMETHOD(GetSpeciesName)(THIS_ LPSTR) PURE;
    STDMETHOD_(BOOL, IsAlive)(THIS) PURE;
};
#endif
  1. Create a new interface definition file, Interfaces\IMammal\IMammal.h.

  2. Run utility GUIDGEN, select the DEFINE_GUID format, and paste it into the appropriate section of the file. This is the statistically unique identifier for this interface for the world! Do not use the one I have defined here, it's bogus!

  3. Change the <<name>> to the name of your Interface, IMammal.

  4. Create a line that says, DECLARE_INTERFACE_(IMammal, IUnknown). This creates a standard definition of virtual base class called IMammal, derived from the already-defined IUnknown.

  5. Every interface requires the three methods (QueryInterface, AddRef and Release) defined in IUnknown, so copy those three lines verbatim from above.

  6. Now add any methods you want your interface to support. There are two macros that are used to do this: One is STDMETHOD(MethodName)(THIS_ Parm1, Parm2) PURE, the other is STDMETHOD_(ReturnType, MethodName)(THIS Parm1, Parm2). The difference between the two is that STDMETHOD defaults to a return value of HRESULT, while STDMETHOD_ allows you to define the return value. Also note that inasmuch as an interface is language-independent, you need to have a THIS parameter passed to all methods for an interface. Use THIS if there are no parameters following, and use THIS_ if there are parameters following. These macros hide all language details from the user.
    #ifndef _IDog_
    #define _IDog_
    
    #undef INTERFACE
    #define INTERFACE IDog
    
    // {B312D550-EF8E-11cd-8A01-FFFFFFFFFFFF}
    DEFINE_GUID(IID_IDog, 
    0xb312d550, 0xef8e, 0x11cd, 0x8a, 0x1, 0x0, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF);
    
    DECLARE_INTERFACE_(IDog, IUnknown)
    {
        // Definition of IUnknown.
        STDMETHOD(QueryInterface)(THIS_ REFIID, LPVOID *) PURE;
        STDMETHOD_(ULONG,AddRef)(THIS) PURE;
        STDMETHOD_(ULONG,Release)(THIS) PURE;
    
        // Definition of IDog.
        STDMETHOD(GetPetName)(THIS_ LPSTR) PURE;
        STDMETHOD_(BOOL, IsBarking)(THIS) PURE;
    };
    #endif