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

Dynamic Creation of IDispatch interfaces using simple classes

0.00/5 (No votes)
3 Sep 2001 1  
Create dynamic objects that you can pass using IDispatch interfaces

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 IDs/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:

  • dDataCDynamicMetho
  • CDynamicInterface
  • 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 repetitively 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, it's 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) )
{
// Its a property Alright...
// But Which one?
} else if(wFlags & DISPATCH_METHOD)
> {
// Its a method Call..
// which one?
}

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 )
{
	/*
		This is where all the work really takes place...
		Since we know most of the methods, functions etc....
	*/

         // call a function
	if(dispidMember == MY_INSTANCE_FUNCTION)
		return i_MyFunction(wFlags,pparams,pvarResult);

	if(dispidMember == INSTANCE_GET_X || 
	   dispidMember ==     INSTANCE_SET_X)// X
		return i_HandleGetSetX(wFlags,pparams,pvarResult);

	if(dispidMember == INSTANCE_GET_Y || 
	   dispidMember ==   INSTANCE_SET_Y)// 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;
}

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;// our array of parameters (which we should know)

	// do something interesting
	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!".

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.

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