Converting Classes in Executables to Automation Objects

Manmeet Dhody

If you want to build a COM-aware application from scratch-say, a container or a server or an Automation server-you'll get lots of help from AppWizard. But what if you've finished your application, and it suddenly occurs to you that you'd like to expose some of your functionality to other programs? Now what? Manmeet will show you.

Would you like your executables to be Automation servers-that is, to expose automation objects that can be used from scripting clients like Visual Basic? They can, if you use MFC. MFC supports Automation with the CCmdTarget class, which implements IUnknown and IDispatch. As all window classes inherit from CWnd (which in turn inherits from CCmdTarget), any of these classes can be easily exposed to the world.

The primary requirement in such a scenario is implementing a class factory for these objects. Let's say you have an SDI application with a custom dialog box that you'd like to convert into a COM creatable Automation object. Start by adding these macros to the class definition of the dialog box:

DECLARE_DYNCREATE(CServerDialog)
DECLARE_OLECREATE(CServerDialog)

DECLARE_DYNCREATE enables the CServerDialog to be dynamically created at runtime. The macro DECLARE_OLECREATE declares a COleObjectFactory (MFC's support for class factories) object and a guid for the CServerDialog class. Next, add the following code to the implementation file for the dialog box:

IMPLEMENT_DYNCREATE(CServerDialog, CDialog)
IMPLEMENT_OLECREATE(CServerDialog, _
      "SampleServer.Dialog", 0xa6fd42c6, _
      0x481e, 0x11d2, 0x8c, 0x47, 0x0, 0x1, _
      0xfa, 0x37, 0x40, 0xae)

IMPLEMENT_OLECREATE constructs the class factory object and initializes the guid, which serves as the class id for our CServerDialog. (Replace the hex numbers in this line with those that match your guid.) Here are the arguments that the COleObjectFactory constructor needs:

COleObjectFactory::COleObjectFactory(REFCLSID clsid,
          CRuntimeClass* pRuntimeClass, 
          BOOL bMultiInstance, 
          LPCTSTR lpszProgID)

IMPLEMENT_OLECREATE passes in the clsid of the CServerDialog, uses the RUNTIME_CLASS macro to get the runtime class of CServerDialog, passes FALSE so that multiple instances of the object can't be created by one instance of the server, and builds a ProgID, the identifier that will be used by scripting languages to create an instance of CServerDialog.

Add this code to the InitInstance method of your application class:

   // Initialize OLE libraries.
   if (!AfxOleInit())
   {
      AfxMessageBox("OLE Initialization failed!");
      return FALSE;
   }

   COleObjectFactory::UpdateRegistryAll();
   COleObjectFactory::RegisterAll();

This initializes OLE, registers the application's class factories with the Registry, and registers the class factories with OLE's system DLLs.

You'll need a custom ODL file to specify the methods and events of the CServerDialog class. The ODL file for CServerDialog is shown in Listing 1 (see page 15).

I picked up an existing ODL file, modified it to represent CServerDialog (specified the correct Class id and dispinterface id), and then used ClassWizard to add a sample method called PopupMessageBox. The method PopupMessageBox takes a string input parameter and displays the string in a message box.

That's all that needs to be done to convert CServerDialog to a COM createable Automation object. I wrote a small VB program to create an instance of the CServerDialog object:

Dim x As Object
Set x = CreateObject("SampleServer.Dialog")
x.popupmessagebox "Sample message"
Set x = Nothing

If you need a custom class factory, it's not much harder to do. Just declare a class inheriting from COleObjectFactory, provide a constructor that will take the class id of the object (CServerDialog class, in this case), a pointer to the runtime class, and a program identifier. Then pass these along to the base COleObjectFactory:

COleServerCF::COleServerCF() : 
COleObjectFactory(CLSID_OleServer,
                  RUNTIME_CLASS(CServerDialog), 
                  FALSE,
                  _T("SampleServer.Dialog"))
{
}

Override OnCreateObject() (the virtual function of CCmdTarget used to create a new object) to custom create the object. This function provides a lot of control as to how we wish to create our object. For example, if you want only one instance of the object to be created, create a single instance and return the same pointer every time OnCreateObject() is called. I create a new instance each time:

CCmdTarget* COleServerCF::OnCreateObject()
{
   // create an instance of the CServerDialog class
    CServerDialog* pOleServer = new CServerDialog;

   // add ref
   pOleServer->ExternalAddRef();
   return pOleServer;
}

The sample code available in the Subscriber Downloads at www.pinpub.com/vcd includes versions of CServerDialog with and without a custom class factory, though both samples work the same, since the custom class factory I provide has the same behavior as the default. The tiny VB sample is also included. s

   CONVERT.ZIP at www.pinpub.com/vcd 

Manmeet Dhody is a COM/OLE consultant with Countrywide Home Loans in Los Angeles. Most of his COM work uses MFC as a framework. He writes on COM and MFC topics. Manmeet_Dhody@Countrywide.com.