Sean Baxter

COM Automation:
Type Information (Part II)

1999

 

Type Info Motivations 

COM is the most ambitious software technology of all time.  As the universally recognized protocol for interoperable software, COM has standardized client-server communication.  COM Automation provides the foundation for truly language-independent code distribution.  Development platforms implementing the Automation controller mechanisms can connect to any Automation server, putting millions upon millions of lines of tested and reliable code at every programmer's fingertips.

COM Type Information, the driving force responsible for this great interoperability between client and server, is the result of a mad programmer's eugenics project.  "Let's take a C++ header file," the mad programmer mused, "and make it elite."  The mad programmer made a list of the things that C++ headers define or declare:

These are undeniably the building blocks of all applications.  The mad programmer did not necessarily seek to improve these basic types; instead he constructed a radically new implement to their delivery.  Throwing pots and pans, the mad programmer shouted "Away with ye, blasted header files!"  He recognized these sorrowful limitations about C++ header files:

These are truly appaling deficiencies.  This lack of langauge and location transparency makes conventional headers innapropriate as the type information mechanism for component development.  The mad programmer remedied all of these problems with COM Type Information. 

The first thing he did was get rid of the text file.  COM Type Information is stored in binary type libraries.  These type libraries are opaque data; there is no benefit to directly working with these files.  Why is this important?   Implementors never need to write a type information parser for their Automation controllers.  Instead, the COM runtime library provides a number of mechanisms for loading a type library (LoadTypeLib, LoadRegTypeLib, IProvideClassInfo::GetClassInfo and IDispatch::GetTypeInfo are most common) and providing the information contained within through the interfaces ITypeInfo and ITypeLib.   This eliminates the language dependency inherent in C++ headers. 

All methods parameterized in COM type libraries are part of a COM interface (with one minor exception we'll get to in a bit).  The client can communicate with a remote server by way of a proxy object which implements the requested interface.  This capability capitalizes on the virtual function call indirection, where an interface pointer of type IFoo may in reality point to a pointer to an instance of the _IFooProxyVtbl vtable.  Each slot in this vtable points to a remoting function.  So, for example, invoking a method IFoo::Hello could very likely delegate to a remoting function _IFoo_Hello_Proxy.  Thanks to COM interfaces, life is good.

But wait!  What if someone specifies method parameters which aren't remotable?  Windows API has many types with process-affinity.  The HDC is a pointer to opaque data describing a window or in-memory device context.  Clearly any HDC is meaningless to remote machines.  It's even meaningless to other processes; hence why controls that implement IViewObject are all in-proc servers (IViewObject::Draw takes two HDC parameters).  Non-remotable types abound, and COM servers should avoid using them as function parameters unless they play some critical role.  Our mad programmer friend, every bit the genius, made provisions to COM Type Information to prevent these unwanted non-remotable types from creeping into interface methods.  You can define an interface to support only "Automation-compatible types."  By no means of coincidence, the Automation-compatible types are the very same types which can be stored in a VARIANT.  They are all, of course, remotable types.

Think about calling methods of dispatch interfaces.  You resolve the DISPID of whatever method with IDispatch::GetIDsOfNames, and call the method by passing the DISPID to IDispatch::Invoke.  If the method takes parameters, you need to instantiate a DISPPARAMS structure and load the contained VARIANTARG array with your method arguments.  Whoa, a VARIANTARG array.   VARIANTARG is identical to VARIANT, but it takes parameters by reference as well as by value (to allow [out] parameters).  Since all VARIANT types are Automation-compatible types (as stated above), all parameters passed to a method through IDispatch::Invoke are also Automation-compatible types.  By extension, any interface which supports dispatch calls to its methods (dispatch interfaces and dual interfaces) are Automation-compatible.  By utilizing interfaces and mandating Automation-compatible types, COM has solved the location transparency problem in an elegant fashion.

Next on the table: identifier uniqueness.  C++ fails miserably here.   The language just isn't capable of producing truly component-oriented software without some help from COM.  The items contained in COM type libraries have their own GUIDs.  The identifier names are really just a convience; COM software works with the GUIDs internally to avoid name collisions.  And what about support of multiple languages?  You can further differentiate between type libraries by means of the lcid, or locale identifier.

As I've stated before, COM is a protocol for interoperable software.  You don't need to worry about the vendor of one type library issuing non-standard extensions.  With COM, you can feel safe that no one is pulling the rug out from under you with slop like #pragma and _declspec

Finally, the remaining item on our list, "C++ headers must be distributed externally from their libraries," has been solved with COM Type Information.  The CreateTypeLib API (and tools which encapsulate it, like MIDL) produce binary TLB files.  As demonstrated in Part I, these type libraries can be added to your project as a resource; LoadTypeLib has no trouble plucking the type library resource out of a distributable.

Clearly, COM Type Information is a powerful system.  Unfortunately, our mad programmer isn't called "mad" for nothing.  ITypeLib and ITypeInfo, the COM-provided interfaces for pulling meaningful information from the opaque type library data, are a touch on the nasty side.  Actually the recursive definitions of some of the type description structures is downright bizarre.  The mad programmer is scheduled to appear in front of a war tribunal for this crime against humanity.  Renowned COM-guy Don Box has even ranked ITypeInfo as his number two least favorite interface (only topped by another Automation interface, IDispatch).   While Microsoft deserves some ghastly punishment for these interfaces (perhaps a week of Chinese water torture for the executives.  Or some heavy DOJ action.   Hell - just force Bill and Steve to watch the Frank and Kathy Lee Christmas Special... twice!), we can't change ITypeLib and ITypeInfo.  The only thing to do is grit your teeth and dive in.

The Interfaces 

Before we start our exploration of the deep, dark, spooky crevices of the ITypeLib and ITypeInfo interfaces, let's find out what exactly can be parameterized in a COM type library.

  • Constants / Enumerations
  • Structures
  • Unions
  • Typedefs
  • Functions
  • Classes
  • Enumerations
  • Structures
  • Unions
  • Typedefs
  • Interfaces
  • Coclasses
  • Modules

Figure 1

The left column comprises the items that can be defined in a C++ header file.   The items in the right column are what you can define a COM type library.   Nearly everything you can define in a C++ header you can define as COM Type Information.  While you don't see "Functions" on the COM side of the table, an interface is essentially a group functions.  Nothing surprising there.  The only other differnce in Figure 1 is the existance of a "Modules" item for COM type libraries.  A module is a collection of miscellaneous items, inclunding constants and non-member functions.  I strongly suggest avoiding modules altogether.  While they afford some additional flexibility for your project, they cause more problems than they're worth.  Many Automation controllers don't work with modules at all; so their usefulness is pretty limited.

While you can produce type libraries with the ICreateTypeLib and ICreateTypeInfo interfaces obtained from CreateTypeLib, using MIDL (or the tool supplied by your particular development platform) is a better idea.  In addition to providing a clean, C-like syntax for defining type libraries, MIDL can generate marshalling code for custom interfaces.  Construction of a comparable tool would be a massive undertaking.

So without further ado, we begin our tour of COM Type Information.  Having a few instances of the MSDN help open is highly recommended. 

You are an Automation controller and are handed an IDispatch interface pointer.  Your job is to present a list of the object's methods and properties to the user, who, if it weren't for a single pane of strategically placed glass, would be strangling you out of coding frustration.  Don't screw up.

IDispatch::GetTypeInfoCount(UINT* pctinfo) is the place to start.  This function's [out] argument indicates if the object provides type information.  If after the call pctinfo holds 1, your object supports type information; otherwise you are out of luck.  Obtaining an ITypeInfo pointer for the dispatch interface is incredibly easy - just invoke IDispatch::GetTypeInfo.  The first parameter, UINT iTInfo is fairly worthless.  You will always pass 0, requesting type information for the IDispatch interface (you aren't sure that any other interfaces even exist at this point).  We have an ITypeInfo pointer.  Now what?

