Sean Baxter

COM Automation:
Type Information (Part III)

1999

 

The Type Info Browser 

To really drive home the mechanics of the COM Type Information description interfaces, I've prepared a little browser for the world's greater benefit.   This program isn't perfect.  Oh certainly not.  However, keep in mind that Microsoft's OLEViewer is in version 57.  My viewer is in version 1  :)  You can download the source and the executable here.  Windows 98 users, beware.  The type browser is built from the common control tree view.  This tree view is subject to Windows 98's "Animate windows, menus and lists" enhancement.  With this setting enabled, the tree view will not be responsive when being expanded or collapsed.  You can turn this very annoying feature off through control panel-> display-> effects-> Animate windows, menus, and lists.

Recall from Part II that the type description structures FUNCDESC, VARDESC, TYPEATTR, and TLIBATTR (not covered in Part II) need to be freed with the appropriate ReleaseXXXX method in ITypeInfo or ITypeLib.  Because it's convienent to be able to break out of a stack frame without having to explicitly release all resources in that frame, I've defined some thin wrappers for these four structures.   Note that they throw an exception EVeryBadThing when their initialization function fails.  This saves us from having to check error codes.   However, note that I am not using any exception-throwing COM interface wrappers.  Those would clean the program up even more.  This will be left as an exercise to the reader (and if you do write a wrapper that throws exceptions on failed return codes, why don't you email it to me? :)

Enter the source code.  This first part includes definitions for the type description structures.  I'll break the source into chunks and make comments on each one.

#pragma comment(lib, "comctl32")

#include <windows.h>
#include <atlbase.h>
#include <commctrl.h>
#include <sstream>
#include <vector>

struct EVeryBadThing { };
struct CComTypeAttr {
    TYPEATTR* _typeAttr;
    CComPtr<ITypeInfo> _pTypeInfo;
    operator TYPEATTR*() { return _typeAttr; }
    TYPEATTR*
operator->() { return _typeAttr; }
    explicit CComTypeAttr(ITypeInfo* ti) throw(EVeryBadThing) : _pTypeInfo(ti) {
        HRESULT hr(_pTypeInfo->GetTypeAttr(&_typeAttr));
        if(hr)
throw EVeryBadThing();
    }
    ~CComTypeAttr() { _pTypeInfo->ReleaseTypeAttr(_typeAttr); }
};
struct CComFuncDesc {
    FUNCDESC* _funcDesc;
   
operator FUNCDESC*() { return _funcDesc; }
    FUNCDESC*
operator->() { return _funcDesc; }
    CComPtr<ITypeInfo> _pTypeInfo;
    CComFuncDesc(ITypeInfo* ti, int index)
throw(EVeryBadThing) : _pTypeInfo(ti) {
        HRESULT hr(_pTypeInfo->GetFuncDesc(index, &_funcDesc));
       
if(hr) throw EVeryBadThing();
    }
    ~CComFuncDesc() { _pTypeInfo->ReleaseFuncDesc(_funcDesc); }
};
struct CComVarDesc {
    VARDESC* _varDesc;
   
operator VARDESC*() { return _varDesc; }
    VARDESC*
operator->() { return _varDesc; }
    CComPtr<ITypeInfo> _pTypeInfo;
    CComVarDesc(ITypeInfo* ti, int index)
throw(EVeryBadThing) : _pTypeInfo(ti) {
        HRESULT hr(_pTypeInfo->GetVarDesc(index, &_varDesc));
       
if(hr) throw EVeryBadThing();
    }
    ~CComVarDesc() { _pTypeInfo->ReleaseVarDesc(_varDesc); }
};
struct CComLibAttr {
    TLIBATTR* _libAttr;
   
operator TLIBATTR*() { return _libAttr; }
    TLIBATTR*
operator->() { return _libAttr; }
    CComPtr<ITypeLib> _pTypeLib;
   
explicit CComLibAttr(ITypeLib* tlb) throw(EVeryBadThing) : _pTypeLib(tlb) {
        HRESULT hr(_pTypeLib->GetLibAttr(&_libAttr));
       
if(hr) throw EVeryBadThing();
    }
    ~CComLibAttr() { _pTypeLib->ReleaseTLibAttr(_libAttr); }
};

These are very simple wrappers.  When the appropriate initialization method fails, the constructor throws an EVeryBadThing exception to prevent the object from existing in an undefined state.  The next method, stringifyParameterAttributes, simply returns a string holding the parameter attributes gleaned from the PARAMDESC structure.

std::string stringifyParameterAttributes(PARAMDESC* paramDesc) {
    USHORT paramFlags = paramDesc->wParamFlags;
    int numFlags(0);
    for(DWORD bit(1); bit <= PARAMFLAG_FHASDEFAULT; bit<<=1)
        numFlags += (paramFlags & bit) ? 1 : 0;
    if(!numFlags) return "";
    std::ostringstream oss;
    oss<< '[';
    if(paramFlags & PARAMFLAG_FIN)
    { oss<< "in";
if(--numFlags) oss<< ", "; }
   
if(paramFlags & PARAMFLAG_FOUT)
    { oss<< "out";
if(--numFlags) oss<< ", "; }
   
if(paramFlags & PARAMFLAG_FLCID)
    { oss<< "lcid";
if(--numFlags) oss<< ", "; }
   
if(paramFlags & PARAMFLAG_FRETVAL)
    { oss<< "retval";
if(--numFlags) oss<< ", "; }
   
if(paramFlags & PARAMFLAG_FOPT)
    { oss<< "optional";
if(--numFlags) oss<< ", "; }
   
if(paramFlags & PARAMFLAG_FHASDEFAULT) {
        oss<< "defaultvalue";
       
if(paramDesc->pparamdescex) {
            oss<< '(';
            PARAMDESCEX& paramDescEx = *(paramDesc->pparamdescex);
            VARIANT defVal();
            CComBSTR bstrDefValue;
            CComVariant variant;
            HRESULT hr(VariantChangeType(&variant,
                &paramDescEx.varDefaultValue, 0, VT_BSTR));
           
if(hr) oss<< "???)";
           
else {
                char ansiDefValue[MAX_PATH];
                WideCharToMultiByte(CP_ACP, 0, variant.bstrVal,
                    SysStringLen(variant.bstrVal) + 1, ansiDefValue,
                    MAX_PATH, 0, 0);
               
if(paramDescEx.varDefaultValue.vt == VT_BSTR)
                    oss<< '\"'<< ansiDefValue<< '\"'<< ')';
                else oss<< ansiDefValue<< ')';
            }
        }
    }
    oss<< ']';
    return oss.str();
}

stringifyParameterAttributes iterates through each bit of PARAMDESC::wParamFlags (well, at least the bits we care about), and concatenates the parameter's attributes between two brackets - just like MIDL.  The PARAMFLAG_FHASDEFAULT bit should draw your attention.   Methods exposed through IDispatch::Invoke can have default values.   Because the parameters of a dispatch method are oleautomation compliant, the default values can be stored in VARIANTARG structures.   PARAMDESC::pparamdescex points to a PARAMDESCEX structure which holds the default value of the parameter.  stringifyParameterAttributes uses VariantChangeType to coerce the default parameter to a Basic String, which is then inserted into the character stream.

param1.gif (18041 bytes)

Figure 1

Figure 1 shows stringifyParameterAttributes at work.  Exhilarating, isn't it.

These next functions do most of the low-level type description interface work.  I'll forgo the comments, as these procedures have been explained in Part II:

std::string stringifyCustomType(HREFTYPE refType, ITypeInfo* pti) {
    CComPtr<ITypeInfo> pTypeInfo(pti);
    CComPtr<ITypeInfo> pCustTypeInfo;
    HRESULT hr(pTypeInfo->GetRefTypeInfo(refType, &pCustTypeInfo));
    if(hr) return "UnknownCustomType";
    CComBSTR bstrType;
    hr = pCustTypeInfo->GetDocumentation(-1, &bstrType, 0, 0, 0);
    if(hr) return "UnknownCustomType";
    char ansiType[MAX_PATH];
    WideCharToMultiByte(CP_ACP, 0, bstrType, bstrType.Length() + 1,

        ansiType, MAX_PATH, 0, 0);
    return ansiType;
}

std::string stringifyTypeDesc(TYPEDESC* typeDesc, ITypeInfo* pTypeInfo) {
    std::ostringstream oss;
    if(typeDesc->vt == VT_PTR) {
        oss<< stringifyTypeDesc(typeDesc->lptdesc, pTypeInfo)<< '*';
       
return oss.str();
    }
   
if(typeDesc->vt == VT_SAFEARRAY) {
        oss<< "SAFEARRAY("
            << stringifyTypeDesc(typeDesc->lptdesc, pTypeInfo)<< ')';
       
return oss.str();
    }
   
if(typeDesc->vt == VT_CARRAY) {
        oss<< stringifyTypeDesc(&typeDesc->lpadesc->tdescElem, pTypeInfo);
        for(int dim(0); typeDesc->lpadesc->cDims; ++dim)
            oss<< '['<< typeDesc->lpadesc->rgbounds[dim].lLbound<< "..."
                << (typeDesc->lpadesc->rgbounds[dim].cElements +
                typeDesc->lpadesc->rgbounds[dim].lLbound - 1)<< ']';
       
return oss.str();
    }
   
if(typeDesc->vt == VT_USERDEFINED) {
        oss<< stringifyCustomType(typeDesc->hreftype, pTypeInfo);
       
return oss.str();
    }
   
    switch(typeDesc->vt) {
        // VARIANT/VARIANTARG compatible types
    case VT_I2:
return "short";
   
case VT_I4: return "long";
   
case VT_R4: return "float";
   
case VT_R8: return "double";
   
case VT_CY: return "CY";
   
case VT_DATE: return "DATE";
   
case VT_BSTR: return "BSTR";
   
case VT_DISPATCH: return "IDispatch*";
   
case VT_ERROR: return "SCODE";
   
case VT_BOOL: return "VARIANT_BOOL";
   
case VT_VARIANT: return "VARIANT";
   
case VT_UNKNOWN: return "IUnknown*";
   
case VT_UI1: return "BYTE";
   
case VT_DECIMAL: return "DECIMAL";
   
case VT_I1: return "char";
   
case VT_UI2: return "USHORT";
   
case VT_UI4: return "ULONG";
   
case VT_I8: return "__int64";
   
case VT_UI8: return "unsigned __int64";
   
case VT_INT: return "int";
   
case VT_UINT: return "UINT";
   
case VT_HRESULT: return "HRESULT";
   
case VT_VOID: return "void";
   
case VT_LPSTR: return "char*";
   
case VT_LPWSTR: return "wchar_t*";
    }
    return "BIG ERROR!";
}

std::string stringifyVarDesc(VARDESC* varDesc, ITypeInfo* pti) {
    CComPtr<ITypeInfo> pTypeInfo(pti);
    std::ostringstream oss;
   
if(varDesc->varkind == VAR_CONST) oss<< "const ";
    oss<< stringifyTypeDesc(&varDesc->elemdescVar.tdesc, pTypeInfo);
    CComBSTR bstrName;
    HRESULT hr(pTypeInfo->GetDocumentation(varDesc->memid, &bstrName, 0, 0, 0));
   
if(hr) return "UnknownName";
    char ansiName[MAX_PATH];
    WideCharToMultiByte(CP_ACP, 0, bstrName, bstrName.Length() + 1, ansiName,
        MAX_PATH, 0, 0);
    oss<< ' '<< ansiName;
   
if(varDesc->varkind != VAR_CONST) return oss.str();
    oss<< " = ";
    CComVariant variant;
    hr = VariantChangeType(&variant, varDesc->lpvarValue, 0, VT_BSTR);
   
if(hr) oss<< "???";
    else {
        WideCharToMultiByte(CP_ACP, 0, variant.bstrVal,

            SysStringLen(variant.bstrVal) + 1, ansiName, MAX_PATH, 0, 0);
        oss<< ansiName;
    }
   
return oss.str();
}

Because stringifying an entire COM method prototype is one of the more complex tasks in the realm of type information, I've constructed two helper functions to make this simpler.  The first, stringifyFunctionArgument, concatenates the result of stringifyParameterAttributes with the result of stringifyTypeDescstringifyCOMMethod builds the entire prototype as seen in Figure 1.  It streams first the return type, then the method name, and finally the stringifyFunctionArgument result for each of the method's parameters.  These arguments are enclosed in parantheses (just like an IDL function prototype).

std::string stringifyFunctionArgument(ELEMDESC* elemDesc, ITypeInfo* pti) {
    CComPtr<ITypeInfo> pTypeInfo(pti);
    std::ostringstream oss;
    oss<< stringifyParameterAttributes(&elemDesc->paramdesc);
    if(oss.str().size()) oss<< ' ';
    oss<< stringifyTypeDesc(&elemDesc->tdesc, pti);
    return oss.str();
}

std::string stringifyCOMMethod(FUNCDESC* funcDesc, ITypeInfo* pti) {
    CComPtr<ITypeInfo> pTypeInfo(pti);
    std::ostringstream oss;
   
if(funcDesc->funckind == FUNC_DISPATCH)   
        oss<< "[id("<< (int)funcDesc->memid<< ')';
    else oss<< "[VOffset("<< funcDesc->oVft<< ')';
    switch(funcDesc->invkind) {
    case INVOKE_PROPERTYGET: oss<< ", propget] "; break;
   
case INVOKE_PROPERTYPUT: oss<< ", propput] "; break;
   
case INVOKE_PROPERTYPUTREF: oss<< ", propputref] "; break;
   
case INVOKE_FUNC: oss<< "] "; break;
    }
    oss<< stringifyTypeDesc(&funcDesc->elemdescFunc.tdesc, pTypeInfo);
    CComBSTR bstrName;
    pTypeInfo->GetDocumentation(funcDesc->memid, &bstrName, 0, 0, 0);
    char ansiName[MAX_PATH];
    WideCharToMultiByte(CP_ACP, 0, bstrName, bstrName.Length() + 1,
        ansiName, MAX_PATH, 0, 0);
    oss<< ' '<< ansiName<< '(';
    for(int curParam(0); curParam < funcDesc->cParams; ++curParam) {
        oss<< stringifyFunctionArgument(&funcDesc->lprgelemdescParam[curParam],
            pTypeInfo);
       
if(curParam < funcDesc->cParams - 1) oss<< ", ";
    }
    oss<< ')';
    return oss.str();
}

Each method is either invoked through a vtable interface or through IDispatch::Invoke.   The method's offset in the object's virtual table is displayed for interface methods, and the method's DISPID is displayed for dispinterface methods. 

With the six functions functions posted above, the core functionality of the type browser is complete.  What we now need to do is provide a way for the user to select a type library, and navigate the type library's contents through a tree view hierarchy.  Naturally, this is easier said than done.  I've whipped up a number of classes to accomplish this task.  Check out the class view:

clsview.gif (8122 bytes)

Figure 2

Looks nasty?  It isn't actually that bad - four of the classes are just our type description wrappers, and EVeryBadThing is the trivial exception structure.  To keep the app fast and prevent excessive memory usage, I've taken a "wait and find out" approach to expanding the items on the tree view.  The children for a node are constructed not when the parent node is constructed, but when the parent node is expanded.  The only problem with this is that the parent doesn't know how many children it has until it is actually expanded.  And to get expanded, it needs to have children.  All non-terminal nodes (like structures, interfaces, and containers of structures, interfaces, etc) add a single child node upon construction.  This is encapsulated in the CExpansionNode class.   When the parent node is expanded, the child node is deleted, and the legitimate child nodes are added.  Of course if there are no child nodes, then expanding the parent node will only cause the expansion button to disappear.  While this may seem problematic, it really isn't.  Microsoft's OLE Viewer (now in version 57) uses the same scheme.

template<typename T> class CExpansionNode {
    mutable HTREEITEM _expNode;
public:
    void BuildExpansionNode() {
        if(_expNode) return;
        TVINSERTSTRUCT tvInsertStruct;
        tvInsertStruct.hParent = static_cast<T*>(this)->treeItem();
        tvInsertStruct.hInsertAfter = TVI_FIRST;
        tvInsertStruct.item.mask = 0;
        _expNode = TreeView_InsertItem(
static_cast<T*>(this)->treeView(),
            &tvInsertStruct);
    }       
    void KillExpansionNode() {
        if(!_expNode)
return;
        TreeView_DeleteItem(static_cast<T*>(this)->treeView(), _expNode);
        _expNode = 0;
    }       
    CExpansionNode() : _expNode(0) { }
    CExpansionNode(const CExpansionNode& c) : _expNode(c._expNode) {
       c._expNode = 0;
    }
   CExpansionNode& operator=(const CExpansionNode& c) {
        KillExpansionNode();
        _expNode = c._expNode;
        c._expNode = 0;
        return *this;
    }
    ~CExpansionNode() {
        KillExpansionNode();
    }
};

CExpansionNode is a very simple class.  The template argument is the name of the derived class (which in this program will always be a node object).  T::treeItem returns the parent node's HTREEITEM.  T::treeView returns the HWND of the tree view.  The CExpansionNode's this pointer is static_casted to T* (which thunks if necessary) producing a pointer to the start of the derived object.  Notice that CExpansionNode's constructor does not insert the dummy node.  Why not?  The derived class T will also inherit CTypeBrowserBase.  The CTypeBrowserBase constructor must be invoked before the dummy node is built (to initialize the data for treeView and treeItem).  For this reason, construction of the dummy node has been relegated to CExpansionNode::BuildExpansionNode.  This method is called from the body of the node class's constructor.  This will all make more sense when you see CTypeBrowserBase, so here it is:

class CTypeBrowserBase {
   
mutable HTREEITEM _treeItem;
    HWND _treeView;
    std::string _displayName;
    HTREEITEM _parentTreeItem;
protected:
    CTypeBrowserBase* GetParentNode() {
       
if(_parentTreeItem == TVI_ROOT || !_parentTreeItem) return 0;
        TVITEM tvItem;
        tvItem.lParam = 0;
        tvItem.hItem = _parentTreeItem;
        tvItem.mask = TVIF_PARAM;
        TreeView_GetItem(_treeView, &tvItem);
       
return reinterpret_cast<CTypeBrowserBase*>(tvItem.lParam);
    }
public:
    CTypeBrowserBase() : _treeView(0) { }
    CTypeBrowserBase& operator=(const CTypeBrowserBase& c) {
       
if(_treeItem) TreeView_DeleteItem(treeView(), treeItem());
        _treeView = c._treeView;
        _displayName = c._displayName;
        _treeItem = c._treeItem;
        TVITEM tvItem;
        tvItem.mask = TVIF_HANDLE | TVIF_PARAM;
        tvItem.hItem = _treeItem;   
        TreeView_GetItem(treeView(), &tvItem);
        tvItem.lParam = reinterpret_cast<LPARAM>(this);
        TreeView_SetItem(treeView(), &tvItem);
        c._treeItem = 0;
       
return *this;
    }
   
    std::string displayName() {
return _displayName; }
    HWND treeView() {
return _treeView; }
    HTREEITEM treeItem() {
return _treeItem; }       
   
    CTypeBrowserBase(BSTR name, HWND tv, HTREEITEM parentTreeItem) :
        _treeView(tv), _parentTreeItem(parentTreeItem) {
        char ansiName[MAX_PATH];
        WideCharToMultiByte(CP_ACP, 0, name, SysStringLen(name) + 1,
            ansiName, MAX_PATH, 0, 0);
        _displayName = ansiName;
       
        TVINSERTSTRUCT tvInsertStruct;
        tvInsertStruct.hParent = parentTreeItem;
        tvInsertStruct.hInsertAfter = TVI_LAST;
        tvInsertStruct.item.mask = TVIF_TEXT | TVIF_PARAM;
        tvInsertStruct.item.pszText = LPSTR_TEXTCALLBACK;
        tvInsertStruct.item.cchTextMax = 0;
        tvInsertStruct.item.lParam = reinterpret_cast<LPARAM>(this);
       
        _treeItem = TreeView_InsertItem(treeView(), &tvInsertStruct);
    }   
    CTypeBrowserBase(const CTypeBrowserBase& c) : _treeView(c._treeView),
        _displayName(c._displayName), _treeItem(c._treeItem) {
        TVITEM tvItem;
        tvItem.mask = TVIF_HANDLE | TVIF_PARAM;
        tvItem.hItem = _treeItem;   
        TreeView_GetItem(treeView(), &tvItem);
        tvItem.lParam = reinterpret_cast<LPARAM>(this);
        TreeView_SetItem(treeView(), &tvItem);
        c._treeItem = 0;
    }   
    ~CTypeBrowserBase() {
       
if(treeItem()) {
            TreeView_DeleteItem(treeView(), treeItem());
            _treeItem = 0;
        }
    }
    virtual void collapse() = 0;
   
virtual void expand() = 0;   
   
virtual std::string editBoxText() {
        return std::string(" COM Owns You");
    }
};

CTypeBrowserBase is a class with a lot of functionality.  It is a base for all node class in the type browser program.  CTypeBrowserBase does two things: it manages node data and serves as a base class for the window procedure to send expand and collapse events.  CTypeBrowserBase::GetParentNode returns a pointer to the CTypeBrowserBase slice of the parent node.  This is a protected method so it can only be invoked by the derived class.  How does this function actually work?  It surely isn't returning any cached pointer: TVITEM::lParam is cast to CTypeBrowserBase* and returned to the caller.  Curious.  The lParam is four free bytes that the tree view control gives the programmer, to store any sort of information (akin to the GWL_USERDATA entry, but for individual nodes instead of windows).   Because the HTREEITEM handles are not nice indices into an array of nodes (as list view item handles would be) but pointers to opaque data, they aren't much help in associating the actual tree view item with corresponding type information object.  What we do is store the address of the type info object as the node's lParam.  Given an HTREEITEM handle to a node, the corresponding type info object can be accessed by dereferencing the pointer stored in TVITEM::lParam by calling the TreeView_GetItem macro.

The CTypeBrowserBase constructor creates the actual tree view node for the type information item.  The name of the node (the string that appears next to the node's button), the HWND of the tree view, and the HTREEITEM of the parent node are all passed as arguments.  The constructor initializes a TVINSERTSTRUCT structure using with the HWND and HTREEITEM parameters.  TVINSERTSTRUCT::item::pszText points to the node's display name.  When this value is LPSTR_TEXTCALLBACK, the tree view sends a TVN_GETDISPINFO message to its parent's window's procedure.  Our message handler casts the node's lParam to a CTypeBrowserBase pointer and invokes CTypeBrowserBase::displayName.  This displayName text is the CTypeBrowserBase constructor's first parameter.  The tree view can similarly retrieve an item's edit box text (displayed when the node is selected and the TVN_SELCHANGED message is fired).

Sounds like a great scheme, doesn't it!  Use the node's lParam to store the location of the corresponding type info object.  But wait...  What happens if the object changes location?  The pointer stored in the lParam then refers to garbage.  While an object obviously can't just pick up and move to a different place in memory, it can be copied.  These objects are often part of an STL vector, so they are created on the stack before being pushed to their container.  This sequence will invoke the object's copy constructor or assignment operator at least once.  So how do we handle this?  Recall the functionality of the Standard Library auto_ptr.  An auto_ptr has a single data member: a pointer to the data it wraps.  The auto_ptr's destructor deletes this memory if the pointer is non-zero.  Invoking the copy constructor or assignment operator on an auto_ptr will set the source's pointer to the argument object's pointer, and then clear the argument object's pointer.  This effectively transfers "ownership" of the memory to the most recently created auto_ptr.  We employ a similar strategy here. 

CTypeBrowserBase "owns" a tree view node.   Like auto_ptr, CTypeBrowserBase transfers ownership of its _treeItem member during copy construction and assignment operations.  The copy constructor grabs the TVITEM structure of the argument's node and writes its own pointer to TVITEM::lParam.  Here's the kicker: it sets the argument's _treeItem member to zero.  This means that the object has "lost ownership" over its node, and the object's death will not cause the destructor to remove its node from the tree.  To allow the copy constructor and assignment operator to fiddle with its argument's HTREEITEM, CTypeBrowserBase::_treeItem is defined as mutable.

CTypeBrowserBase also serves as the common base class for all nodes.  The virtual methods collapse, expand, and editBoxText are overridden by the derived class to perform operations specific to the type information object.  These methods, along with CTypeBrowserBase::displayName (which is non-virtual), are invoked from the parent window's message procedure.  The methods treeView and treeItem provide the derived class (and CExpansionNode) with access to the HWND of the tree view and the HTREEITEM of the node. 

Here come the first two node classes.

class CComEndNode : public CTypeBrowserBase {
public:
    CComEndNode(BSTR name, HWND tv, HTREEITEM parentTreeItem) :
        CTypeBrowserBase(name, tv, parentTreeItem) { }
   
virtual void collapse() { }
    virtual
void expand() { }
};

class CComMessageEndNode : public CComEndNode {
    std::string _message;
public:
    CComMessageEndNode(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        std::string message) :
        CComEndNode(name, tv, parentTreeItem), _message(message) { }
   
virtual std::string editBoxText() { return _message; }
};

Shamefully simple.  These two classes are terminating nodes; they don't inherit CExpansionNode, so they can't be expanded.  CComEndNode's constructor merely passes its parameters to CTypeBrowserBase and provides trivial implementations of collapse and expand.  The class inherits CTypeBrowserBase's implemention of editBoxText, which returns the string " COM Owns You."  CComMessageEndNode is identical to its base, CComEndNode, except it lets you override the edit box message with whatever you want. 

Obviously these terminating nodes aren't too helpful by themselves.  These end nodes are children of "container nodes," that is, nodes that are expandable.  Examples of these nodes include structures, enums, unions, interfaces, dispinterfaces, and typedef "containers."  Typedefs have no child values, so they are actually end nodes.  The following template class serves as a container for union values, structure values, or enum values, depending on the template argument.

class CComVariableGroup : public CTypeBrowserBase,
   
public CExpansionNode<CComVariableGroup> {
    CComPtr<ITypeInfo> _pTypeInfo;
    std::vector<CComEndNode> _items;
public:
    CComVariableGroup(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        ITypeInfo* ti) : CTypeBrowserBase(name, tv, parentTreeItem),
        _pTypeInfo(ti) {
        BuildExpansionNode();
    }
    virtual void collapse() {
        if(_items.size()) {
            _items.clear();
            BuildExpansionNode();
        }
    }
   
virtual void expand() {
        try {
            KillExpansionNode();
            CComTypeAttr typeAttr(_pTypeInfo);
            int totalVars(typeAttr->cVars);
            for(int var(0); var < totalVars; ++var) {
                try {
                    CComVarDesc varDesc(_pTypeInfo, var);
                    CComBSTR name(stringifyVarDesc(varDesc, _pTypeInfo).c_str());
                    _items.push_back(CComEndNode(name, treeView(), treeItem()));
                } catch (EVeryBadThing) { }
            }
        } catch (EVeryBadThing) { }
    }
};

CComVariableGroup contains any number of CComEndNode objects.  This class derives from CExpansionNode, meaning it is expandable.  The expand and collapse methods also have nontrivial implementations.  In addition to the parameters it sends to CTypeBrowserBase, CComVariableGroup's constructor is passed an ITypeInfo pointer.  This is a type description pointer for one of the type library's variable types, namely a structure, a union, or an enum.  The name of this variable type is passed to the constructor as the name parameter. TYPEATTR::cVars (initialized with the CComTypeAttr constructor) specifies the number of values for the type.  On expand, CComVariableGroup for loops through each of these variables and invokes stringifyVarDesc to produce a string version of the value.  Remember that for constants (i.e. enumerated values) stringifyVarDesc will append an equals sign and the constant value.  A CComEndNode is constructed from this string, and added to the variable group's vector.  Upon collapse, CComVariableGroup clears its vector, destroying all end nodes.  This invokes CTypeBrowserBase's destructor for each end node, which removes the node from the tree view.  Finally, CComVariableGroup's expansion node is built again to enable another expansion.

vargro1.gif (22555 bytes)

Figure 3

Figure 3 shows the relationships between the CComVariableGroup objects and their CComEndNodes.  In addition to the "Enums" node, expanding the "Structs" node and "Unions" node will expose more CComVariableGroups as well, each with a set of CComEndNodes.  So CComVariableGroup supplies functionality for three different types of variables.  That's a pretty flexible class.  CComInterfaceJoint is also a flexible class; it manages both interface and dispinterface nodes.

class CComInterfaceJoint : public CTypeBrowserBase,
   
public CExpansionNode<CComInterfaceJoint> {
    CComPtr<ITypeInfo> _pTypeInfo;
    std::vector<CComMessageEndNode> _items;
public:
    CComInterfaceJoint(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        ITypeInfo* ti) : CTypeBrowserBase(name, tv, parentTreeItem),
        _pTypeInfo(ti) {
        BuildExpansionNode();
    }
    virtual void collapse() {
        if(_items.size()) {
            _items.clear();
            BuildExpansionNode();
        }
    }
   
virtual void expand() {
        try {
            KillExpansionNode();
            CComTypeAttr typeAttr(_pTypeInfo);
            int totalFuncs(typeAttr->cFuncs);
            for(
int curFunc(0); curFunc < totalFuncs; ++curFunc) {
               
try {
                    CComFuncDesc funcDesc(_pTypeInfo, curFunc);
                    CComBSTR name[1];
                    UINT cNames;
                    HRESULT hr(_pTypeInfo->GetNames(funcDesc->memid,
                        reinterpret_cast<BSTR*>(&name), 1, &cNames));
                   
if(hr) continue;
                    char ansiName[MAX_PATH];
                    WideCharToMultiByte(CP_ACP, 0, *name, name->Length() + 1,
                        ansiName, MAX_PATH, 0, 0);
                    std::ostringstream oss;
                    oss<< ansiName;
                    switch(funcDesc->invkind) {
                    case INVOKE_FUNC: oss<< " (method)"; break;
                   
case INVOKE_PROPERTYGET: oss<< " (prop get)"; break;
                   
case INVOKE_PROPERTYPUT: oss<< " (prop put)"; break;
                   
case INVOKE_PROPERTYPUTREF: oss<< " (prop put ref)"; break;
                    }
                    std::string methodMessage(stringifyCOMMethod(funcDesc,
                        _pTypeInfo));
                    _items.push_back(CComMessageEndNode(CComBSTR(oss.str().c_str()),
                        treeView(), treeItem(), methodMessage));
                } catch(EVeryBadThing) { }
            }
        }
catch(EVeryBadThing) { }
    }
   
virtual std::string editBoxText() {
       
try {
            CComTypeAttr typeAttr(_pTypeInfo);
            wchar_t stringClsid[39];
            StringFromGUID2(typeAttr->guid, stringClsid, 39);
            char ansiClsid[39];
            WideCharToMultiByte(CP_ACP, 0, stringClsid, 39, ansiClsid, 39, 0, 0);
            std::ostringstream oss;
            oss<< "    IID: "<< ansiClsid<< ' '<<displayName();
            return oss.str();
        }
catch(EVeryBadThing) { return std::string(""); }            
    }
};

Like CComVariableGroup, CComInterfaceJoint is a container for end nodes.  In addition to the CTypeBrowserBase constructor parameters, CComInterfaceJoint::CComInterfaceJoint is passed an ITypeInfo pointer.  This is a type description for the interface in question, be it dispatch or virtual.  TYPEATTR::cFuncs (retrieved with the CComTypeAttr constructor) specifies the number of functions of the interface.  expand enters a for loop which iterates through each function.  ITypeInfo::GetNames returns the function's name, and a FUNCDESC::invkind switch stringifies the invoke kind of the function.  Together, these strings comprise the end node display names, as shown in Figure 4

Notice that CComInterfaceJoint manages not a vector of CComEndNodes, but of CComMessageEndNodes.  The latter class allows overriding of the edit box text.  In the case of CComInterfaceJoint's end nodes, the edit box displays the full function prototype, procured from stringifyCOMMethod.   We certainly are getting a lot of mileage from the functions presented in Part II and early in Part III.  Another interesting aspect of CComInterfaceJoint is its overridden editBoxText method.  When selected, the interface joint displays its IID and interface name in the edit box.  It might loook something like this: "IID: {35053A20-8589-11D1-B16A-00C0F0283628} IProgressBar."  One nice thing about the edit control is that you can select an arbitrary portion of its contents and copy it to the clipboard through the popup menu.  Quite helpful for dropping IIDs into your own projects.

intgro1.gif (25461 bytes)

Figure 4

Figure 4 shows the relationship between the CComInterfaceJoint and its CComMessageEndNode children.  By relying on stringifyCOMMethod, the CComMessageEndNode edit box text displays the DISPID for dispatch methods and the vtable offsets for virtual methods.  Now onto the containers!

template<TYPEKIND tk> class CComVariableGroupContainer :
    public CTypeBrowserBase, public CExpansionNode<CComVariableGroupContainer> {
    CComPtr<ITypeLib> _pTypeLib;
    std::vector<CComVariableGroup> _variables;
public:    
    CComVariableGroupContainer(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        ITypeLib* pTlb) : CTypeBrowserBase(name, tv, parentTreeItem),
        _pTypeLib(pTlb) {
        BuildExpansionNode();   
    }   
    virtual void expand() {
        KillExpansionNode();
        if(!_pTypeLib) return;
        UINT infoCount(_pTypeLib->GetTypeInfoCount());
       
if(!infoCount) return;
       
        for(int info(0); info < infoCount; ++info) {
            TYPEKIND typeKind;
            HRESULT hr(_pTypeLib->GetTypeInfoType(info, &typeKind));
           
if(hr || typeKind != tk) continue;
           
            CComBSTR name;
            hr = _pTypeLib->GetDocumentation(info, &name, 0, 0, 0);
           
if(hr) continue;
            CComPtr<ITypeInfo> pTypeInfo;
            hr = _pTypeLib->GetTypeInfo(info, &pTypeInfo);
           
if(hr) continue;
            _variables.push_back(CComVariableGroup(name, treeView(),
                treeItem(), pTypeInfo));
        }
    }   
   
virtual void collapse() {
       
if(_variables.size()) {
            _variables.clear();
            BuildExpansionNode();
        }
    }
   
virtual std::string editBoxText() { };
};

template<> std::string CComVariableGroupContainer<TKIND_ENUM>::editBoxText() {
    std::ostringstream oss;
    oss<< "    Enumerations in "<< GetParentNode()->displayName();
    return oss.str();
}

template<> std::string CComVariableGroupContainer<TKIND_UNION>::editBoxText() {
    std::ostringstream oss;
    oss<< "    Teamsters in "<< GetParentNode()->displayName();
   
return oss.str();
}

template<> std::string CComVariableGroupContainer<TKIND_RECORD>::editBoxText() {
    std::ostringstream oss;
    oss<< "    Structures in "<< GetParentNode()->displayName();
   
return oss.str();
}

CComVariableGroupContainer objects contain CComVariableGroups (how aptly named).  This is a templated class, where the template argument, tk, holds a value of the TYPEKIND enum.  Because the contained objects, CComVariableGroups only handle enums, unions, and structures, tk must only be TKIND_ENUM, TKIND_UNION, or TKIND_RECORD (why not TKIND_STRUCTURE I do not know).  The class's constructor takes a name parameter, which is "Enums," "Unions," or "Structs" depending on the TYPEKIND.  More importantly, the constructor is passed an ITypeLib pointer, which describes the type library in question. 

On expand, CComVariableGroupContainer retrieves the number of described types through ITypeLib::GetTypeInfoCount.   It then loops through each type, querying ITypeLib::GetTypeInfoType and comparing the returned TYPEKIND to the template argument TYPEKIND.  When the two match, the type's name is retrieved through ITypeLib::GetDocumentation and the ITypeInfo pointer describing the type is retrieved.  The type's name and type description pointer are passed to the CComVariableGroup constructor, which is pushed to the vector.  None of this is very complicated - with good design, building a type info browser isn't that painful.

I'm sure your eyes have wandered down to the editBoxText definition.  "What ht hell..." you must be thinking.  It returns no value.  In fact, it doesn't do anything.  CComVariableGroupContainer::editBoxText will not compile.  Why then did I define it this way?  To allow for specialization.  The three specializations that follow send customized messages to the edit box.  The name of the type library is glued to the stringified typekind ("Enumerations", "Structures", or "Teamsters" (Hoffa lives!)) and returned from editBoxText.  Yippie.  Download the executable and see for yourself.

template<TYPEKIND tk> class CComInterfaceGroupContainer :
    public CTypeBrowserBase, public CExpansionNode<CComInterfaceGroupContainer> {
    CComPtr<ITypeLib> _pTypeLib;
    std::vector<CComInterfaceJoint> _interfaces;
public:
    CComInterfaceGroupContainer(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        ITypeLib* pTlb) : CTypeBrowserBase(name, tv, parentTreeItem),
        _pTypeLib(pTlb) {
        BuildExpansionNode();
    }
    virtual void expand() {
        KillExpansionNode();
        if(!_pTypeLib) return;
        UINT infoCount(_pTypeLib->GetTypeInfoCount());
       
if(!infoCount) return;
        for(int info(0); info < infoCount; ++info) {
            TYPEKIND typeKind;
            HRESULT hr(_pTypeLib->GetTypeInfoType(info, &typeKind));
           
if(hr || typeKind != tk) continue;
           
            CComBSTR name;
            hr = _pTypeLib->GetDocumentation(info, &name, 0, 0, 0);
           
if(hr) continue;
            CComPtr<ITypeInfo> pTypeInfo;
            hr = _pTypeLib->GetTypeInfo(info, &pTypeInfo);
           
if(hr) continue;
            _interfaces.push_back(CComInterfaceJoint(name, treeView(),
                treeItem(), pTypeInfo));
        }
    }
   
virtual void collapse() {
       
if(_interfaces.size()) {
            _interfaces.clear();
            BuildExpansionNode();
        }
    }
   
virtual std::string editBoxText() { }
};

template<> std::string CComInterfaceGroupContainer<TKIND_DISPATCH>::editBoxText() {
    std::ostringstream oss;
    oss<< "    Dispatch Interfaces in "<< GetParentNode()->displayName();
   
return oss.str();
}

template<> std::string CComInterfaceGroupContainer<TKIND_INTERFACE>::editBoxText() {
    std::ostringstream oss;
    oss<< "    VTable Interfaces in "<< GetParentNode()->displayName();
   
return oss.str();
}

CComInterfaceGroupContainer is very similar to CComVariableGroupContainer.  The TYPEKIND template argument specifies which sort of interface the object describes: TKIND_INTERFACE or TKIND_DISPATCH.  The name of the interface is passed to the class's constructor, as is the type library pointer.  The expand method iterates through each of the library's types, comparing ITypeLib::GetTypeInfoType to the template argument tk.  On a match, the interface's name and ITypeInfo description pointer are passed to the CComInterfaceJoint constructor.  The resulting object is pushed to the vector.  The editBoxText specializations send a helpful messages to the application's edit control.

If you were really paying attention in Part II, you'd already have identified a problem with CComInterfaceGroupContainer::expand.  So let me clue you in: dual interfaces are always exposed as type TKIND_DISPATCH.  To quote the Automation Programmer's Reference, "For dual interfaces, ITypeLib::GetTypeInfo returns only the TKIND_DISPATCH type information.  To get the TKIND_INTERFACE type information, ITypeInfo::GetRefTypeOfImplType can be called on the TKIND_DISPATCH type information, passing an index of –1.   Then, the returned type information handle can be passed to ITypeInfo::GetRefTypeInfo."  This is easily taken care of with an expand specialization:

template<> void CComInterfaceGroupContainer<TKIND_INTERFACE>::expand() {
    KillExpansionNode();
    if(!_pTypeLib) return;
    UINT infoCount(_pTypeLib->GetTypeInfoCount());
    if(!infoCount)
return;
    for(int info(0); info < infoCount; ++info) {
        try {
            TYPEKIND typeKind;
            HRESULT hr(_pTypeLib->GetTypeInfoType(info, &typeKind));
           
if(hr) continue;
           
if(typeKind == TKIND_DISPATCH) {
                CComPtr<ITypeInfo> pDispatchInfo;
                hr = _pTypeLib->GetTypeInfo(info, &pDispatchInfo);
               
if(hr) continue;
                HREFTYPE interfaceRefType;
                hr = pDispatchInfo->GetRefTypeOfImplType(-1, &interfaceRefType);
               
if(hr) continue;
                CComPtr<ITypeInfo> pInterfaceInfo;
                hr = pDispatchInfo->GetRefTypeInfo(interfaceRefType,
                    &pInterfaceInfo);
               
if(hr) continue;
                CComTypeAttr typeAttr(pInterfaceInfo);
               
if(typeAttr->typekind != TKIND_INTERFACE) continue;
                CComBSTR name;
                hr = _pTypeLib->GetDocumentation(info, &name, 0, 0, 0);
               
if(hr) continue;
                _interfaces.push_back(CComInterfaceJoint(name, treeView(),
                    treeItem(), pInterfaceInfo));
               
continue;
            }
           
if(typeKind != TKIND_INTERFACE) continue;
            CComBSTR name;
            hr = _pTypeLib->GetDocumentation(info, &name, 0, 0, 0);
           
if(hr) continue;
            CComPtr<ITypeInfo> pTypeInfo;
            hr = _pTypeLib->GetTypeInfo(info, &pTypeInfo);
           
if(hr) continue;
            _interfaces.push_back(CComInterfaceJoint(name, treeView(),
                treeItem(), pTypeInfo));
        } catch(EVeryBadThing) { }
    }
}

Fairly beefy function; I'm getting paid by the line.  On second consideration, I'm not getting paid at all.  Anyways, this specialization has two cases: TKIND_INTERFACE (vtable interface) and TKIND_DISPATCH (dual or dispatch).  If ITypeLib::GetTypeInfoType returns TKIND_DISPATCH, GetRefTypeOfImplType is invoked on the corresponding ITypeInfo pointer to procure the vtable interface's HREFTYPE (if there is one).  ITypeInfo::GetRefTypeInfo produces the ITypeInfo pointer describing the vtable interface.  ITypeInfo::GetDocumentation produces the interface's name.  We ship these two pieces of informations off to CComInterfaceJoint's constructor, and add the resulting object to the vector.  If the initial ITypeLib::GetTypeInfoType call produces a TKIND_INTERFACE, we can skip all this nonsense and just take the straightforward approach.

We've reached the homestretch (well, in the same sense that a marathon runner who enters the fourteenth mile is in the homestretch).  How are typedefs added to the browser?  Check out CComTypedefContainer:

class CComTypedefContainer : public CTypeBrowserBase,
    CExpansionNode<CComTypedefContainer> {
    CComPtr<ITypeLib> _pTypeLib;
    std::vector<CComEndNode> _typedefs;
public:
    CComTypedefContainer(HWND tv, HTREEITEM parentTreeItem, ITypeLib* pTlb) :
        CTypeBrowserBase(CComBSTR("Typedefs"), tv, parentTreeItem),
        _pTypeLib(pTlb) {
        BuildExpansionNode();
    }
    virtual void expand() {
        KillExpansionNode();
        if(!_pTypeLib) return;
        UINT count(_pTypeLib->GetTypeInfoCount());
        for(int curAlias(0); curAlias < count; ++curAlias) {
            try {
                TYPEKIND typeKind;
                HRESULT hr(_pTypeLib->GetTypeInfoType(curAlias, &typeKind));
               
if(hr || typeKind != TKIND_ALIAS) continue;
                CComBSTR name;
                hr = _pTypeLib->GetDocumentation(curAlias, &name, 0, 0, 0);
               
if(hr) continue;
                CComPtr<ITypeInfo> pTypeInfo;
                hr = _pTypeLib->GetTypeInfo(curAlias, &pTypeInfo);
               
if(hr) continue;
                CComTypeAttr typeAttr(pTypeInfo);
                               
                std::ostringstream typeDefString;
                typeDefString<< "typedef "<<
                    stringifyTypeDesc(&typeAttr->tdescAlias, pTypeInfo);
                char ansiName[MAX_PATH];
                WideCharToMultiByte(CP_ACP, 0, name, name.Length() + 1,
                    ansiName, MAX_PATH, 0, 0);
                typeDefString<< ' '<< ansiName;
                _typedefs.push_back(CComEndNode(CComBSTR(
                    typeDefString.str().c_str()), treeView(), treeItem()));
            } catch(EVeryBadThing) { }
        }
    }
   
virtual void collapse() {
       
if(_typedefs.size()) {
            _typedefs.clear();
            BuildExpansionNode();
        }
    }
   
virtual std::string editBoxText() {
        std::ostringstream oss;
        oss<< "     Type Definitions in "<< GetParentNode()->displayName();
       
return oss.str();
    }       
};

CComTypedefContainer manages a vector of CComEndNodes.  Each end node's display value is a typedef statement.  This statement is the concatenation of "typedef", stringifyTypeDesc, and the type's name.  CComTypedefContainer adds typedefs to our type browser.  The only type left is the coclass (I am neglecting TKIND_MODULE types, as they are evil). 

class CComCoClassNode : public CTypeBrowserBase,
    public CExpansionNode<CComCoClassNode> {
    CComPtr<ITypeInfo> _pTypeInfo;
    std::vector<CComEndNode> _interfaces;
public:
    CComCoClassNode(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        ITypeInfo* ti) : CTypeBrowserBase(name, tv, parentTreeItem),
        _pTypeInfo(ti) {
        BuildExpansionNode();
    }
    virtual void expand() {
        try {
            KillExpansionNode();
           
if(!_pTypeInfo) return;
            CComTypeAttr typeAttr(_pTypeInfo);
            int implInterfaces(typeAttr->cImplTypes);
            for(int curInterface(0); curInterface < implInterfaces;
                ++curInterface) {
                HREFTYPE hRefType;
                HRESULT hr(_pTypeInfo->GetRefTypeOfImplType(curInterface,
                    &hRefType));
                CComPtr<ITypeInfo> interfaceTypeInfo;
                hr = _pTypeInfo->GetRefTypeInfo(hRefType, &interfaceTypeInfo);
               
if(hr) continue;
                CComBSTR interfaceName;
                hr = interfaceTypeInfo->GetDocumentation(-1, &interfaceName,
                    0, 0, 0);
                if(hr)
continue;
                _interfaces.push_back(CComEndNode(interfaceName, treeView(),
                    treeItem()));
            }
        } catch(EVeryBadThing) { }
    }
   
virtual void collapse() {
        BuildExpansionNode();
    }
   
virtual std::string editBoxText() {
        try {
            CComTypeAttr typeAttr(_pTypeInfo);
            wchar_t stringClsid[39];
            StringFromGUID2(typeAttr->guid, stringClsid, 39);
            char ansiClsid[39];
            WideCharToMultiByte(CP_ACP, 0, stringClsid, 39, ansiClsid, 39, 0, 0);
            std::ostringstream oss;
            oss<< " CLSID: "<< ansiClsid<< ' '<<displayName();
            return oss.str();
        } catch(EVeryBadThing) { return std::string(""); }           
    }
};

Expanding a coclass node displays a list of the interfaces implemented by the coclass.  These terminating nodes are managed by CComEndNode objects.  For a breakdown of the interface's methods, the user can look in the "Interfaces" and "Dispinterfaces" containers.  CComCoClassNode::editBoxText provides some useful information to the user.  A CComTypeAttr object is created, and the CLSID is gleaned from it.  This CLSID plus the name of the coclass is displayed in the edit control when the coclass node is selected.  The CComCoClassNode object is managed by (yes, you guessed it) CComCoClassContainer.  And here it is:

class CComCoClassContainer : public CTypeBrowserBase,
   
public CExpansionNode<CComCoClassContainer> {
    CComPtr<ITypeLib> _pTypeLib;
    std::vector<CComCoClassNode> _coClasses;
public:
    CComCoClassContainer(HWND tv, HTREEITEM parentTreeItem, ITypeLib* pTlb) :
        CTypeBrowserBase(CComBSTR("CoClasses"), tv, parentTreeItem),
        _pTypeLib(pTlb) {
        BuildExpansionNode();
    }
    virtual
void expand() {
        KillExpansionNode();
        if(!_pTypeLib) return;
        UINT count(_pTypeLib->GetTypeInfoCount());
        for(int curCoClass(0); curCoClass < count; ++curCoClass) {
            try {
                TYPEKIND typeKind;
                HRESULT hr(_pTypeLib->GetTypeInfoType(curCoClass, &typeKind));
               
if(hr || typeKind != TKIND_COCLASS) continue;
                CComBSTR name;
                hr = _pTypeLib->GetDocumentation(curCoClass, &name, 0, 0, 0);
               
if(hr) continue;
                char ansiName[MAX_PATH];
                WideCharToMultiByte(CP_ACP, 0, name, name.Length() + 1,
                    ansiName, MAX_PATH, 0, 0);
                CComPtr<ITypeInfo> pTypeInfo;
                hr = _pTypeLib->GetTypeInfo(curCoClass, &pTypeInfo);
               
if(hr) continue;
                _coClasses.push_back(CComCoClassNode(name, treeView(),
                    treeItem(), pTypeInfo));
            } catch(EVeryBadThing) { }
        }
    }
   
virtual void collapse() {
       
if(_coClasses.size()) {
            _coClasses.clear();
            BuildExpansionNode();
        }
    }
   
virtual std::string editBoxText() {
        std::ostringstream oss;
        oss<< "     CoClasses in "<< GetParentNode()->displayName();
       
return oss.str();
    }
};

CComCoClassContainer behaves in the spirit of the other containers.  It constructs CTypeBrowserBase by passing the string "CoClasses," which sets "CoClasses" as the display name.   On expand, each type in the library is tested against TKIND_COCLASS.  For those that match, the name of the coclass is retrieved with ITypeLib::GetDocumentationCComCoClassNode objects are created for each coclass, and are initialized with the name of the coclass.  It's very consistent with the classes we've already covered.

Guess what?  There are only two classes left to cover.   But before we work on those, take a look at Figure 5 and make sure you understand where each class fits into the tree view.

brkdown1.gif (35997 bytes)

Figure 5

This program looks a lot simpler after seeing Figure 5, doesn't it!  Or maybe not.  There are two classes remaining: CComTypeLibrary manages the actual type library pointer and serves as the parent node for the six container classes.  From Figure 5, "Microsoft XML 1.0 <1.0>," "Active Setup Control Library <1.0>," and "PStore 1.0 Type Library <1.0>" are CComTypeLibrary objects.  All of these type library objects are children of the root object, CComTypeLibraryContainer.  While CComTypeLibrary is a very simple class (it just has a few auto_ptrs and statements to initialize them), CComTypeLibraryContainer does some nontrivial work.  CComTypeLibraryContainer::expand iterates through all the keys in HKEY_CLASSES_ROOT\TypeLib and pulls out the type libraries.  It then instantiates CComTypeLibrary objects from this registry information. 

class CComTypeLibrary : public CTypeBrowserBase,
   
public CExpansionNode<CComTypeLibrary> {
    std::auto_ptr<CComVariableGroupContainer<TKIND_ENUM> > _enumContainer;
    std::auto_ptr<CComVariableGroupContainer<TKIND_RECORD> > _structContainer;
    std::auto_ptr<CComVariableGroupContainer<TKIND_UNION> > _unionContainer;
    std::auto_ptr<CComTypedefContainer> _typedefContainer;
    std::auto_ptr<CComInterfaceGroupContainer<TKIND_INTERFACE> >
        _interfaceContainer;
    std::auto_ptr<CComInterfaceGroupContainer<TKIND_DISPATCH> >
        _dispatchContainer;
    std::auto_ptr<CComCoClassContainer> _coClassContainer;
    std::string _editBoxText;
    CComBSTR _filename;
public:
    CComTypeLibrary(BSTR name, HWND tv, HTREEITEM parentTreeItem,
        CComBSTR filename, std::string editBoxInfo) : CTypeBrowserBase(name,
        tv, parentTreeItem), _editBoxText(editBoxInfo), _filename(filename) {
        BuildExpansionNode();
    }   
    virtual void expand() {
        KillExpansionNode();
        CComPtr<ITypeLib> pTypeLib;
        HRESULT hr(LoadTypeLib(_filename, &pTypeLib));
        if(hr) return;
        _enumContainer = std::auto_ptr<CComVariableGroupContainer<TKIND_ENUM> >(
            new CComVariableGroupContainer<TKIND_ENUM>(CComBSTR(L"Enums"),
            treeView(), treeItem(), pTypeLib));
        _structContainer = std::auto_ptr<CComVariableGroupContainer<TKIND_RECORD> >(
            new CComVariableGroupContainer<TKIND_RECORD>(CComBSTR(L"Structs"),
            treeView(), treeItem(), pTypeLib));
        _unionContainer = std::auto_ptr<CComVariableGroupContainer<TKIND_UNION> >(
            new CComVariableGroupContainer<TKIND_UNION>(CComBSTR(L"Unions"),
            treeView(), treeItem(), pTypeLib));
        _typedefContainer = std::auto_ptr<CComTypedefContainer>(new
            CComTypedefContainer(treeView(), treeItem(), pTypeLib));
        _interfaceContainer =
            std::auto_ptr<CComInterfaceGroupContainer<TKIND_INTERFACE> >(
            new CComInterfaceGroupContainer<TKIND_INTERFACE>(
            CComBSTR(L"Interfaces"), treeView(), treeItem(), pTypeLib));
        _dispatchContainer =
            std::auto_ptr<CComInterfaceGroupContainer<TKIND_DISPATCH> >(
            new CComInterfaceGroupContainer<TKIND_DISPATCH>(
            CComBSTR(L"Dispinterfaces"), treeView(), treeItem(), pTypeLib));
        _coClassContainer = std::auto_ptr<CComCoClassContainer>(
            new CComCoClassContainer(treeView(), treeItem(), pTypeLib));
    }   
   
virtual void