Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles
(untagged)

Buried treasure in MFC: COleDispatchDriver

0.00/5 (No votes)
8 Nov 2001 1  
Introducing a simple and dynamic com dispatch driver class

Introduction

If you have used the VC++ ClassWizard to import a class from a type library, you will be familiar with the COleDispatchDriver class. This class is typically used as the base class of machine generated wrapper classes for COM objects. Actually, I have never seen it being used for anything else except as the base of wrapper classes (until now, of course). Why do I bother to write an article about COleDispatchDriver? The reason is that I think this class has some unreleased power that will make a C++ developer's life a lot easier.

Have you noticed that VB developers can create almost any COM object and call its methods at run-time, and VC++ developers have to use compile-time generated wrapper classes? Anyone understands COM will say that we can do the same thing in C++, but we don't want to bother with writing so much low-level com code to accomplish this. My goal is to derive a utility class from COleDispatchDriver so that we can write code like the following in VC++ without generating any wrapper class at compile-time:

// declare a utility object

MyUtilityClass utility;
// create a com object with prog id "MyComObj1"

utility.CreateObject("MyComObj1");
// call the method "GetData" (no argument) in the com object

CString strData = utility.CallMethod("GetData");
// create another com object with prog id "MyComObj2"

utility.CreateObject("MyComOb2");
// call the method "GetMsgCount" with one argument

long nUserID = 3;
long nCount = utitilty.CallMethod("GetMsgCount", nUserID);

As you can see later, I came pretty close to achieving this goal. If someone flames me for damaging a developer's brain with VB-style code, then my job is done :-). Please note that this is not meant to replace other existing methods of creating and using a com object.

I would like to thank Chris Losinger and Aaron Schaefer for answering my "anonymous" programming questions.

XYDispDriver

XYDispDriver is the name of my utility class. The source code project will build a library XYDispDriver.dll which exports the XYDispDriver class. As usual, you may use and modify the source code anyway you wish, I would appreciate some acknowledgment for my contribution (if possible). Here is the header file of my class.
#include <afxdisp.h>


class XYDispDriver: public COleDispatchDriver
{
    // an internal array to store type information

    CPtrArray m_arrayDispInfo;
    // an internal function to get index to stored type information

    int FindDispInfo(const CString strName, const WORD wFlag = DISPATCH_METHOD);
    // clear internal storage

    void Clear();
    // these 3 functions are here to hide functions with the same names in the base class

    BOOL CreateDispatch(REFCLSID clsid, COleException* pError = NULL) { return FALSE; }
    void AttachDispatch(LPDISPATCH lpDispatch, BOOL bAutoRelease = TRUE ) {}
    LPDISPATCH DetachDispatch( ) { return NULL; }
public:
    XYDispDriver();
    ~XYDispDriver();
    // create a com object with given prog id, this function

    // hide the function with the same name in the base class

    BOOL CreateDispatch(LPCTSTR lpszProgID);
    // get the type of a property

    VARTYPE GetPropertyType(const CString strPropertyName);
    // get the property value

    void* GetProperty(const CString strPropertyName);
    // set the property value

    void SetProperty(const CString strPropertyName, ...);
    // get return type of a method

    VARTYPE GetReturnType(const CString strMethodName);
    // get number of parameters in a method

    int GetParamCount(const CString strMethodName);
    // get the type of a parameter in a method

    VARTYPE GetParamType(const CString strMethodName, const int nParamIndex);
    // invoke a method

    void* InvokeMethod(const CString strMethodName, ...);
};

To use the XYDispDriver class, you first need to declare an instance of this class and call the CreateDispatch member function passing the prog id of a com object. This method not only creates an IDispatch pointer, it also retrieves and stores all the necessary type information using the ITypeInfo interface. Then you can call other member functions to either invoke a method in the com object or get/set a property value.

Please note that GetProperty and InvokeMethod both return a void pointer. To access the return data, you need to cast the pointer to the appropriate data type and then dereference it. Also, the return data will be over-ridden by the next call to the same method. I know this is cumbersome and un-OO like, but who said the world is perfect? :-)

Please also note that methods of XYDispDriver may throw exceptions of type COleException, an MFC class.

Sample code

Here is a sample program that creates an XYMailClient object described in my other article and sends/receives e-mail messages. The code can be written and executed without using any type library at compile-time.
/*************** Test.cpp ***********************/

#include "XYDispDriver.h&quot

void main()
{
    XYDispDriver disp;
    try
    {
       if(disp.CreateDispatch("XYMailClient.1"))
        {
           if(*(BOOL*)disp.InvokeMethod("InitSession","MyProfile","")==FALSE)
            {
                printf("Failed to call InitSession\n");
                return;
            }
           if(!*(BOOL*)disp.InvokeMethod("SendMsg",""mailto:XiangYangL@aol.com">XiangYangL@aol.com",
                                         "","","Hello","This is a test",""))
            {
                printf("Failed to call SendMsg\n");
            }
           long nCount = *(long*)disp.InvokeMethod("FetchMsg");
            printf("Total messages = %d\n",nCount);
           for(int i=0;i<nCount;i++)
            {
                printf("Message %d: %s\n",i,*(CString*)disp.InvokeMethod("GetMsg",i));
            }
            disp.InvokeMethod("DeleteMsg",0);
            disp.InvokeMethod("EndSession");
        }
       else
        {
            printf("Failed to create com obj\n");
        }
    }
    catch(COleException* e)
    {
        printf("Ole exception\n");
       TCHAR buf[251];
        e->GetErrorMessage(buf,250);
        printf("%s\n",buf);
        e->Delete();
    }
    printf("Done\n");
}
/**************** End of Test.cpp ****************/
 

Using XYDispDriver in your own program

XYDispDriver is implemented with VC++ 5.0 and MFC.

To use XYDispDriver, you need only to add the source files to your project or use it as a dll. You should always use the CString class for string data, although you can pass literal strings as input parameters. I have not done extensive testing with various data types except for strings and numbers. Please let me know if you found or fixed a bug.

Thank you for reading this article. Please visit my home page for my other articles and programs.

License

This article has no explicit license attached to it but may contain usage terms in the article text or the download files themselves. If in doubt please contact the author via the discussion board below.

A list of licenses authors might use can be found here