A cursory inspection of ITypeInfo shows that this interface supports the methods of IDispatch.  It's likely that the IDispatch methods exposed to the client actually delegate to the ITypeInfo implementations, as demonstrated in Part I.  The first thing you'll want to do when presented with a new ITypeInfo pointer is get its type attributes; call ITypeInfo::GetTypeAttr().   Notice that GetTypeAttr takes not a pointer to a TYPEATTR structure, but a pointer to a pointer.  It's an [out] parameter.  The server allocates the space for the structure, so you are responsible for freeing it by invoking ITypeInfo::ReleaseTypeAttr.  

from OAIDL.IDL

typedef struct tagTYPEATTR {
    GUID guid;                      /* the GUID of the TypeInfo */
    LCID lcid;                      /* locale of member names and doc strings */
    DWORD dwReserved;
    MEMBERID memidConstructor;      /* ID of constructor, MEMBERID_NIL if none */

    MEMBERID memidDestructor;       /* ID of destructor, MEMBERID_NIL if none */
    LPOLESTR lpstrSchema;
    ULONG cbSizeInstance;           /* the size of an instance of this type */
    TYPEKIND typekind;              /* the kind of type this typeinfo describes */
    WORD cFuncs;                    /* number of functions */
    WORD cVars;                     /* number of variables / data members */
    WORD cImplTypes;                /* number of implemented interfaces */
    WORD cbSizeVft;                 /* the size of this types virtual func table */
    WORD cbAlignment;               /* specifies the alignment requirements for
                                       an instance of this type,
                                       0 = align on 64k boundary
                                       1 = byte align
                                       2 = word align
                                       4 = dword align... */

    WORD wTypeFlags;
    WORD wMajorVerNum;              /* major version number */
    WORD wMinorVerNum;              /* minor version number */
    TYPEDESC tdescAlias;            /* if typekind == TKIND_ALIAS this field
                                       specifies the type for which this type
                                       is an alias */

    IDLDESC idldescType;            /* IDL attributes of the described type */
} TYPEATTR, * LPTYPEATTR;

Don't let the size of this thing scare you!  It's one of the simpler structures in the Type Information API.  The GUID of TYPEATTR is the identifier of whatever the invoked ITypeInfo pointer describes.  As we'll study later, ITypeInfo describes more than just interfaces.  Since this TYPEATTR describes the IDispatch interface of an object (you CoCreateInstanced for IID_IDispatch did you not?), it would be logical that TYPEATTR::guid equals IID_IDispatch.   Right?  Well, actually no.  This GUID is the identifier of the default interface for the object.  For example, if we CoCreateInstance a MSComctlLib.Slider (Slider control), QueryInterfaced for IID_IDispatch, invoked IDispatch::GetTypeInfo, and finally ITypeInfo::GetTypeAttr, we'd discover that TYPEATTR::guid would equal IID_ISlider.   This is far more helpful than IID_IDispatch; it would be quite unfortunate if the type information for all default interfaces returned the same interface identifier.

TYPEATTR::typekind indicates exactly what the ITypeInfo pointer is describing. 

from OAIDL.IDL

typedef [v1_enum] enum tagTYPEKIND {
    TKIND_ENUM = 0,
    TKIND_RECORD,
    TKIND_MODULE,
    TKIND_INTERFACE,
    TKIND_DISPATCH,
    TKIND_COCLASS,
    TKIND_ALIAS,
    TKIND_UNION,
    TKIND_MAX                        /* end of enum marker */
} TYPEKIND;

It's important to notice that TYPEKIND includes both TKIND_INTERFACE and TKIND_DISPATCH values, for describing vtable interfaces and dispinterfaces, respectively.  So what does IDispatch::GetTypeInfo give you?  Obviously the interface is a dispinterface, but the interface might also be dual.  There is a slight anomaly here: IDispatch::GetTypeInfo might give you a TKIND_INTERFACE description, but on the other hand, it might give you a TKIND_DISPATCH description.  Well, let's say you are working with a dual interface, and IDispatch::GetTypeInfo hands you a TKIND_INTERFACE pointer, but what you want is a TKIND_DISPATCH pointer.  Pop quiz hot shot.  What do you do?  What do you do?

Check out the method ITypeInfo::GetRefTypeOfImplType.  The parameters are UINT index and HREFTYPE* pRefType, respectively.  The Automation Programmer's Reference states, "If a type description describes a COM class, it retrieves the type description of the implemented interface types. For an interface, GetRefTypeOfImplType returns the type information for inherited interfaces, if any exist."  So how does that help us?  Check the comment: "If the TKIND_DISPATCH type description is for a dual interface, the TKIND_INTERFACE type description can be obtained by calling GetRefTypeOfImplType with an index of -1, and by passing the returned pRefType handle to GetRefTypeInfo to retrieve the type information."  Well, we have a TKIND_INTERFACE type description and want to get a TKIND_DISPATCH description.   Unbenownst to the official documentation, GetRefTypeOfImplType is actually an oscillating function of sorts.  For a TKIND_DISPATCH type description, passing it -1 yields an HREFTYPE for a TKIND_INTERFACE description.  Similarly, for a TKIND_INTERFACE description, passing GetRefTypeOfImplType -1 yields an HREFTYPE for a TKIND_DISPATCH description.  Wacky.

Now we know how to find a dispinterface description from an interface description, and vice versa, but how do we even know if the interface is dual?  TYPEATTR is not a complicated structure, but it tells you a lot about the type in question.  TYPEATTR::wTypeFlags is an instance of the enumeration TYPEFLAGS

from OAIDL.IDL

typedef enum tagTYPEFLAGS {
    TYPEFLAG_FAPPOBJECT = 0x01,
    TYPEFLAG_FCANCREATE = 0x02,
    TYPEFLAG_FLICENSED = 0x04,
    TYPEFLAG_FPREDECLID = 0x08,
    TYPEFLAG_FHIDDEN = 0x10,
    TYPEFLAG_FCONTROL = 0x20,
    TYPEFLAG_FDUAL = 0x40,
    TYPEFLAG_FNONEXTENSIBLE = 0x80,
    TYPEFLAG_FOLEAUTOMATION = 0x100,
    TYPEFLAG_FRESTRICTED = 0x200,
    TYPEFLAG_FAGGREGATABLE = 0x400,
    TYPEFLAG_FREPLACEABLE = 0x800,
    TYPEFLAG_FDISPATCHABLE = 0x1000,
    TYPEFLAG_FREVERSEBIND = 0x2000
} TYPEFLAGS;

