IDispatch
interfaces
on the fly? They don't have to be generated from the IDL or typelib
files like ATL requires you to do.
The following classes allow you to generate IDispatch
name tables
dynamically instead of loading method information from the typelib.
There are two classes: the IDispDynImpl allows you to turn
any C++ object into an automation COM object - without the
need of the ATL framework!
The other class presented here achieves the same goal, but this class integrates
into the ATL architecture.
The classes allow you to create one IDispatch
interface
for one instance of your C++ class, while handing out a completely different
IDispatch
interface for other clients.
You may have heard of IDispatchEx
and think that this code
is violating COM rules. IDispatch
interfaces are not allowed
to change at runtime - but it is perfectly acceptable to generate a new
(and even a different) name table for every instance of an object!
The Dispatch Map
Both classes use a macro map to describe the name table. You use
these macros to generate the methods and properties in your
IDispatch
object.
The MFC library has something called Dispatch Maps.
It is absent in ATL, so let's define one...
Here is an example of a dispatch map:
BEGIN_DISPATCH_MAP(CMyObject)
DISP_METHOD(FooManyArgs, VT_EMPTY, 2, VTS_DISPATCH VTS_I4)
DISP_METHOD0(FooNoArgs, VT_EMPTY)
DISP_METHOD1(FooOneArg, VT_I2, VT_BOOL)
END_DISPATCH_MAP()
The DISP_METHOD
macro defines a method with return code
and arguments. The first parameter is the name of the C++ function and
the automation name.
The 2nd parameter is the type of the return code.
This parameter uses the VARIANT type identifiers (or VARTYPE
).
The identifier VT_EMPTY
means "no value".
The 3rd parameter in DISP_METHOD
is the number of arguments the function
takes. And the last argument is a space-separated list of one or
more constants specifying the function’s parameter list.
The
DISP_METHOD0
and DISP_METHOD1
are short-hand
macros for defining methods with one or no arguments.
The implemented C++ functions should look like this...
void __stdcall FooManyArgs(IDispatch *pDisp, LONG i);
void __stdcall FooNoArgs();
SHORT __stdcall FooOneArg(VARIANT_BOOL b);
Properties are supported using the following macros:
BEGIN_DISPATCH_MAP(CMyObject)
DISP_PROP(PropReadWrite, VT_I2)
DISP_PROPGET(PropReadOnly, VT_BSTR)
DISP_PROPPUT(PropAssignOnly, VT_I4)
END_DISPATCH_MAP()
where DISP_PROP
defines both a read and write C++ function for accessing the
property, here through the C++ functions...
SHORT __stdcall get_PropReadWrite();
void __stdcall put_PropReadWrite(SHORT value);
BSTR __stdcall get_PropReadOnly();
void __stdcall put_PropAssignOnly(LONG value);
IDispDynImpl
The IDispDynImpl class allows you to take any C++ object and turn it into an
IDispatch
object. No ATL initialisation code is needed.
The class is for use when you know exactly
who is calling you and how. The class implements IUnknown
and
the basic IDispatch
interfaces using stubs. There is no
real reference counting, so you had better watch out how your object is used.
I find this class particular useful when I need to pass automation objects e.g. between a web browser and the hosting application. There is no real need to create class factories, typelibs and those sorts of things in that situation. VTable (early) binding is not useful because scripting code cannot use them anyhow.
CComDynTypeInfoHolder
If you dive into the architecture of ATL, you can fish out a
class called CComTypeInfoHolder
, which manages the type
information of the ATL IDispatch
implementation.
This class is templated on superclasses like IDispatchImpl
,
so clearly the intention has always been that ATL should be able to handle
other means of instantiating the IDispatch
interface than
using typelibs.
The CComDynTypeInfoHolder class does exactly that. It uses the
above macro map to create the IDispatch
name table.
It also uses an ancient OLE Automation method called
::CreateDispTypeInfo()
to generate Type Info for the macro map.
This info can then be used by the other ATL classes. In addition, it implements
the IDispatch
code needed to be used in your object.
How useful is this
You may argue that the dispatch map shown here doesn't really change much. You still
have to hardcode a list of your methods, just like in the IDL file.
This is not entirely true. Just like all the standard ATL maps, it allows
you to override the default behaviour. The internal function
_GetDispMap()
is created by the BEGIN_DISPATCH_MAP
macro.
You can provide your own function instead -
allowing truly dynamic creation of the name table.
Performance... how about performance?
Let's not talk speed here. If performance were a concern, you wouldn't be
using IDispatch
in the first place. Actually, because this is
a very custom implementation, it is slightly faster than the standard ATL implementation
of IDispatch!
Is validation and safety the same? The class does standard checking for invalid arguments and return values.
How to use them
Here is a simple sample of using IDispDynImpl.
Note how the methods must have stdcall
calling convention.
class CMyObject :
public IDispDynImpl<CMyObject>
{
BEGIN_DISPATCH_MAP(CMyObject)
DISP_METHOD1(Foo, VT_BOOL, VT_I2)
END_DISPATCH_MAP()
VARIANT_BOOL __stdcall Foo(SHORT in)
{
return VARIANT_TRUE;
}
};
class CMyObject :
public IDispDynImpl<CMyObject>
{
BEGIN_DISPATCH_MAP(CMyObject)
DISP_METHOD1(Foo, VT_BOOL, VT_I2)
END_DISPATCH_MAP()
VARIANT_BOOL __stdcall Foo(SHORT in)
{
return VARIANT_TRUE;
}
};
The CComDynTypeInfoHolder class is used like this...
class ATL_NO_VTABLE CTestObj :
public CComObjectRootEx<CComSingleThreadModel>,
public CComCoClass< CTestObj, &CLSID_TestObj >,
public IDispatchImpl< IDispatch, NULL, NULL, 1, 0,
CComDynTypeInfoHolder<CTestObj> >
{
public:
DECLARE_REGISTRY_RESOURCEID(IDR_TESTOBJ)
BEGIN_COM_MAP(CTestObj)
COM_INTERFACE_ENTRY(IDispatch)
END_COM_MAP()
BEGIN_DISPATCH_MAP(CTestObj)
DISP_METHOD(Foo, VT_I2, 2, VTS_BSTR VTS_BSTR)
END_DISPATCH_MAP()
SHORT __stdcall Foo(BSTR bstr1, BSTR bstr2)
{
ATLTRACE("Foo %ls %ls\n", bstr1, bstr2);
return 0;
}
};
This is a stripped ATL object generated with the ATL Wizard.
With both classes there seems to be a few quirks. First, I haven't figured out how to add COM exceptions and error handling, not without introducing the penalty of C++ exceptions into the project anyway. Also Named Arguments are not supported.
Source Code Dependencies
Microsoft ATL LibraryUseful Links
Oops, Valery Pryamikov did a similar classChris Sells also has a few ATL IDispatch modifications
Download Files
![]() | Source Code (5 Kb) |