Step 2: Use Helper Classes to Add Methods and Properties

You can use the helper classes CElemDesc, CVarDesc, and CFuncDesc together with IProvideDynamicClassInfoImpl to extend an existing type information object.

The template for IProvideDynamicClassInfoImpl includes seven parameters; only the first three (class, run-time class ID, and GUID of source type information) are required. If you request dynamic type information (by setting bGetStaticRuntimeTypeInfo to FALSE), your designer will support dynamic type information. In this case, the framework generates a base type information object from the class and run-time class ID specified in the first two parameters when IProvideDynamicClassInfo is called.

The following example shows how to add a method to the existing dynamic type information that has been created by IProvideDynamicClassInfoImpl. The code adds a new method to the source interface of the dynamic type information of the run-time coclass. It uses the CFuncDesc and CElemDesc helper classes, described later in this section.

HRESULT   CShapesDynDsgnCtl::AddShapeToTypeInfo(int iShapeType)
{
   HRESULT            hr   = E_FAIL;
   CComPtr<ITypeInfo>   pITypeInfo;
   USES_CONVERSION;

   //--------------------------------------------------
   // get a pointer to the source lib for later.
   //--------------------------------------------------
   hr = GetSourceLib(&pITypeLib);
   CLEANUP_ON_ERROR(hr);

   switch(iShapeType)
   {
   case ICIRCLETYPE:
      //--------------------------------------------------
      // generate name
      //--------------------------------------------------
      strcpy(pchname,"Circle\0");
      //--------------------------------------------------
      // get the circle count
      //--------------------------------------------------
      iCount = m_pShapesListArray[ICIRCLETYPE]->GetCount();
      _itoa(iCount,pchnum,10);
      strcat(pchname,pchnum);
      //--------------------------------------------------
      // create new CFuncDesc object based on zero indexed
      // count -- this method will look like this :
      // ICircleObject * CircleX(void), where X = iCount
      //--------------------------------------------------
      pFuncDesc = new CFuncDesc(DISPID_CIRCLE_BASEOFFSET + 
         (iCount),INVOKE_PROPERTYGET);
      if(!pFuncDesc)
         return(E_OUTOFMEMORY);
      hr = pFuncDesc->SetFuncName(A2OLE(pchname));
      CLEANUP_ON_FAILURE(hr);
      //--------------------------------------------------
      // get to the circle object typeinfo in the 
      // source typelib
      //--------------------------------------------------
      hr = pITypeLib->GetTypeInfoOfGuid(CLSID_CircleObject, &pITypeInfo);
      CLEANUP_ON_FAILURE(hr);
      //--------------------------------------------------
      // create the CElemDesc objectpointing to the circle object
      // typeinfo.
      //--------------------------------------------------
      pElemDesc = new CElemDesc;
      if(!pElemDesc)
      {
         hr = E_OUTOFMEMORY;
         CLEANUP_ON_FAILURE(hr);
      }
      hr = pElemDesc->SetType(VT_USERDEFINED,1);
      CLEANUP_ON_FAILURE(hr);
      hr = pElemDesc->SetUserDefinedData(pITypeInfo);
      CLEANUP_ON_FAILURE(hr);
      hr = pFuncDesc->SetReturnType(pElemDesc);
      CLEANUP_ON_FAILURE(hr);
       hr = AddMethod(IDEFINTERFACE,pFuncDesc);   
      CLEANUP_ON_FAILURE(hr);
      break;

Generating new type information is not the only way to add methods or properties to the dynamic type information of the run-time object. You can also model the new type information on existing type information.

First, you extract the type information for a method or property from an existing type information object, then insert it into the dynamic type information object of the run-time object. The section of code below shows how to copy a property from one interface to another.

STDMETHOD(CopyProperty)(int iPropertyIndex,ITypeInfo * ptiSrcInterface)
   {
      HRESULT         hr            = E_FAIL;
      BSTR         bstrPropertyName= NULL;   
      unsigned int   uiNames         = 0;
      CVarDesc *      pVarDesc      = NULL;
      ELEMDESC *      pElemDesc       = NULL;
      TYPEDESC *      pTypeDesc      = NULL;

      if(!ptiSrcInterface)
         return(E_POINTER);
      //--------------------------------------------------
      // get the VARDESC that will provide property
      // attributes.
      //--------------------------------------------------
      pVarDesc = new CVarDesc(iPropertyIndex,ptiSrcInterface);
      if(!pVarDesc)
         return(E_OUTOFMEMORY);
      //--------------------------------------------------
      // Now add to the default interface
      //--------------------------------------------------
      hr = AddProperty(pVarDesc)
      CLEANUP_ON_FAILURE(hr);
      …
}

Adding methods and properties is the most common way of extending type information. You can also, however, directly access the ICreateTypeInfo pointer of the dynamic type information object. IProvideDynamicClassInfoImpl is meant to be a starting point that provides basic features for extending type information.

Using the CElemDesc Helper Class

The CElemDesc class encapsulates a ELEMDESC structure. ELEMDESC structures are part of FUNCDESC and VARDESC structures; they provide data on method parameters, method return types, and property types. The public methods of the CElemDesc class are listed below.

class CElemDesc
{
public:
   CElemDesc();
   CElemDesc(ELEMDESC  * pElemDesc,ITypeInfo * ptiSrc = NULL);
   CElemDesc(CElemDesc * pElemDesc);
   ~CElemDesc();
   CElemDesc & operator = (CElemDesc & edToCopy);
   operator ELEMDESC();
   STDMETHOD(Attach)   (ELEMDESC *pElemDesc,ITypeInfo * ptiSrc = NULL);
   ELEMDESC * Detach(void);
   STDMETHOD(Reset)   (ELEMDESC *pElemDesc);
   CElemDesc * SetNext(CElemDesc * pNext);
   CElemDesc * GetNext(void);
   STDMETHOD(SetName)   (BSTR bstrName);
   STDMETHOD(GetName)   (BSTR *pbstrName);
   STDMETHOD(SetType)   (VARTYPE vt,int iIndirectionCt = 0);
   STDMETHOD(SetUserDefinedData)   (ITypeInfo * ptiSrc);
   STDMETHOD(SetArrayData)   (ARRAYDESC * pArrayDesc);
   STDMETHOD(GetType)      (VARTYPE * pvt);
   STDMETHOD(GetUserDefinedData)   (ITypeInfo ** pptiSrc);
   STDMETHOD(GetArrayData)   (ARRAYDESC ** ppArrayDesc);
   STDMETHOD(SetParamDesc)   (PARAMDESC ParamDesc);
   STDMETHOD(GetParamDesc)   (PARAMDESC * pParamDesc);
   STDMETHOD(SetIDLDesc)   (IDLDESC IdlDesc);
   STDMETHOD(GetIDLDesc)   (IDLDESC * pIdlDesc);
   STDMETHOD(AddUserDefinedData)   (ICreateTypeInfo * pctiDestInterface);
};

The constructors for this class are as follows:

   CElemDesc();

Use this constructor to create a blank element descriptor. You are responsible for setting the type attributes on this object.

   CElemDesc(ELEMDESC  * pElemDesc,ITypeInfo * ptiSrc = NULL);

Use this constructor to create a CElemDesc object from an existing ELEMDESC structure. The ELEMDESC structure is assumed to have been retrieved from an existing ITypeInfo: that interface is the second parameter in the constructor, and is required if the type of the ELEMDESC is VT_USERDEFINED.

   CElemDesc(CElemDesc * pElemDesc);

Use this constructor to create a CElemDesc object from an existing CElemDesc object.

   CElemDesc & operator = (CElemDesc & edToCopy);

Use this operator to copy the contents of one CElemDesc object to another.

   operator ELEMDESC();

Use this operator to extract the ELEMDESC structure from the CElemDesc object.

   STDMETHOD(Attach)   (ELEMDESC *pElemDesc,ITypeInfo * ptiSrc = NULL);

Use this method to attach the CElemDesc object to an ELEMDESC structure. The ITypeInfo pointer is required only if the type of the ELEMDESC structure is VT_USERDEFINED.

   ELEMDESC * Detach(void);

This method detaches the CElemDesc object from the ELEMDESC structure it currently points to.

   STDMETHOD(Reset)   (ELEMDESC *pElemDesc);

This method resets the location of the internal ELEMDESC structure pointer so that it points to the supplied ELEMDESC structure.

   CElemDesc * SetNext(CElemDesc * pNext);
   CElemDesc * GetNext(void);

These methods link CElemDesc objects. Linking is done by a CFuncDesc object as parameters are added to that object.

   STDMETHOD(SetName)   (BSTR bstrName);
   STDMETHOD(GetName)   (BSTR *pbstrName);

These methods set and get the element names.

   STDMETHOD(SetType)   (VARTYPE vt,int iIndirectionCt = 0);

Use this method to set the type of the CElemDesc object. Possible types are the same as those for an ELEMDESC object. Call SetType before you set any user defined data or array data.

   STDMETHOD(SetUserDefinedData)   (ITypeInfo * ptiSrc);

Call this method after calling SetType with the type set to VT_USERDEFINED. The parameter is the ITypeInfo pointer that applies to the user defined type.

   STDMETHOD(SetArrayData)   (ARRAYDESC * pArrayDesc);

Call this method after calling SetType with the type set to VT_ARRAY. The pArrayDesc parameter must be a valid safe array descriptor with a lifetime that is guaranteed to exceed that of the CElemDesc object.

   STDMETHOD(GetType)      (VARTYPE * pvt);

Call this method to get the type of the CElemDesc object.

   STDMETHOD(GetUserDefinedData)   (ITypeInfo ** pptiSrc);

Call this method to get an ITypeInfo pointer to the user-defined data. The pointer is valid only within the source type information object supplied with either the constructor for the CElemDesc object or the CElemDesc::Attach method. If the CElemDesc type is not VT_USERDEFINED, this method returns S_FALSE.

   STDMETHOD(GetArrayData)   (ARRAYDESC ** ppArrayDesc);

Call this method to get the array descriptor for the CElemDesc object. The array descriptor is assumed to point to a safe array that has a lifetime exceeding that of the CElemDesc object. If the type of CElemDesc is not VT_ARRAY, this method returns S_FALSE.

   STDMETHOD(SetParamDesc)   (PARAMDESC ParamDesc);
   STDMETHOD(GetParamDesc)   (PARAMDESC *pParamDesc);

These methods get and set the PARAMDESC structure. The PARAMDESC structure is defined by Automation; it contains information needed to transfer the element described by CElemDesc between processes.

   STDMETHOD(SetIDLDesc)   (IDLDESC IdlDesc);
   STDMETHOD(GetIDLDesc)   (IDLDESC *pIdlDesc);

These methods get and set the IDLDESC structure. The IDLDESC structure is an Automation-defined type that contains information needed in transferring the element described by CIdlDesc between processes.

   STDMETHOD(AddUserDefinedData)   (ICreateTypeInfo * pctiDestInterface);

Use this method to add user-defined data to the ICreateTypeInfo pointer that you are using to create a new method/property. The AddMethod and AddProperty methods of the IProvideDynamicClassInfoImpl class call this method to add user-defined data to the dynamically created default and source interfaces.

Using the CFuncDesc Helper Class

This class encapsulates a FUNCDESC structure, so that you can get and set various fields of the FUNCDESC structure without having to know their exact location. It also allows you to set parameters and return types using the CElemDesc class.

The public methods of the CFuncDesc class are listed below.

class CFuncDesc
{
public:
   CFuncDesc(MEMBERID memid,INVOKEKIND invkind,
      FUNCKIND funckind = FUNC_DISPATCH,
      CALLCONV callconv = CC_STDCALL,
      WORD funcflags = 0);
   CFuncDesc(int iIndex,ITypeInfo * ptiSrc = NULL);
   CFuncDesc::~CFuncDesc();
   operator FUNCDESC();
   STDMETHOD(GetSrcTypeInfo)   (ITypeInfo **ppTypeInfo);
   STDMETHOD(AddParameter)      (CElemDesc * pElemDesc, BOOL bOptional = FALSE);
   STDMETHOD(GetParameter)      (CElemDesc **ppElemDesc,int iCount);
   STDMETHOD(SetReturnType)   (CElemDesc * pElemDesc);
   STDMETHOD(GetReturnType)   (CElemDesc ** ppElemDesc);
   STDMETHOD(GetNames)         (SAFEARRAY ** ppsarray);
   STDMETHOD(SetFuncName)      (BSTR bstrFuncName);
   STDMETHOD(GetFuncName)      (BSTR * pbstrFuncName);
   STDMETHOD(SetRetCodes)      (SCODE  * prgscode, int icount);
   STDMETHOD(GetRetCodes)      (SCODE ** pprgscode); 
   STDMETHOD(SetMemid)         (MEMBERID Memid);
   STDMETHOD(GetMemid)         (MEMBERID *pMemid);
   STDMETHOD(SetInvKind)      (INVOKEKIND Invkind);
   STDMETHOD(GetInvKind)      (INVOKEKIND *pInvkind);
   STDMETHOD(SetFuncKind)      (FUNCKIND Funckind);
   STDMETHOD(GetFuncKind)      (FUNCKIND * pFunckind);
   STDMETHOD(SetCallConv)      (CALLCONV Callconv);
   STDMETHOD(GetCallConv)      (CALLCONV * pCallconv);
   STDMETHOD(SetFuncFlags)      (WORD wfuncflags);
   STDMETHOD(GetFuncFlags)      (WORD * pwfuncflags);
   STDMETHOD(GetCParams)      (int * pCParams);
   STDMETHOD(GetCSCodes)      (int * pCScodes);
}

The GetXxx and SetXxx methods make the class quite large, but the size is necessary because Automation's FUNCDESC structure is complex.

The important methods in this class hide memory management of parameters and return types. Those methods are described in this section; see CFuncDesc for a summary of the other methods.

CFuncDesc(MEMBERID memid,INVOKEKIND invkind,
   FUNCKIND funckind = FUNC_DISPATCH,
   CALLCONV callconv = CC_STDCALL,
   WORD funcflags = 0);

The first constructor is used when creating a CFuncDesc object without a reference type information object. You might do this to add a new method (one that doesn't exist in any type library) to the default or event interface for the coclass.

CFuncDesc(int iIndex,ITypeInfo * ptiSrc);

The second method creates a CFuncDesc representation of a method that already exists in valid interface type information object. You must supply the index, iIndex, of the method in the interface. The constructor gets a FUNCDESC structure from the input ITypeInfo and releases it upon deletion.

operator FUNCDESC(); 

This method extracts the FUNCDESC structure from the CFuncDesc object. Use this method to get a direct pointer to the FUNCDESC structure, as required by some methods of the ITypeInfo and ICreateTypeInfo interfaces.

STDMETHOD(GetSrcTypeInfo)   (ITypeInfo **ppTypeInfo);

This method provides access to the type information of the method. The CFuncDesc object has a type information object only if it was constructed using a source type information object. If the CFuncDesc object has no such object, this method returns S_FALSE.

STDMETHOD(AddParameter)(CElemDesc * pElemDesc,BOOL bOptional = FALSE);

This method adds a parameter to the method. Parameters are added in order, and cannot later be removed. Note that the parameter is encapsulated in a CElemDesc object. The bOptional flag should be TRUE if the parameter is optional, and FALSE otherwise.

STDMETHOD(GetParameter)(CElemDesc **ppElemDesc,int iCount);

This method returns the parameter designated by the index value iCount. The iCount index starts with zero and must specify a valid parameter location.

STDMETHOD(SetReturnType)   (CElemDesc * pElemDesc);

This method sets the return type of the method to an object type encapsulated in the CElemDesc class. The type of returned object is defined within that CElemDesc class. Note that if a method returns an SCODE, you should use the SetSCode method.

STDMETHOD(GetReturnType)   (CElemDesc ** ppElemDesc);

This method gets a method's return type. If the return type is an SCODE, GetReturnType returns S_FALSE. Use GetSCode to return an SCODE.

STDMETHOD(GetNames)      (SAFEARRAY ** ppsarray);

This method gets the names of the method and its parameters and loads them at the location specified by the input SAFEARRAY pointer. Your code must free the SAFEARRAY pointer. Like Automation's ITypeInfo method-name retrieval mechanism, GetNames does not return the name of a property-put method.

STDMETHOD(SetFuncName)   (BSTR bstrFuncName);

This method sets the name of the method.

STDMETHOD(GetFuncName)   (BSTR * pbstrFuncName);

This method returns the name of the method.

Using the CVarDesc Helper Class

This class encapsulates a VARDESC structure, which is used to manipulate a property variable that can be added to an interface.

class CVarDesc
{
public:
   CVarDesc(MEMBERID memid,VARTYPE vt,unsigned short wVarFlags = 0,
      VARKIND varkind = VAR_DISPATCH);
   CVarDesc(int iIndex,ITypeInfo * psrcTypeInfo);
   ~CVarDesc();
   operator VARDESC();
   STDMETHOD(SetName)   (BSTR bstrName);
   STDMETHOD(GetName)   (BSTR * pbstrElemName);
   STDMETHOD(SetType)   (VARTYPE vt,int iIndirectionCt = 0);
   STDMETHOD(SetUserDefinedData)(ITypeInfo * ptiSrc);
   STDMETHOD(SetArrayData)(ARRAYDESC * pArrayDesc);
   STDMETHOD(GetType)   (VARTYPE * pvt);
   STDMETHOD(GetUserDefinedData)   (ITypeInfo ** pptiSrc);
   STDMETHOD(GetArrayData)   (ARRAYDESC ** ppArrayDesc);
   STDMETHOD(SetParamDesc)   (PARAMDESC ParamDesc);
   STDMETHOD(GetParamDesc)   (PARAMDESC *pParamDesc);
   STDMETHOD(SetIDLDesc)   (IDLDESC IDLDesc);
   STDMETHOD(GetIDLDesc)   (IDLDESC *pIDLDesc);
   STDMETHOD(SetMemid)   (MEMBERID Memid);
   STDMETHOD(GetMemid)   (MEMBERID *pMemid);
   STDMETHOD(SetVarFlags)   (unsigned short wVarFlags);
   STDMETHOD(GetVarFlags)   (unsigned short  *pwVarFlags);
   STDMETHOD(SetVarkind)   (VARKIND Varkind);
   STDMETHOD(GetVarkind)   (VARKIND *pVarkind);
   STDMETHOD(SetoInst)   (unsigned long uloInst);
   STDMETHOD(GetoInst)   (unsigned long * puloInst);
   STDMETHOD(SetvarValue)   (VARIANT * pvarValue);
   STDMETHOD(GetvarValue)   (VARIANT ** ppVarValue);
   STDMETHOD(AddUserDefinedData)(ICreateTypeInfo * pctiDestInterface);
};

The CVarDesc object contains a CElemDesc object, because the VARDESC structure contains an ELEMDESC structure. CVarDesc methods therefore wrap the CElemDesc methods. The following methods are unique to CVarDesc.

   CVarDesc(MEMBERID memid,VARTYPE vt,unsigned short wVarFlags = 0,
      VARKIND varkind = VAR_DISPATCH);

This constructor creates a CVarDesc from scratch. The required parameters are the MEMBERID and the VARTYPE for the descriptor. In addition, you can optionally specify the VarFlags and VARKIND.

   CVarDesc(int iIndex,ITypeInfo * psrcTypeInfo);

This constructor creates a CVarDesc from an existing ITypeInfo pointer. You must supply iIndex, the index of the variable in the ITypeInfo. The CVarDesc class retrieves a VARDESC structure from the ITypeInfo and releases the VARDESC structure when it is deleted.

   operator VARDESC();

This method extracts the VARDESC structure from the CVarDesc object.

   STDMETHOD(SetoInst)   (unsigned long uloInst);
   STDMETHOD(GetoInst)   (unsigned long * puloInst);

These methods set and return the oInst member of the VARDESC structure that is encapsulated by the CVarDesc object. The oInst member supplies a fixed offset into the instance of another variable at which the current variable resides. It is used when the VARKIND of the variable is VAR_PERINSTANCE and the variable is a member of another variable.

   STDMETHOD(SetvarValue)   (VARIANT * pvarValue);
   STDMETHOD(GetvarValue)   (VARIANT ** ppVarValue);

These methods set and return the varValue field of the VARDESC structure. The varValue field is used when the type of the variable is VAR_CONST.

See Also

Step 3: Support Type Extensions at Run Time