Aha!  The TYPEFLAG_FDUAL bit is set for dual interfaces.  For dual interfaces, the TYPEFLAG_FOLEAUTOMATION flag is also set (for the reasons presented in the "Type Info Motivations" section).  Note, however, that TYPEFLAG_FOLEAUTOMATION is not set for pure-dispatch interfaces (even though they can only use automation compatible types).  TYPEFLAG_FNONEXTENSIBLE is an important attribute.  Dispinterfaces are wily creatures.  Their definitions can change at runtime by programming IDispatch::GetIDsOfNames to resolve or not resolve names based on state.  This ability is worrisome to Automation controllers which use ID binding (using Invoke to call methods but resolving the DISPIDs at compile time).  TYPEFLAG_FNONEXTENSIBLE gives the client the guarantee that GetIDsOfNames will always resolve the same identifiers.  The other TYPEFLAGS values are generic MIDL attributes, or are used to describe the other items listed in Figure 1.

Let's finish up our coverage of TYPEATTR.  If TYPEATTR::typekind indicates that the type information is describing an interface or dispinterface, TYPEATTR::cImplTypes specifies the number of immediate base classes the interface derives from.  Since COM interfaces do not support multiple inheritance, this value will always be 0 or 1.   The first of the COM Commandments, "Thou shalt derive IUnknown," proves that TYPEATTR::cImplTypes will hold 1 for every interface other than IUnknownIUnknown, of course, has no base class.  How do you retrieve a type description for an interface's base?   Invoke ITypeInfo::GetRefTypeOfImplType, passing 0 for index.   Pass the resultant HREFTYPE to ITypeInfo::GetRefTypeInfo to procure the appropriate ITypeInfo pointer.

TYPEATTR::wMajorVerNum and wMinorVerNum simply hold the version of the type.  This corresponds to the MIDL version attribute.  TYPEATTR::cbSizeInstance applies not to interfaces, but structures and unions.  This holds the size in bytes of an instance of the structure or union.  TYPEATTR::cbSizeVft gives the size of the virtual table of the described TKIND_INTERFACE.  Everything mentioned above is nice to know, but it doesn't hit at the heart of type information.  By far the two most important elements of TYPEATTR are cVars and cFuncs.

These two members specify the number of "variables" (enum values, structure members, union members) and functions (interface methods) that the described type contains.  These two values are mutually exclusive; when cVar is nonzero, cFuncs is zero, and vice versa.  Pulling method definitions out of the type library is a difficult thing.  We'll cover variables first.  For this you need to get a type description interface for the structure, enum, or union in question.  Of course all we have right now is the type description of the default dispatch interface.  An ITypeLib pointer to the containing type library can be acquired with the appropriately named method ITypeInfo::GetContainingTypeLib.  The returned ITypeLib pointer gives you access to the type descriptions for all seven categories in Figure 1.  If you'd rather load the type library without instantiating any objects, use LoadTypeLib or LoadRegTypeLib.

ITypeLib::GetTypeInfoCount is a curious and mis-documented function.  As of the January 99 MSDN distribution, Microsoft claims that it takes no parameters and returns HRESULT.  This return value, MSDN states, is either S_OK or E_NOTIMPL.  Well, then where's the "count" in GetTypeInfoCount?   The answer is simple: MSDN is screwed up.  Checking OAIDL.IDL shows that ITypeLib::GetTypeInfoCount returns a UINT.  This value is the total number of type descriptions in the type library.  Passing an index between 0 and GetTypeInfoCount - 1 to ITypeLib::GetTypeInfo returns the appropriate ITypeInfo type description pointer.  Here's where things get fun.

Let's say we want to print out a list of all the enumerations in the type library.   We have our ITypeLib pointer, and have retrieved the count of type descriptions with ITypeLib::GetTypeInfoCount.  How do we tell if any arbitrary type description (based on its index) describes an enum?  ITypeLib::GetTypeInfoType returns a TYPEKIND when passed an index.  This is the same as the TYPEATTR we just covered.  If ITypeLib::GetTypeInfoType on an index between 0 and GetTypeInfoCount - 1 returns TKIND_ENUM, we know we've got a winning index.  Now we just need to print out its name.   Take a look through the type description interfaces for something to resolve a name from an ITypeInfo pointer or ITypeLib index.  Aha!   ITypeInfo::GetNames.  Sorry.  While this looks promising, GetNames is used to produce method names and arguments, and structure, enum, and union values, as indicated by its MEMBERID parameter (think DISPID).  GetNames won't work, but ITypeLib::GetDocumentation will (why type names are called "documentation" is beyond me).  We can pass the type library index to ITypeLib::GetDocumentation to retrieve a bunch of goodies.  Not just the name, but the helpstring, helpcontext, and helpfile too.  Here's a sample app:

#include <windows.h>
#include <atlbase.h>
#include <iostream>

void main() {
    HRESULT hr(CoInitialize(0));
   
if(hr) { std::cout<< "No COM... This is embarassing.\n\n"; return; }

    CComPtr<ITypeLib> pTypeLib;
    hr = LoadTypeLib(L"C:\\Windows\\System\\msdxm.ocx", &pTypeLib);
   
if(hr) { std::cout<< "Couldn't load the library :(\n\n"; return; }

    UINT infoCount(pTypeLib->GetTypeInfoCount());
    for(UINT curEnum(0); curEnum < infoCount; ++curEnum) {
        TYPEKIND typeKind;
        hr = pTypeLib->GetTypeInfoType(curEnum, &typeKind);
        if(hr || typeKind != TKIND_ENUM) continue;
       
        CComBSTR name;
        CComBSTR helpString;
        hr = pTypeLib->GetDocumentation(curEnum, &name, &helpString, 0, 0);

        if(hr) continue;
        std::wcout<< name.m_str;

        if(helpString) std::wcout<< L" helpstring: "<< helpString.m_str;
        std::wcout<< L"\n";   
    }
}

Amazingly simple code.  The OCX file specified is the Windows Media Player component.  Here is the program output:

enumres1.gif (5931 bytes)

That was sure easy.  ITypeLib::GetTypeInfo will return the type description for the specified index.  Calling ITypeInfo::GetTypeAttr fills out a TYPEATTR struct, giving the GUID and number of values in the enumerator.  Invoking ITypeInfo::GetVarDesc on the indices 0 to TYPEATTR::cVars builds a VARDESC structure for each value in the enumerator.

from OAIDL.IDL

typedef struct tagVARDESC {
    MEMBERID memid;
    LPOLESTR lpstrSchema;
    [switch_type(VARKIND), switch_is(varkind)] union {
        /* offset of variable within the instance */
        [case(VAR_PERINSTANCE, VAR_DISPATCH, VAR_STATIC)] ULONG oInst;
        [case(VAR_CONST)] VARIANT * lpvarValue; /* the value of the constant */
    };
    ELEMDESC elemdescVar;
    WORD wVarFlags;
    VARKIND varkind;
} VARDESC, * LPVARDESC;

