I have yet to post code on my blog, so now is the time.

I have various function nuggets that have followed me around like a lost sick puppy over the years and this is one of my most recent.  Since I work with a UI architecture that is very dynamic in nature and extremely plugin-oriented, I need to have a set of functions to explore the type information of objects and allow me to dynamically call methods and properties. Here is an example of such a function.  Let's say you had some XML that looked something like:


<Object type='Object.ProgID' prop1='XYZ' prop2='true' prop3='43' prop4='3.14159'/>

This is just an example but just by looking you can see that it defines a COM object (with the progID in the type attribute) with several different properties of varying data types (string, boolean, integer, float).  The first thing you need to do is obviously parse the relavent data out.  For instance, the parser finds a new Object tag, grabs the type attribute and does a CoCreateInstance to create the COM object (I'll leave that as an exercise for the reader...unless someone really doesn't know how to do it).  The parser then enumerates through each attribute grabbing 2 strings, the attribute name and the attribute value.  The parser doesn't need to worry about doing any type coercion (the method that follows does it for you).

So, at this point a COM object has been created and the parser is enumerating each attribute which corresponds to an actual property name in the COM object.  Here's the method that given a instance of a COM object, the property name (string) and the property value (string), calls the correct property setter on the object (criticism is expected but keep in mind the code is still rough since I'm still in implementation phase of the UI engine):

HRESULT CSomeClass::SetObjectProperty( IDispatch* pObject, BSTR propertyName, BSTR propertyValue )
{
    USES_CONVERSION;
    HRESULT hr = S_OK;
    DISPID dispID = 0;
    TYPEATTR* typeAttr;

    UINT puArgError;
   
   
// get ITypeInfo from object
    ITypeInfo* pTypeInfo;
    hr = pObject->GetTypeInfo( 0, 0, &pTypeInfo );

   
if
( FAILED(hr) )
    {
        LogMsg( 1, (1, "ERROR:Unable to get type info for pipeline object. Ensure that COM_MAP contains: COM_INTERFACE_ENTRY2( IDispatch, IMainInterfaceName )"));
       
return
FALSE;
    }

   
// get type attributes (# functions e.g.)
    pTypeInfo->GetTypeAttr(&typeAttr);

   
for
( WORD iFunction = 0; iFunction < typeAttr->cFuncs; iFunction++ )
    {
        FUNCDESC* funcDesc;
        CComBSTR methodName;   
        CComVariant vPropValue;

       
// get function description info
        hr = pTypeInfo->GetFuncDesc( iFunction, &funcDesc );

       
// make sure its a propput function
        if
( funcDesc->invkind != INVOKE_PROPERTYPUT && funcDesc->invkind != INVOKE_PROPERTYPUTREF )
        {
            pTypeInfo->ReleaseFuncDesc( funcDesc );
           
continue
;
        }

       
// get method name
        hr = pTypeInfo->GetDocumentation(funcDesc->memid, &methodName, 0, 0, 0);
       
if
( FAILED(hr) )
        {
            pTypeInfo->ReleaseFuncDesc( funcDesc );
           
continue
;
        }

       
// check to make sure we have the right property
        if
( CString(methodName).CompareNoCase(CString(propertyName )) != 0 )
        {
           
continue
;
        }
       
       
// the dispid
        dispID = funcDesc->memid;

       
// at this point we found the correct property function to call
       
// now we need to build the VARIANT arg
        vPropValue = propertyValue;
       
        vPropValue.ChangeType( funcDesc->lprgelemdescParam[0].tdesc.vt );

       
// build dispparams
       
// NOTE: PRB: Error 0x80020004 When Setting a Property (http://support.microsoft.com/support/kb/articles/q175/6/18.asp)
        DISPPARAMS dispParams;
        DISPID dispidNamed = DISPID_PROPERTYPUT;
        dispParams.rgvarg = NULL;

        dispParams.rgdispidNamedArgs = &dispidNamed;
        dispParams.rgvarg = &vPropValue;

        dispParams.cArgs = 1;
        dispParams.cNamedArgs = 1;

       
// invoke function (which is the propput method)
        HRESULT hr = pTypeInfo->Invoke( pObject, dispID, funcDesc->invkind, &dispParams, NULL, NULL, &puArgError );

       
if
( FAILED(hr) )
        {
           
// error setting property
            LogMsg( 1, (1, "Cannot set property \"%s\". hr=0x%x", OLE2T(methodName), hr ));
            pTypeInfo->ReleaseFuncDesc(funcDesc);
        }
       
else
        {
            pTypeInfo->ReleaseFuncDesc(funcDesc);
        }
    }

    pTypeInfo->Release();

   
return
TRUE;
}

I commented the different sections of the method so it should all be pretty clear.  I have several variations that exist but essentially do the same thing. For instance, one variation uses a hashtable of property name -> property values so I don't have to repeatedly call this method over and over again for each property.

There you have it.  My first code contribution.  I actually have another method that is way cooler than this one.  It basically accepts a COM object and a list of property name/property value pairs and calls a method.  Those name/value pairs are the parameters to the COM object's method.  The cool thing is that the list of pairs can be a) all strings and the method will change the types correctly and b) in any order (i.e. the name/value pairs do not have to match the order of the parameter list for the method. The dynamic method call method will order them and convert types for you).

Thoughts?

posted on Tuesday, May 25, 2004 9:23 AM
Filed Under [ C++ General Programming ]

Comments

Gravatar
# re: Dynamically Setting COM Object Properties
posted by Kula
on 6/7/2004 2:06 AM
great work!

I'm having trouble with how to use GetTypeInfo...and this article saves me lots of time.

By the way, I have another question:
basiclly I'm creating a control at run time by type the ProgId, and now I need to list all the proporties and methods that control has.

I use CWnd.CreateControl, then I have no idea how to get the IDispatch* from the CWnd Object.

Can you elaborate me?

Gravatar
# re: Dynamically Setting COM Object Properties
posted by Mark Schmidt
on 6/7/2004 6:42 PM
Kula,

Look at the method GetControlUnknown. Something like:

LPUNKNOWN pUnk = pMyCwnd->GetControlUnknown();

// From there get the IDispatch interface of control.
LPDISPATCH pDisp = NULL;
pUnk->QueryInterface(IID_IDispatch, (LPVOID*)&pDisp);

Hope that helps.
Gravatar
# re: Dynamically Setting COM Object Properties
posted by Kula
on 6/7/2004 8:07 PM
Thanks Mark Schmidt,
I just realize how powerful QueryInterface is, your solution rocks!

Now the only thing troubles me is the string convention...:P
Gravatar
# re: Dynamically Setting COM Object Properties
posted by Ramanan
on 7/8/2004 10:11 PM
WONDERFUL ...and Thanks

Post A Comment
Title:
Name:
Email:
Website:
Comment:
Verification: