Any object model, to be taken seriously, must support something called dynamic type discovery, i.e., it must provide mechanisms that enable the inspection and interpretation of types by programs dynamically at runtime. With COM this service is provided through the use of Type-Libraries.
Type-Libraries are 'simple' binary files that contain in them almost everything that you would want to (and sometimes wouldn't want to) or even need to know about a given type. Ideally, the client program (i.e., the application that will make use of the services from the component), if it has access to the component's type-library, has in effect all the information that it needs to create and use the component. The type-library is the friendly tour guide who helps you find your way around in the component at runtime.
If you think about it, to be able to successfully use a component, you would need to know the following possible things about it.
In fact a component is defined by these pieces of information. The type-library is the place where you can put all this crud to provide a complete description of the component. In this article we will attempt to research and find out exactly how we are to go about extracting the information that we want from the type-library.
I have written a simple type-library browser that you may download at the sources section of this site. You could perhaps download and play around with it to get a feel of what we are attempting to do here. Even though the source for the program is available for download, I would not recommend that you look at it yet. You see, I would like you to retain sanity for a while longer (by when, hopefully, the fog would have begun to dissipate)!
Let us first try to gain a proper understanding of how the type-library is organised. The type-library, at the top-level, has the following bits of information (it has some more, but lets pretend its not there for now),
Figure 1 - The Type-Library
Each type-info object, in turn would provide complete information about that type.
So all we need to do is first query the library regarding the number of types stored
in it and then ask for the type-info object for each of those types. The functionality
of the type-library is defined by an interface called ITypeLib
. This
interface has a method called GetTypeInfoCount
that gets you the count
of types stored in the library. For each of those types, you would call GetTypeInfo
to be handed a pointer to an object implementing an interface called ITypeInfo
.
But I am getting ahead of myself here. Before we can do all that, obviously, we need to have loaded the library in the first place. Let us see how we can achieve that next.
There are two ways you can go about doing this. Typically, a type-library would simply be a file on the disk with a .tlb extension. It could also have been made a part of a DLL or an EXE as a resource (with a special resource identifier). The type-library may or may not be registered, meaning, information about its location, version information etc., may or may not have been recorded in the Windows registry. If it is recorded, then it is recorded under the HKEY_CLASSES_ROOT\\TypeLib key.
If you have explicit knowledge about the physical location of the type-library, then you
can use the LoadTypeLib
COM library function to load the library.
LoadTypeLib
returns a pointer to an object implementing the ITypeLib
interface through an output parameter. You can load the library like so,
ITypeLib *pTLib; HRESULT hr; hr = LoadTypeLib( L"C:\\File.tlb", &pTLib );
If it is successfully loaded, then pTLib will be a valid pointer to a COM object
implementing ITypeLib
.
Alternately, if you have the following information,
then you can use the LoadRegTypeLib
API to do the loading. Like this,
HRESULT hr; ITypeLib *pTLib; WORD wVerMajor, wVerMinor; LIBID libid; //get hold of the libid and the version somehow hr = LoadRegTypeLib( libid, wVerMajor, wVerMinor, LOCALE_SYSTEM_DEFAULT, &pTLib );
This function simply pokes the registry to find the physical location of the library
before loading it. You might ask, exactly how we are supposed to be in posession of
this information. Well, if you just happen to have it with you (ESP perhaps) then fine!
If you don't, then maybe you can manually look in the registry! Isn't that what
LoadRegTypeLib
does? Well, yes. But we'll talk more about this later.
Right now, lets move on and figure out what we can do with this pointer to the object
that implements ITypeLib
.
ITypeLib
Interface - Get the basics rightLike I said earlier, the type-library, at the top level gives you the following 4 pieces of information.
Let us now see, how we can get this data starting with the library name.
Retrieving the name of the library is not an immediately obvious operation. The
ITypeLib
interface has a method called GetDocumentation
to which
you can supply an index to retrieve the human readable name of the type. If you supply
the value -1
for the index, it gives you the name of the library itself!
Weird!
ITypeLib *pTLib; HRESULT hr; BSTR bstrLibName; //load the library somehow hr = pTLib->GetDocumentation( -1, &bstrLibName, NULL, NULL, NULL );
As you may have noticed, the name of the library is returned as a BSTR
. Once
you are done using the string you must free it by calling SysFreeString
.
By attributes, we are really talking about the following aspects of the type-library.
This information is provided to you through a structure called TLIBATTR
that
has been declared like so in oaidl.h,
typedef struct tagTLIBATTR { GUID guid; LCID lcid; SYSKIND syskind; WORD wMajorVerNum; WORD wMinorVerNum; WORD wLibFlags; } TLIBATTR;
SYSKIND
is an enumeration that has been declared in the same file like this,
typedef /* [v1_enum] */ enum tagSYSKIND { SYS_WIN16 = 0, SYS_WIN32 = SYS_WIN16 + 1, SYS_MAC = SYS_WIN32 + 1 } SYSKIND;
As you can see, it tells you something about the system on which the library was built. Now, why you would want to know this is anybody's guess (in other words, I don't know ;).
The wLibFlags
member can take values from the LIBFLAGS
enumeration.
So, how do we get this information? This is how,
ITypeLib *pTLib; TLIBATTR *pTLibAttr; HRESULT hr; //load the type-library somehow hr = pTLib->GetLibAttr( &pTLibAttr );
Note that, ITypeLib::GetLibAttr
takes a pointer to a pointer
to a TLIBATTR
object as its argument. In other words, the type-library
object allocates the memory for holding a TLIBATTR
. Once you are done using
the structure, you will release the memory held by it by calling
ITypeLib::ReleaseTLibAttr
. Like this,
pTLib->ReleaseTLibAttr( pTLibAttr );
This probably is the single most important piece of information that you can pull out of the type-library without which you wouldn't be able to proceed further. Retrieving it however is a delightfully trivial affair :).
ITypeLib *pTLib; HRESULT hr; UINT uiCount; //load the type-library somehow uiCount = pTLib->GetTypeInfoCount();
That's it! For once, we have a function that does what it sounds like it does. It gives you the count of the type-info objects in the library. Lets now see how we can use this to get hold of the type-info objects which of course is our ultimate aim in life!
ITypeInfo
InterfaceFrom here on, the level of complexity (or tediousness) begins to ascend. There aren't a lot of fans when it comes to making sense of the information stored in the type-library. If you ever wondered why, then this interface is the reason! We'll try to lay it bare however, one tiny step at a time, so that towards the end, you may actually find yourself appreciating it!
I had earlier indicated the kind of information that you can pull out of the type-library. Here is a more complete listing.
Each of these types (except typedefs), are represented in the type-library by separate type-info objects. Let us now see how we can go about enumerating all these different type-info objects.
You can run through (or enumerate) all the objects in the library using the count that is returned by ITypeLib::GetTypeInfoCount
. If all you want to do is find out what the type of a given object in the library is, then you can invoke ITypeLib::GetTypeInfoType
passing in the index of the object whose type you want to know. This method returns the type as an instance of the TYPEKIND
enumeration which has been declared in oaidl.h like this,
typedef /* [v1_enum] */ enum tagTYPEKIND { TKIND_ENUM = 0, TKIND_RECORD = TKIND_ENUM + 1, TKIND_MODULE = TKIND_RECORD + 1, TKIND_INTERFACE = TKIND_MODULE + 1, TKIND_DISPATCH = TKIND_INTERFACE + 1, TKIND_COCLASS = TKIND_DISPATCH + 1, TKIND_ALIAS = TKIND_COCLASS + 1, TKIND_UNION = TKIND_ALIAS + 1, TKIND_MAX = TKIND_UNION + 1 } TYPEKIND;
Let us write a little function that will take a TYPEKIND
object as its argument and return a string representation (an STL string, to be precise) of it so that we can use it later on whenever we need it.
string StringifyTypeKind( TYPEKIND tkind ) { switch( tkind ) { case TKIND_ENUM: return "Enum"; case TKIND_RECORD: return "Structure"; case TKIND_MODULE: return "Module"; case TKIND_INTERFACE: return "Interface"; case TKIND_DISPATCH: return "Dispinterface"; case TKIND_COCLASS: return "Coclass"; case TKIND_ALIAS: return "Typedef"; case TKIND_UNION: return "Union"; default: return "BAD BAD BAD"; } }
As you can see, this is a trivial little function that simply returns the type in a human readable form. What follows is a simple program that opens a type-library and prints out the type of all the objects in the library. Error checks have been removed for brevity.
#include <windows.h> #include <iostream> #include <string> using namespace std; string StringifyTypeKind( TYPEKIND tkind ); int main() { ITypeLib *pTLib; TYPEKIND typeKind; UINT uiCount; HRESULT hr; hr = CoInitialize( NULL ); hr = LoadTypeLib( L"C:\\File.tlb", &pTLib ); uiCount = pTLib->GetTypeInfoCount(); for( UINT i = 0 ; i < uiCount ; ++i ) { cout<<i + 1<<") "; hr = pTLib->GetTypeInfoType( i, &typeKind ); cout<<StringifyTypeKind( typeKind )<<endl; } pTLib->Release(); CoUninitialize(); return 0; }
Simple eh? Let us go one step further and wrench out the name of each of those types. You might remember that we invoked ITypeLib::GetDocumentation
in order to retrieve the name of the library itself. The same method can be used to retrieve the name of all the other types stored in the library simply by passing in the index of the type in the place of the -1
. The loop in the code given above has been modified and reproduced below with the modifications highlighted in bold.
BSTR bstrName; for( UINT i = 0 ; i < uiCount ; ++i ) { cout<<i + 1<<") "; hr = pTLib->GetDocumentation( i, &bstrName, NULL, NULL, NULL ); wcout<<bstrName<<L" - "; SysFreeString( bstrName ); hr = pTLib->GetTypeInfoType( i, &typeKind ); cout<<StringifyTypeKind( typeKind )<<endl; }
Since ITypeLib::GetDocumentation
returns the name of the type as a BSTR
we need to treat it as such. It is the caller's reponsibility to call SysFreeString
on the BSTR
returned by ITypeLib::GetDocumentation
.
Finally, getting a pointer to the actual type-info itself involves a rather simple call to ITypeLib
's GetTypeInfo
method. You hand in the index of the object whose type-info you want and it dutifully returns the pointer to you. Like this.
ITypeLib *pTLib; ITypeInfo *pTInfo; HRESULT hr; UINT index = 0; //get hold of a type-library somehow //initialize the index to something you want hr = pTLib->GetTypeInfo( index, &pTInfo ); if( SUCCEEDED( hr ) ) { //voila! you've got a handle on the beast! //do your stuff and get rid of it pTInfo->Release(); } pTLib->Release();
That's it! Now that we've got a pointer to the type-info object we can go right ahead and peer into the deep dark crevices of the type-library and find the truth that we all know is out there!
Now would be a good time for you to take a deep breath, summon all your extra sensory capabilities, do the magic dance etc., because from here on the ride begins to get a bit bumpy (yes, thats right, even more bumpy!).
Each type-info object can be thought of as containing the following three components
VARDESC
objects, and/or
FUNCDESC
objects
Figure 2 - The Type-Info Object
The type's attributes are handed to us through a structure called TYPEATTR
. There is a whole lot of information packed up in this structure and this is the first thing that you would want to pull out once you get the pointer to the type-info object. Among others, it tells you the exact type of the object that we are dealing with, i.e., TYPEATTR
has a member variable called typeKind
, which is an instance of the TYPEKIND
enumeration (that we got to know and love so much earlier), that tells you whether the type-info object describes an enumeration or a structure or a union or an interface and so on. This is the same value as returned by the GetTypeInfoType
method of the ITypeLib
interface.
You retrieve a pointer to the TYPEATTR
structure from the type-info object by invoking ITypeInfo::GetTypeAttr
. Like this.
ITypeInfo *pTInfo; TYPEATTR *pTAttr; HRESULT hr; //get hold of a pointer to a type-info object somehow hr = pTInfo->GetTypeAttr( &pTAttr ); //use the structure hr = pTInfo->ReleaseTypeAttr( pTAttr );
As you can see, GetTypeAttr
yields a pointer to a TYPEATTR
object. What this means is that, the memory for storing the TYPEATTR
object has been allocated by the object itself. Consequently once we are done using it, the job of releasing that memory is delegated to the object by calling ReleaseTypeAttr
.
If the type-info describes
TKIND_UNION
),
TKIND_RECORD
),
TKIND_ENUM
),
the rest of the type-info object then contains as many VARDESC
objects as indicated by the cVars
member of the TYPEATTR
structure.
If on the other hand, it describes
TKIND_INTERFACE
),
TKIND_DISPINTERFACE
),
the rest of the type-info object then contains as many FUNCDESC
objects as indicated by the cFuncs
member of the TYPEATTR
structure.