The first element, VARDESC::memid, can be passed to ITypeInfo::GetNames to retrieve the name of the enumeration value.  For structure and union data members, VARDESC::varkind is usually set to VAR_PERINSTANCE; for enums it is set to VAR_CONST.  However, structs and unions can both contain constant data members, which don't add to the size of an instance of the struct or union.  The lpvarValue VARIANT holds the default value for the constant.  Because the type of this VARIANT is not necessarily the type of the constant, it must be able to be coerced.  The type of the constant is described by the ELEMDESC instance VARDESC::elemdescVar.

from OAIDL.IDL

typedef struct tagELEMDESC {
    TYPEDESC tdesc;                      /* the type of the element */
    PARAMDESC paramdesc;                 /* IDLDESC is a subset of PARAMDESC */
} ELEMDESC;

ELEMDESC is a powerful structure used for describing nearly everything in a COM type library.  Interface method parameters are each described with a TYPEDESC, one of ELEMDESC's two items, as are structure and union members (and typedefs too).  Let's look at the PARAMDESC structure.

from OAIDL.IDL

typedef struct tagPARAMDESC {
    LPPARAMDESCEX pparamdescex;          /* valid if PARAMFLAG_FHASDEFAULT bit is set */
    USHORT wParamFlags;                  /* IN, OUT, etc */
} PARAMDESC, * LPPARAMDESC;

Starting to fear a proliferation of structures yet?  There's much more to come.  The constants for PARAMDESC::wParamFlags are as follows.

from OAIDL.IDL

const USHORT PARAMFLAG_NONE = 0x00;
const USHORT PARAMFLAG_FIN = 0x01;
const USHORT PARAMFLAG_FOUT = 0x02;
const USHORT PARAMFLAG_FLCID = 0x04;
const USHORT PARAMFLAG_FRETVAL = 0x08;
const USHORT PARAMFLAG_FOPT = 0x10;
const USHORT PARAMFLAG_FHASDEFAULT = 0x20;
const USHORT PARAMFLAG_FHASCUSTDATA = 0x40;

These flags differ from the others we've seen; they aren't part of an enumeration.  The seven items from Figure 1 don't include constants defined out in the global namespace, so how is this possible?  Quite simple really - the constants aren't inside a library clause.  The PARAMFLAG_FHASCUSTDATA is so rarely used (I've never seen it used) we'll ignore it here.  The remaining flags all modify method parameters.  Each corresponds to a MIDL attribute: in, out, lcid, retval, optional, and defaultvalue

If the PARAMFLAG_FHASDEFEAULT bit is set on the PARAMDESC::wParamFlags element, PARAMDESC::pparamdescex is a pointer to the method's default value.  This does not hold the value of our enumeration item; that's back in VARDESC::lpvarValue.  

from OAIDL.IDL

typedef struct tagPARAMDESCEX {
    ULONG cBytes;                        /* size of this structure */
    VARIANTARG varDefaultValue;          /* default value of this parameter */
} PARAMDESCEX, * LPPARAMDESCEX;

Because parameters can be passed to methods by reference (typically as [out] parameters), the VARIANTARG structure (which allows VT_BYREF data) is used instead of VARIANT. 

We've covered PARAMDESC, so let's start on the other element of ELEMDESC: TYPEDESC.  Things will get complicated here.  Much of the criticism of the COM Type Information interfaces is a result of the loony design of TYPEDESC.  However once you figure out its designs, it isn't that bad to work with.

from OAIDL.IDL

typedef struct tagTYPEDESC {
    [switch_type(VARTYPE), switch_is(vt)] union {
        [case(VT_PTR, VT_SAFEARRAY)] struct tagTYPEDESC * lptdesc;
        [
case(VT_CARRAY)] struct tagARRAYDESC * lpadesc;
        [
case(VT_USERDEFINED)] HREFTYPE hreftype;
        [default] ;
    };
    VARTYPE vt;
} TYPEDESC;

Fortunately IDL makes the intent of unions pretty clear.  Let's see here, if TYPEDESC::vt is VT_PTR or VT_SAFEARRAY, then TYPEDESC::lptdesc is a valid pointer to a...  TYPEDESC structure.  Oh hell.  A recursively defined structure.  For convienence, we name our original VARDESC structure varDesc.  We grab a reference to the first contained TYPEDESC: TYPEDESC& typeDesc = varDesc.elemdescVar.tdesc.  If typeDesc.vt is VT_PTR, then varDesc describes a pointer to type typeDesc->lptdesc.  If typeDesc->lptdesc.vt equals VT_I4, for example, then we have a type description for a long* element.  Automation-compatible types are by value or by a single reference, so they won't lead you down more than one level of indirection via VT_PTR values (a single VT_PTR is basically equivalent to the VARIANT VT_BYREF flag).  Of course not all types are Automation-compatible.  If you don't care about IDispatch and program only a custom interface (like most C++ programmers do), your types can be arbitrarily complex.  The TYPEDESC structure won't hold you back.

typeDesc.vt equaling VT_SAFEARRAY also sets up a special case.

from OAIDL.IDL

typedef struct tagSAFEARRAYBOUND {
    ULONG cElements;
    LONG lLbound;
} SAFEARRAYBOUND, * LPSAFEARRAYBOUND;

typedef struct tagSAFEARRAY {
    USHORT cDims;
    USHORT fFeatures;
    ULONG cbElements;
    ULONG cLocks;
    PVOID pvData;
    SAFEARRAYBOUND rgsabound[];
} SAFEARRAY;

const USHORT FADF_AUTO       = 0x0001;  /* array is allocated on the stack */
const USHORT FADF_STATIC     = 0x0002;  /* array is staticly allocated */
const USHORT FADF_EMBEDDED   = 0x0004;  /* array is embedded in a structure */
const USHORT FADF_FIXEDSIZE  = 0x0010;  /* may not be resized or reallocated */
const USHORT FADF_RECORD     = 0x0020;  /* an array of records */
const USHORT FADF_HAVEIID    = 0x0040;  /* with FADF_DISPATCH, FADF_UNKNOWN */
                                        /* array has an IID for interfaces */

const USHORT FADF_HAVEVARTYPE= 0x0080;  /* array has a VT type */
const USHORT FADF_BSTR       = 0x0100;  /* an array of BSTRs */
const USHORT FADF_UNKNOWN    = 0x0200;  /* an array of IUnknown* */
const USHORT FADF_DISPATCH   = 0x0400;  /* an array of IDispatch* */
const USHORT FADF_VARIANT    = 0x0800;  /* an array of VARIANTs */
const USHORT FADF_RESERVED   = 0xF008;  /* reserved bits */

SAFEARRAY is a structure for storing arrays of arbitrary complexity.  Not only does it allow multidimensional arrays, but it provides a SAFEARRAYBOUND instance for each dimension to describe the starting index (unlike C++ arrays, SAFEARRAYs don't have to be zero-based) and number of elements.  The SAFEARRAY and SafeArrayXXXX COM runtime methods are documented in the Automation Programmer's Guide.  If typeDesc.vt contains VT_SAFEARRAY, then the varDesc instance describes a type SAFEARRAY(typeDesc->lptdesc)

from OAIDL.IDL

typedef struct tagARRAYDESC {
    TYPEDESC tdescElem;                          /* element type */
    USHORT cDims;                                /* dimension count */
    [size_is(cDims)] SAFEARRAYBOUND rgbounds[];  /* var len array of bounds */
} ARRAYDESC;

typeDesc.vt equaling VT_CARRAY makes typeDesc.lpadesc a valid ARRAYDESC pointer.  This structure is essentially a watered down SAFEARRAY.  The main difference is that while the dimensions and elements of a SAFEARRAY can be defined at runtime, VT_CARRAY is stricly a compile time mechanism.  If typeDesc.vt equals VT_CARRAY, varDesc describes an array of typeDesc->lpadesc.tdescElem with dimension typeDesc->lpadesc.cDims.

If typeDesc.vt is not VT_PTR, VT_SAFEARRAY, VT_CARRAY, or VT_USERDEFINED, it holds a real VARIANT type.  This is the terminating condition of the recursive definition of TYPEDESC.  These VARIANT types are the primitives from which all arrays and user-defined types are built from.  Speaking of user-defined types, the TYPEDESC union switch has a case for VT_USERDEFINED.  Let's investigate that now (by the way, we are still exploring the mechanisms used to display the values of an enumeration.  We're just taking the scenic route).

typeDesc.hreftype is valid if typeDesc.vt holds VT_USERDEFINED.  As we've already discovered, passing an HREFTYPE to ITypeInfo::GetRefTypeInfo produces an ITypeInfo pointer describing the referenced type.  When presented with a new ITypeInfo pointer, it's a good idea to retrieve the TYPEATTR structure.  This indicates not just the number of variables or functions, but the TYPEKIND of the type description.  If the type kind indicates a union, structure, enum, or typedef, you need to hack at yet another TYPEATTR structure.  That's just great.  Is there at least an easy way to retrieve the name of the described type?  ITypeInfo::GetNames needs a MEMBERID, which we don't have (the MEMBERID is literally for members of the type - methods and values).  ITypeInfo::GetDocumentation also needs a MEMBERID.  ITypeLib::GetDocumentation only needs the library's index of the type, which we can acquire with a call to ITypeInfo::GetContainingTypeLib.  While the ITypeInfo::GetContainingTypeLib / ITypeLib::GetDocumentation pair will retrieve our type's name, it's too much work.  The mad programmer helped us out a bit here - we can pass -1 as the MEMBERID to ITypeInfo::GetDocumentation to procure our type's name, help string, and help file context.  What a great time!

Hopefully you have a sense of the flexibility of COM Type Information; items of arbitrary complexity can be defined, but at the cost of increased programmer aggravation.  We've covered the ELEMDESC structure pretty thoroughly now.   Let's finish our program for displaying the enumerated types of a type library.  "But wait!" you protest.  Can enums in a COM type library be of any type allowed by the TYPEDESC structure?  Actually, no.  As the MIDL documentation explains, "Objects of type enum are int types, and their size is system-dependent."  So why would I go through all the trouble of recursing around TYPEDESC when all I'll come up with is an int?  The ultimate goal of the programmer is to write as little code as he possibly can.  If one function can serve many purposes, he's done a good job with that function.  The information we get from TYPEDESC is nontrivial for unions, structures, typedefs, and (disp)interface methods.  This procedure will be invoked from the function to process VARDESC structures (which describe unions, structures, and enums) as well.  Given just a VARDESC pointer, there is no way to determine if you are working with a type description for an enum, structure, or union.  Therefore, we call the procedure for processing TYPEDESCs for all three variable types.  It just makes for cleaner, more reusable code.

Let's write our program for displaying all the enumerated types (and their values) in a type library.  The amazing part about this code is that with only a trivial change, you can adapt the code to display the structures or unions in a type library instead.  I'll make comments about the source after every function.

#include <windows.h>
#include <atlbase.h>
#include <iostream>
#include <sstream>

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;
}

