Introduction
This concept may be hard to grasp so I will tell you what my original intent for these
classes were, and that should help you understand how you can use them.
I was writing a piece of software exposing certain objects to a VBscript host. This is easy when
you write your objects in ATL or create an ActiveX object - you just pass in the
IDispatch to the
scripting host and it takes care of resolving method and property ID's/names so your object can be
naturally scripted.
So after getting the scripting host working I decided that it would be really nice if I could write
plugins that could somehow be exposed to the script host as well. I did not want to limit the plugins
to a specific number of methods or properties. Essentially what I wanted to do was create an ActiveX
control in real-time.
I did not know if this was possible,
so the search began, the coding began, the headaches began. The end result of
all this work is a set of classes that can be used to create
IDispatch
interfaces 'on the fly'. A friend of mine made the comment "why bother" well if you see any use
for this then paste it on the thread please.
I learned a lot more about COM than I really wanted to throughout this exercise, but in the end
my idea actually worked. And the information I picked up did help down the road.
The interesting thing about this is that you don't have to concern yourself with
GUIDs or other
things to create and use these objects. They don't have to be registered, they only have to exist.
In my original code I would query a DLL for what props and methods it wanted, give it a name, and then
added it as an object to the script host. It made writing scriptable plugins easy, and allowed the
plugins to have all the flexibility of exposing their own unique interfaces to the
VBscript without
having to worry about COM.
The classes involved are:
CDynamicMethodData<br>
CDynamicInterface<br>
CDynamicObject
The CDynamicMethodData object is used to set up our desired properties/methods.
It has a method called
CreateNewMethod(CString sMethodName,unsigned short usMethodType).
sMethodName - the name of the method or property
usMethodType - what kind of call is this (method)
the valid method types are :
DISPATCH_METHOD (call a function),
DISPATCH_PROPERTYGET (self explanatory)
DISPATCH_PROPERTYPUT (self explanatory)
The CDynamicInterface encapsulates the CDynamicMethodData, and provides the mechanisms for actually
generating the realtime COM interfaces.
void CDynamicInterface::AddMethod(CString sMethod,CString sParms)
Since I wanted to make adding methods and properties easier I decided that sending
a string of parameters for a method made more sense than repetively calling the AddMethod function. So to
set up a method Called "SetPoint" with two parameters we would do this:
myDynamicInterface.AddMethod("SetPoint","VT_INT,VT_INT");
To add a property we simply call:
myDynamicInterface.AddProperty("intprop",VT_INT);
Make note that we use VARIANTS for everything. Also know that when you add a property you
are actually adding 2 methods (1 for put and 1 for get). If you call the CDynamicMethodData directly to
add props then you can specify if you want just a DISPATCH_PROPERTYPUT or just a
DISPATCH_PROPERTYGET. In
the case of my dynamic interface I made it simple and add both when you create a property.
Whenever a method or property is added to an interface you need realize that it is assigned an ID (0 based).
This makes it important for your eventual implementation class to know the order that you added properties
and methods so that you can deal with them appropriately. (will become more clear in a minute)
So now we get to the CDynamicObject which is the class you will inherit from to make your dynamic
objects a reality. It is based directly on IDispatch and will handle any incoming property query or
interface call. In my implementation I decided to make the CDynamicMethodData a pointer instead of a
an actual object. The decision made sense at the time, if you create multiple objects of the same type, why
would you want to create copies of an already existing interface? This could also be achieved using a static
member variable, which you can choose to implement if you really want to.
So by now we may have added some props and methods, so how do we deal with incoming request calls?
In our implementation class (based on CDynamicObject) we need to implement the function:
HRESULT vDispInvoke(
void FAR* _this,
ITypeInfo FAR* ptinfo,
DISPID dispidMember,
unsigned short wFlags,
DISPPARAMS FAR* pparams,
VARIANT FAR* pvarResult,
EXCEPINFO* pexcepinfo,
unsigned int FAR* puArgErr );
Don't worry its a lot less scary than it looks. The key is the dispidMember variable. Remember when I told
you to keep track of the order in which you added the properties and methods? Well if you did not then you are
out of luck figuring out what the heck you should do. In my implementation I used #define to set up
props and methods for my dynamic objects. First we figure out what kind of method the caller wants:
if( (wFlags & DISPATCH_PROPERTYGET) || (wFlags & DISPATCH_PROPERTYPUT) )
{
} else if(wFlags & DISPATCH_METHOD)
> {
}
The answer of "which one?" is contained within the dispidMember variable. If (when you were initializing the interface)
you added "SetPoint" first and the dispidMember = 0, then
"SetPoint" is the method. Remember
that properties have two IDs (one for get and one for set).
So here's an example of what a fully implemented vDispInvoke might look like:
HRESULT CMyDynamicObject::vDispInvoke(
void FAR* _this,
ITypeInfo FAR* ptinfo,
DISPID dispidMember,
unsigned short wFlags,
DISPPARAMS FAR* pparams,
VARIANT FAR* pvarResult,
EXCEPINFO* pexcepinfo,
unsigned int FAR* puArgErr )
{
if(dispidMember == MY_INSTANCE_FUNCTION)
return i_MyFunction(wFlags,pparams,pvarResult);
if(dispidMember == INSTANCE_GET_X ||
dispidMember == INSTANCE_SET_X) return i_HandleGetSetX(wFlags,pparams,pvarResult);
if(dispidMember == INSTANCE_GET_Y ||
dispidMember == INSTANCE_SET_Y) return i_HandleGetSetY(wFlags,pparams,pvarResult);
return S_OK;
}
I chose to implement one function for getting/setting a property. It looks like this:
HRESULT CMyDynamicObject::i_HandleGetSetX(unsigned short wFlags,DISPPARAMS FAR* pparams,VARIANT FAR* pvarResult)
{
if(wFlags & DISPATCH_PROPERTYGET)
{
pvarResult->vt = VT_INT;
pvarResult->intVal = m_iX;
}
else
m_iX=pparams->rgvarg->intVal;
return S_OK;
}
<br>
Receiving the method call is almost the same.
HRESULT CMyDynamicObject::i_MyFunction(unsigned short wFlags, DISPPARAMS FAR* pparams,
VARIANT FAR* pvarResult)
{
VARIANT* pVars = pparams->rgvarg;
return S_OK;
}
The end result of all of this is that you now have an object that can be used by a scripting host or
anything that accepts an IDispatch interface. The caveat is that your interfaces are only known at runtime,
which was the point right? I do not have time at the moment, but will be updating this article at a later date
with a working automation project that shows an example of these classes being used.
You are free to use the code as you wish, just drop me an email if you use it in
something interesting, extend it, or just want to say "hey!".