The stringifyCustomType function is very simple (just the way I like it).  Passed an HREFTYPE and an ITypeInfo pointer, stringifyCustomType invokes ITypeInfo::GetRefTypeInfo to procure the type description of the custom type.  ITypeInfo::GetDocumentation on this type description is passed -1, specifying that the name of the type is wanted.   The result is returned as an std::string.  The next function does a significant amount of work, and is probably bugged as hell :-O

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!";
}

Holy Moly.  A recursive function.  And one that doesn't just compute 7 factorial.

Passing stringifyTypeDesc a TYPEDESC pointer and corresponding ITypeInfo pointer returns the stringified name of the type.   If the VARTYPE of the TYPEDESC is VT_PTR, the function grabs stringifyTypeDesc(typeDesc->lptdesc, pTypeInfo) and appends an asterisk.  For example, if typeDesc.vt equals VT_PTR and typeDesc->lptdesc.vt equals VT_BSTR, the function returns "BSTR*."  The SAFEARRAY case is similar.  The stringified name of typeDesc->lptdesc is returned between the paranthesis in "SAFEARRAY()." 

The C array scenario is different, but no more complicated.  typeDesc->lpadesc->tdescElem is stringified and for each dimension, a bracket pair containing the domain of elements in that dimension is appended.  Notice that the printed range of elements is inclusive.   For example, if the lower bound of a dimension was 6, and the number of elements was 4, the bracket would look like "[6...9]."  The four elements starting at 6 (6, 7, 8, and 9) constitute the domain of the array in that dimension.

Both the VT_USERDEFINED case and the VARIANT cases cause this recursive function to begin unwinding.  VT_USERDEFINED simply returns stringifyCustomType as defined above.  The big VARIANT switch (a very common and goofy looking creature) returns the string representation of the contained VARTYPE.  If typeDesc.vt is none of the cases I've coded for, "BIG ERROR!" is returned.  As time progresses, more types may be added to VARTYPE (although I seriously doubt it - enums should be immutable like interfaces), and "BIG ERROR!" draws attention to the fact that you are using an out of date client.

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();
}

stringifyVarDesc returns the complete string of the enum value, structure value, or union value in question.  If the value is const, "const" is printed before the return value of stringifyTypeDesc.  The value name, produced by ITypeInfo::GetDocumentation, is appended.  If the value is const, an equal sign is added, and VariantChangeType is used to coerce varDesc->lpvarValue to a BSTR, which becomes the right-hand side of the equation.  The string is then returned to the caller.

With all this work done, shall we test the code?  First we will output a list of all the enums and their values.

#include <windows.h>
#include <atlbase.h>
#include <iostream>
#include <sstream>

// print out all the enums and their values in a type library

void main() {
    HRESULT hr(CoInitialize(0));
    if(hr) { std::cout<< "No COM... This is embarassing.\n\n";
return; }
   
    CComPtr<ITypeLib> pTypeLib;
    hr = LoadTypeLib(L"C:\\Windows\\System\\msdxm.ocx", &pTypeLib);
   
if(hr) { std::cout<< "Couldn't load the library :(\n\n"; return; }
   
    UINT infoCount(pTypeLib->GetTypeInfoCount());
   
for(UINT curEnum(0); curEnum < infoCount; ++curEnum) {
        TYPEKIND typeKind;
        hr = pTypeLib->GetTypeInfoType(curEnum, &typeKind);
       
if(hr || typeKind != TKIND_ENUM) continue;
       
        CComPtr<ITypeInfo> pTypeInfo;
        hr = pTypeLib->GetTypeInfo(curEnum, &pTypeInfo);
       
if(hr) { std::cout<< "Failure getting type info\n\n"; return; }
       
        TYPEATTR* typeAttr;
        hr = pTypeInfo->GetTypeAttr(&typeAttr);
       
if(hr) { std::cout<< "Failure getting type attr\n\n"; return; }
        CComBSTR name;
        hr = pTypeInfo->GetDocumentation(-1, &name, 0, 0, 0);
        std::wcout<< name.m_str<< L"\n";       
           
        for(UINT curValue(0); curValue < typeAttr->cVars; ++curValue) {
            VARDESC* varDesc;
            hr = pTypeInfo->GetVarDesc(curValue, &varDesc);
           
if(hr) { std::cout<< "Failure getting var desc\n\n"; return; }
            std::cout<< '\t'<< stringifyVarDesc(varDesc, pTypeInfo)<< '\n';
            pTypeInfo->ReleaseVarDesc(varDesc);            
        }
        pTypeInfo->ReleaseTypeAttr(typeAttr);
    }
}

Again, we are loading the Windows Media Player type library (although any type library with enums would suffice).  After we confirm that the type library index refers to an enum (with ITypeLib::GetTypeInfoType), we ITypeLib::GetTypeInfo to procure the appropriate type description interface pointer.  Next we print the name of the enum (ITypeInfo::GetDocumentation, passing -1).  We retrieve the TYPEATTR of the enum, and loop through all values of the type, each time passing the ITypeInfo::GetVarDesc produced structure to stringifyVarDesc and outputting the result.  Here is a screenshot (the "-- More --" prompt is due to the |more filter):

enumres2.gif (7716 bytes)

Great.  It worked great.  I promised that with just a single modification, this code would also display all the structures in a type library, as well as all the enums.  Well, here you go.  It's the same source but with the check for TKIND_ENUM replaced by TKIND_RECORD (record and struct are interchangeable terms in Automation).

#include <windows.h>
#include <atlbase.h>
#include <iostream>
#include <sstream>

// print out all the structs and their elements in a type library

void main() {
    HRESULT hr(CoInitialize(0));
   
if(hr) { std::cout<< "No COM... This is embarassing.\n\n"; return; }
   
    CComPtr<ITypeLib> pTypeLib;
    hr = LoadTypeLib(L"C:\\Windows\\System\\dx3j.dll", &pTypeLib);
   
if(hr) { std::cout<< "Couldn't load the library :(\n\n"; return; }
   
    UINT infoCount(pTypeLib->GetTypeInfoCount());
    for(UINT curStruct(0); curStruct < infoCount; ++curStruct) {
        TYPEKIND typeKind;
        hr = pTypeLib->GetTypeInfoType(curStruct, &typeKind);
       
if(hr || typeKind != TKIND_RECORD) continue;    // this is the only

                                                         // non-trivial change!
        CComPtr<ITypeInfo> pTypeInfo;
        hr = pTypeLib->GetTypeInfo(curStruct, &pTypeInfo);
       
if(hr) { std::cout<< "Failure getting type info\n\n"; return; }
       
        TYPEATTR* typeAttr;
        hr = pTypeInfo->GetTypeAttr(&typeAttr);
       
if(hr) { std::cout<< "Failure getting type attr\n\n"; return; }
        CComBSTR name;
        hr = pTypeInfo->GetDocumentation(-1, &name, 0, 0, 0);
        std::wcout<< name.m_str<< L"\n";       
           
       
for(UINT curValue(0); curValue < typeAttr->cVars; ++curValue) {
            VARDESC* varDesc;
            hr = pTypeInfo->GetVarDesc(curValue, &varDesc);
           
if(hr) { std::cout<< "Failure getting var desc\n\n"; return; }
            std::cout<< '\t'<< stringifyVarDesc(varDesc, pTypeInfo)<< '\n';
            pTypeInfo->ReleaseVarDesc(varDesc);            
        }
        pTypeInfo->ReleaseTypeAttr(typeAttr);
    }
}

The specified file is the Direct X 3 for Java distributable.  Goofy, I know, but it has a lot of structures:

struct1.gif (4546 bytes)

Compiling the program and testing for TKIND_ALIAS will display all the unions in the type library (I'll spare you the screenshot).  This is pretty useful code.  Let's try to display all the typedefs in a type library. 

#include <windows.h>
#include <atlbase.h>
#include <iostream>
#include <sstream>

// print out all the typedefs in a type library

void main() {
    HRESULT hr(CoInitialize(0));
    if(hr) { std::cout<< "No COM... This is embarassing.\n\n"; return; }
   
    CComPtr<ITypeLib> pTypeLib;
    hr = LoadTypeLib(L"D:\\Program Files\\Visio\\VisLib32.dll", &pTypeLib);
   
if(hr) { std::cout<< "Couldn't load the library :(\n\n"; return; }
   
    UINT infoCount(pTypeLib->GetTypeInfoCount());
    for(UINT curTypedef(0); curTypedef < infoCount; ++curTypedef) {
        TYPEKIND typeKind;
        hr = pTypeLib->GetTypeInfoType(curTypedef, &typeKind);
       
if(hr || typeKind != TKIND_ALIAS) continue;
       
        CComPtr<ITypeInfo> pTypeInfo;
        hr = pTypeLib->GetTypeInfo(curTypedef, &pTypeInfo);
       
if(hr) { std::cout<< "Failure getting type info\n\n"; return; }
       
        TYPEATTR* typeAttr;
        hr = pTypeInfo->GetTypeAttr(&typeAttr);
       
if(hr) { std::cout<< "Failure getting type attr\n\n"; return; }
       
        std::cout<< "typedef "<<
            stringifyTypeDesc(&typeAttr->tdescAlias, pTypeInfo)<< ' ';

        CComBSTR name;
        hr = pTypeInfo->GetDocumentation(-1, &name, 0, 0, 0);

        if(hr) std::cout<< "UnknownTypedefName\n";
        else std::wcout<< name.m_str<< L"\n";       
        pTypeInfo->ReleaseTypeAttr(typeAttr);
    }
}

The TKIND_ALIAS type kind indicates description of a typedef.  The name of the typedef is the name of the type (acquired through GetDocumentation), and the alias type is TYPEATTR::tdescAlias.  We sent this TYPEDESC instance through stringifyTypeDesc to produce the stringified alias.  The following screen shot displays the typedefs of the Visio 5.0 type library.

typedef1.gif (8965 bytes)

We've now covered the variable types: structure, union, enum, and typedef.  It's now time to get back to our original problem; that is, how to enumerate the methods and properties exposed by an interface.

The COM type description API includes two jumbo structures for retrieving information.  We've met one already: VARDESC.  The other structure is called FUNCDESC and is used to retrieve information about COM methods. 

from OAIDL.IDL

typedef struct tagFUNCDESC {
    MEMBERID memid;
    [size_is(cScodes)] SCODE * lprgscode;
    [
size_is(cParams)] ELEMDESC * lprgelemdescParam; /* array of param types */
    FUNCKIND funckind;
    INVOKEKIND invkind;
    CALLCONV callconv;
    SHORT cParams;
    SHORT cParamsOpt;
    SHORT oVft;
    SHORT cScodes;
    ELEMDESC elemdescFunc;
    WORD wFuncFlags;
} FUNCDESC, * LPFUNCDESC;

With an ITypeInfo pointer describing an interface or dispinterface, a FUNCDESC structure can be acquired with the ITypeInfo::GetFuncDesc method.  Like VARDESCs and TYPEATTRs, FUNCDESCs need to be explicitly released with the method ITypeInfo::ReleaseFuncDesc.  An interface is a group of methods, so each interface type description exposes one or more FUNCDESCs.  Invoke ITypeInfo::GetTypeAttr to retrieve an TYPEATTR structure; TYPEATTR::cFuncs is the number of methods the interface supports, so passing an index between 0 and TYPEATTR::cFuncs - 1 to ITypeInfo::GetFuncDesc retrieves the appropriate function description.

FUNCDESC::memid is the DISPID of the method.  IDispatch::GetIDsOfNames often delegates to ITypeInfo::GetIDsOfNames, which returns this very value upon a match.  FUNCDESC::invkind holds a value of the INVOKEKIND enumerator.  While the values are single bits, they are not flags.  If a single method has a INVOKE_PROPERTYGET, INVOKE_PROPERTYPUT, and INVOKE_PROPERTYPUTREF version, each one will have its own index in the type description.  The FUNCKIND and CALLCONV elements should not cause anyone much grief: the function kind is determined by the type of interface (dispatch or virtual), as is indicated by TYPEATTR::typekind; if the calling convention is not CC_STDCALL, the object implementor has already been excommunicated from the Church of COM and put to death by means of trash compactor.

FUNCDESC::wFuncFlags holds a value of the FUNCFLAGS enumerator.  These flags control how a method should be presented to a type browser.  While these attributes have some value, the most important attributes are set at an interface-wide level; check TYPEATTR::wTypeFlagsFUNCDESC::cScodes specifies the number of permitted return values.  The actual return codes are stored in the lprgscode array.  An important property about dispatch and dual interface methods is that they should not return custom error codes.  If a method fails, a COM exception should be set through the EXCEPINFO parameter of IDispatch::InvokecScodes will almost always be zero, and the corresponding array empty.  FUNCDESC::oVft specifies the function's offset in the virtual table, for those Automation controllers implementing their own vcall thunks.  Naturally, only depend on this value for TKIND_INTERFACE methods.

There are three very important elements in FUNCDESC: elemdescFunc, cParams, and lprgelemdescParamelemdescFunc is an ELEMDESC instance holding the return value type.  We can pass elemdescFunc.tdesc to our method stringifyTypeDesc (presented above) to acquire a string representation.  For TKIND_DISPATCH methods, this is not the actual return value, but the parameter with the [retval] attribute.  For TKIND_INTERFACE methods, this is the actual return value, usually HRESULT.

FUNCDESC::cParams specifies the number of parameters the function takes (cParamsOpt specifies the number of optional parameters, but as we'll see in a bit, this is just a convienence).  Indexing from 0 to cParams - 1 into the lprgelemdescParam array gives the ELEMDESC instance for the appropriately ordered parameter.  We could just feed lprgelemdescParam[i].tdesc to our stringifyTypeDesc procedure, but we'd be missing out on so much important information.

from OAIDL.IDL

typedef struct tagELEMDESC {
    TYPEDESC tdesc;                      /* the type of the element */
    PARAMDESC paramdesc;                 /* IDLDESC is a subset of PARAMDESC */
} ELEMDESC;

typedef struct tagPARAMDESC {
    LPPARAMDESCEX pparamdescex;          /* valid if PARAMFLAG_FHASDEFAULT bit is set */
    USHORT wParamFlags;                  /* IN, OUT, etc */
} PARAMDESC, * LPPARAMDESC;

typedef struct tagPARAMDESCEX {
    ULONG cBytes;                        /* size of this structure */
    VARIANTARG varDefaultValue;          /* default value of this parameter */
} PARAMDESCEX, * LPPARAMDESCEX;

const USHORT PARAMFLAG_NONE = 0x00;
const USHORT PARAMFLAG_FIN = 0x01;
const USHORT PARAMFLAG_FOUT = 0x02;
const USHORT PARAMFLAG_FLCID = 0x04;
const USHORT PARAMFLAG_FRETVAL = 0x08;
const USHORT PARAMFLAG_FOPT = 0x10;
const USHORT PARAMFLAG_FHASDEFAULT = 0x20;
const USHORT PARAMFLAG_FHASCUSTDATA = 0x40;

The TYPEDESC is only half of the ELEMDESC structure.  We touched on the PARAMDESC element a few pages back, but now it's time to hammer on it.  PARAMDESC::wParamFlags holds the PARAMFLAG_XXXX values.  These translate almost directly to similarly named MIDL parameter attributes.  Notice PARAMFLAG_FOPT - this indicator of an optional function is far more helpful than FUNCDESC::cParamsOpt count (who cares about how many; I'd rather know which ones).  The PARAMFLAG_FRETVAL attribute is rather interesting; you'll only see it on TKIND_INTERFACE methods.  Dispatch interface methods move the [retval] parameter to the FUNCDESC::elemdescFunc item. 

If the PARAMFLAG_FOPT and PARAMFLAG_FHASDEFAULT attributes are defined, your parameter has a default value.  In this case, PARAMDESC::pparamdescex is nonzero, pointing to a PARAMDESCEX instance.  PARAMDESCEX::varDefaultValue is your parameter's default value.  Because parameters can be passed by reference (indicated by the VT_BYREF flag), a VARIANTARG structure is included in PARAMDESCEX structure.

Shall we complete our assignment and display all the methods exposed by the default interface of a COM object through Type Information?

#include <windows.h>
#include <atlbase.h>
#include <iostream>
#include <sstream>

// print out all the methods in the default interface

void main() {
    HRESULT hr(CoInitialize(0));
    if(hr) { std::cout<< "No COM... This is embarassing.\n\n"; return; }
    CLSID clsid;
    hr = CLSIDFromString(L"Shell.Explorer", &clsid);
   
if(hr) { std::cout<< "Can't find the CLSID in the registry\n\n"; return; }
    CComPtr<IDispatch> pSlider;
    hr = CoCreateInstance(clsid, 0, CLSCTX_INPROC_SERVER | CLSCTX_LOCAL_SERVER,
        IID_IDispatch, (void**)&pSlider);
   
if(hr) { std::cout<< "Can't CoCreateInstance the object\n\n"; return; }   
    CComPtr<ITypeInfo> pTypeInfo;
    hr = pSlider->GetTypeInfo(0, 0, &pTypeInfo);
   
if(hr) { std::cout<< "Object doesn't supply type info\n\n"; return; }
    TYPEATTR* typeAttr;
    pTypeInfo->GetTypeAttr(&typeAttr);
    CComBSTR interfaceName;
    hr = pTypeInfo->GetDocumentation(-1, &interfaceName, 0, 0, 0);
   
if(hr) std::cout<< "Unknown default interface:\n";
    else std::wcout<< interfaceName.m_str<< L":\n";
    for(UINT curFunc(0); curFunc < typeAttr->cFuncs; ++curFunc) {
        FUNCDESC* funcDesc;
        hr = pTypeInfo->GetFuncDesc(curFunc, &funcDesc);
        CComBSTR methodName;
        hr |= pTypeInfo->GetDocumentation(funcDesc->memid, &methodName, 0, 0, 0);
       
if(hr) { std::cout<< "Error In Name\n";

            pTypeInfo->ReleaseFuncDesc(funcDesc); continue; }
       
        std::cout<< '\t'<< stringifyTypeDesc(&funcDesc->elemdescFunc.tdesc,
            pTypeInfo)<< ' ';
        std::wcout<< methodName.m_str<< L"(";
       
for(UINT curParam(0); curParam < funcDesc->cParams; ++curParam) {
            std::cout<< stringifyTypeDesc(
                &funcDesc->lprgelemdescParam[curParam].tdesc, pTypeInfo);
           
if(curParam < funcDesc->cParams - 1) std::cout<< ", ";
        }
        std::cout<< ')';
        switch(funcDesc->invkind) {
        case INVOKE_PROPERTYGET: std::cout<< " propget"; break;
       
case INVOKE_PROPERTYPUT: std::cout<< " propput"; break;
       
case INVOKE_PROPERTYPUTREF: std::cout<< " propputref"; break;
        }
        std::cout<< '\n';
        pTypeInfo->ReleaseFuncDesc(funcDesc);
    }
    pTypeInfo->ReleaseTypeAttr(typeAttr);
}

Here we ouput the methods (and their parameters) of a default interface.  Funny that Shell.Explorer's default interface is IWebBrowser2.  Apparently no one at the Department of Justice has ever written their own COM Type Info browser.  After grabbing the FUNCDESC structure from invoking ITypeInfo::GetFuncDesc on our default interface's type description, we output stringifyTypeDesc on FUNCDESC::elemdescFunc.tdesc.  Calling ITypeInfo::GetDocumentation on a method's MEMBERID (the DISPID) retrieves the name of the method.  ITypeInfo::GetNames will also serve this purpose.  We output this name, and then enter a for loop, invoking stringifyTypeDesc on each parameter's ELEMDESC::tdesc.  Finally, we append the invoke kind to the end of the method (INVOKE_FUNC is implicit).

methods1.gif (10813 bytes)

By now you have a good understanding of the COM type description interfaces.  You can literally walk down the street, and laugh at every person you see saying "I know the COM type description interfaces better than you" and be right.  Although, don't head for the door yet!  There is one last lesson: the coclass.

The coclass is the most important item encoded in type libraries.  Without instantiatable types, COM is worthless.  Think back to the TYPEATTR definition.  For an interface type description, TYPEATTR::cImplTypes held the number of immediate base interfaces.  The ITypeInfo::GetRefTypeOfImplType method was invoked to resolve the HREFTYPE of the base interface.  The HREFTYPE was then fed to ITypeInfo::GetRefTypeInfo, which produced a new ITypeInfo pointer, this one referring to the base interface.  Why do i bring this up now?  The exact same mechanism is used to acquire implemented interfaces of coclasses.  Check out this program that displays a type library's coclasses and their interfaces.

#include <windows.h>
#include <atlbase.h>
#include <iostream>
#include <sstream>

// print out all the coclasses and their interfaces

void main() {
    HRESULT hr(CoInitialize(0));
    if(hr) { std::cout<< "No COM... This is embarassing.\n\n"; return; }
    CComPtr<ITypeLib> pTypeLib;
    hr = LoadTypeLib(L"C:\\Windows\\System\\Shdocvw.dll", &pTypeLib);
   
if(hr) { std::cout<< "Couldn't load type lib *cry*\n\n"; return; }
    UINT infoCount(pTypeLib->GetTypeInfoCount());
    for(UINT curCoClass(0); curCoClass < infoCount; ++curCoClass) {
        TYPEKIND typeKind;
        hr = pTypeLib->GetTypeInfoType(curCoClass, &typeKind);
       
if(hr || typeKind != TKIND_COCLASS) continue;
        CComBSTR coClassName;
        hr = pTypeLib->GetDocumentation(curCoClass, &coClassName, 0, 0, 0);
       
if(hr) continue;
        std::wcout<< coClassName.m_str<< L":\n";
        CComPtr<ITypeInfo> pTypeInfo;
        hr = pTypeLib->GetTypeInfo(curCoClass, &pTypeInfo);
       
if(hr) { std::cout<< "\tProblem with CoClass\n\n"; continue; }
        TYPEATTR* typeAttr;
        hr = pTypeInfo->GetTypeAttr(&typeAttr);
       
if(hr) { std::cout<< "\tCouldn't get TYPEATTR\n\n"; continue; }
       
for(UINT curInterface(0); curInterface < typeAttr->cImplTypes; ++curInterface) {
            HREFTYPE hRefType;
            hr = pTypeInfo->GetRefTypeOfImplType(curInterface, &hRefType);
           
if(hr) continue;
            CComPtr<ITypeInfo> pInterfaceInfo;
            hr = pTypeInfo->GetRefTypeInfo(hRefType, &pInterfaceInfo);
           
if(hr) continue;
            CComBSTR interfaceName;
            hr = pInterfaceInfo->GetDocumentation(-1, &interfaceName, 0, 0, 0);

            if(hr) std::cout<< "ErrorInInterfaceName\n";
            else std::wcout<< L"\t"<< interfaceName.m_str<< L"\n";
        }
        pTypeInfo->ReleaseTypeAttr(typeAttr);
        std::cout<< '\n';
    }
}

Wow.  That was really easy.  Just like grabbing base interfaces.   We are browing the internet controls type library.  Let's see what we've got:

coclass1.gif (4540 bytes)

Very interesting...  Each coclass exposes two interfaces, and two source interfaces (the 'D' prefix generally signifies that the interface is a dispatch only interface, and therefore a source interface to be connected to through IConnectionPointContainer).  Well, now we've covered coclasses, (disp)interfaces, unions, structures, and typedefs.  That pretty much does it for the COM type description interfaces.  Now go forth and write your own Automation controller!   (After reading Part III of this article of course - we build our own GUI type browser.)

Part III



[COMMENTS]

Copyright © Sean BAXTER - http://ript.net/~spec/