This article may contain URLs that were valid when originally published, but now link to sites or pages that no longer exist. To maintain the flow of the article, we've left these URLs in the text, but disabled the links.
Visual Basic: Inspect COM Components Using the TypeLib Information Object Library
|This article assumes youï¿½re familiar with COM and Visual Basic|
|Level of Difficulty 1 2 3 |
|Download the code for this article: Fisher.exe (37KB) |
|Browse the code for this article at Code Center: TypeLib |
|SUMMARY The built-in Object Browser in Visual Basic, like other component browsers such as OLE View and XRay, is one of the more useful tools. But an even better tool would be customizable. You can build your own type library browser using the TypeLib Information Object Library (TLI), a set of COM objects designed to allow programmers to browse type libraries programmatically. This article explains type libraries and the TLI object model, and shows how to use the collections to get information about objects, leading to the creation of a custom type library explorer. |
| f you're anything like me, you probably spend as much time using the Object Browser as you do actually writing code in Visual BasicÂ®. This indispensable tool (shown in Figure 1) allows developers to peer inside any COM component with a Type Library, exploring its methods, properties, events, enums, and so on. While the IntelliSenseÂ® feature built into Visual Basic can help you remember properties and method parameters, the Object Browser takes this assistance one step further, depicting the relationship between objects, providing helpful descriptions, and more. It can also be extremely helpful in learning about unfamiliar components. Of course, this requires you to launch Visual Basic, open an existing project or create a new one (since the Object Browser is otherwise unavailable), set a reference to the type library you're interested in, and then activate the Object Browser.|
Figure 1 Visual Basic Object Browser
What if you could access type libraries directly, without going through all of this? This is where the TypeLib Information Object Library (TLI) comes in. This component, implemented in TlbInf32.dll, is a set of COM objects designed to allow programmers to browse type libraries programmatically. In fact, the Visual Basic built-in Object Browser itself uses this component and with a little bit of work, you can too.
You may be wondering why you'd ever want to develop a type library browser yourself when Microsoft has already provided several tools for this purpose. OLE View (in Visual StudioÂ® Enterprise Edition) and XRay (part of the Internet Information Server Resource Kit) are two examples. Apart from the Zen replyâ""Because I can!"â"the real answer to this question is that you might want to customize exactly how your particular type library browser functions. You might want to expose or hide more or less of the underlying complexity behind the type library. OLE View (see Figure 2), for example, can display the raw Interface Description Language (IDL) file and a mind-numbing level of detail.
Figure 2 OLE View Type Library Browser
XRay (see Figure 3), on the other hand, may be too simplistic for your purposes. You may want to generate customized reports about the contents of type libraries. You may even want to build a type library explorer that functions very much like the native Visual Basic Object Browser, with the simple requirement that it exist independently of Visual Basic. The browser I developed and am going to describe in this article follows this approach, but there are a great many other applications in which you might use the TypeLib Information component.
Figure 3 XRay Type Library Browser
A Word about Type Libraries Before I proceed with my discussion of the TypeLib Information Object Library, I first want to say a few words about what exactly a type library is, how it's created, and the like.
Type libraries are files that explicitly describe some or all of the contents of components. This includes information about the methods, properties, constants, and other members exposed by the component. Development tools such as Visual Basic make use of the information contained in the type library to help you, as a developer, access and use the component. In addition, type libraries provide a convenient way to include a simple level of descriptive documentation for component members.
If you develop COM components using Visual Basic, a type library is automatically created for you when you compile the component. In addition, you can create your own type libraries for your own components. There are a few different ways to do this, but for those who program in Visual Basic a favorite tool is MIDL. This tool allows you to compile IDL files into binary type libraries. In this way, you could, for example, build a type library to encapsulate and expose a set of API calls that you often use. Then, instead of using declare statements in your Visual Basic source code, all you would have to do is set a reference to your custom API type library.
An example of this kind of type library IDL file is shown in Figure 4. To compile this IDL file into a binary type library that you can browse with the Visual Basic Object Browser or with the TypeLib Information application provided with this article, save this file with an IDL extension and then simply call midl.exe, passing the file name as a parameter.
The TypeLib Information Object Library The TLI offers a convenient set of objects, methods, and properties with which to investigate type libraries programmatically. TLI is intended to be useful to programmers using Visual Basic or C++, and it has a relatively flat object model designed to allow bidirectional browsing. TLI exposes a number of different collections through which you can examine all members of a type library, or only members of a select kind (constants, for example). Take a look at the simplified TLI object model shown in Figure 5, and you'll see the straightforward hierarchical representation of a type library.
Although the architecture of the TLI component may look daunting, it's really fairly simple. Generally speaking, you begin by creating an instance of the TypeLibInfo object, either from an external type library file or from an entry in the system registry. From this object, you can simply traverse any of the various collections you're interested in exploring further. To begin an investigation of the TLI type library itself, for example, you would first instantiate a TypeLibInfo object and then open the type library, like this:
TLI provides two approaches to iterating through the entities exposed by a type library. The first and more general approach is to use the TypeInfos collection. This is a collection of TypeInfo objects, representing every available entity in the type library under investigation, without regard to the type of entity. Each TypeInfo object serves as a pointer to a specific object in the type library, whether it be a creatable class, a user-defined type (UDT), a hidden member, an inherited interface, or some other object. You could iterate through all of the available TypeInfos for a type library like this:
Dim tliTypeLibInfo as TLI.TypeLibInfo
Set tliTypeLibInfo = New TypLibInfo
tliTypeLibInfo.ContainingFile = "tlbinf32.dll"
Using this generalized approach to traverse the available objects in a type library is cumbersome because of the generic nature of the TypeInfo object. For this reason, TLI also exposes a more specialized subset of collections, each specifically narrowed to focus only on a particular type of entity. This specialization is based on the TypeKind property of each entity in the type library. In this schema, there are seven types of entities, the entire superset of which is encompassed in the TypeInfos collection, as you can see in Figure 5. Exploring a type library using these subsets is more straightforward, since certain assumptions can be made about the nature of each. To clarify this, let's take a look at the seven kinds of TypeInfos individually.
Dim tliTypeInfo as TypeInfo
For Each tliTypeInfo In tliTypeLibInfo.TypeInfos
The Seven TypeInfo Collections CoClasses is a collection of CoClassInfo objects that represent all creatable objects in the type library. In Visual Basic, these are classes that can be created using the New, CreateObject, or GetObject keywords. These TypeInfos are identified by a TypeKind value of TKIND_COCLASS. Iterating through only the creatable classes exposed in a type library is as simple as:
Constants, identified by a TypeKind of TKIND_ENUM or TKIND_MODULE, represent a collection of all the TypeInfos that contain constant values. These entities are enums, or groups of constants. The individual constant values are Members of each ConstantInfo object in the Constants collection.
Dim tliCoClassInfo as CoClassInfo
For Each tliCoClassInfo In tliTypeLibInfo.CoClasses
You'll encounter Declarations less often thanCoClasses or Constants. Each DeclarationInfo object, identified with a TypeKind of TKIND_MODULE, contains a function declaration which points directly to an external library rather than to a COM interface. Functions that reside in DLLs outside the type library live in this collection.
Note that although it is rare, TypeInfos of TypeKind TKIND_MODULE can be either Declarations or Constants. So how do you tell the difference? As with most TypeInfo objects, each DeclarationInfo and ConstantInfo object exposes a Members collection, which contains the individual constituent members of that object. Two properties of the Members collection serve as filters with which you can differentiate Declarations and Constants. The FuncFilter property is automatically set to a value of TRUE for Members derived from a ConstantInfo object of TypeKind TKIND_MODULE to exclude methods (Declarations). Conversely, the VarFilter property of the Members collection is set to TRUE for Members derived from a DeclarationInfo object of TypeKind TKIND_ MODULE. Just as the FuncFilter property excludes methods from the collection, the VarFilter property excludes constants and properties from the collection. In this way, the two species of TypeInfos can be differentiated.
Moving on, you'll see the Interfaces collection. This is a set of all interface definitions within the type library. An InterfaceInfo object is recognized by a TypeKind of TKIND_DISPATCH or TKIND_INTERFACE. Interfaces of TypeKind TKIND_DISPATCH support only the vTable associated with the IDispatch interface, whereas those of TypeKind TKIND_INTERFACE (including the dual interfaces created by default in Visual Basic) support their own vTables beyond IDispatch. Furthermore, the Interfaces collection contains all interfaces exposed by the type library, including event interfaces as well as the default interfaces of all CoClasses. Moreover, each InterfaceInfo object contains an ImpliedInterfaces collection, which can be used in a more or less recursive manner to traverse down through all levels of inherited interfaces in the type libraryâ"right down to IUnknown. For these reasons, the Interfaces collection is suitable only for very detailed queries and low-level type library operations. You will probably rarely have a use for the explicit Interfaces collection.
Recordsâ"TypeInfos with a TypeKind of TKIND_RECORDâ"are more commonly known as structures or UDTs. You will find that these TypeInfos are not particularly common in most of the type libraries you will likely encounter, so I will say no more about them here.
Likewise, Unions are TypeInfos of TypeKind TKIND_UNION. Unions are not supported in Visual Basic, and are consequently beyond the scope of this article.
The final subset is the IntrinsicAliases collection. IntrinsicAliasInfo objects have a TypeKind of TKIND_ALIAS and represent aliases to intrinsic data types. Most TypeInfos of TypeKind TKIND_ALIAS resolve to other underlying TypeInfos, and hence each of the preceding collections may contain TypeInfos with this TypeKind value; however, the explicit IntrinsicAliases collection contains only those TypeInfo objects that do not resolve to other TypeKinds.
A Type Library Explorer Example Now that I've gone through the theory, I'll explain how I developed a sample application so you can see how this all works in practice. I built a simple type library explorer that demonstrates several of the key principles of using TLI. In an overview such as this, it isn't possible to show every available feature in a component as complex as TLI, but my type library explorer application should serve as an effective jumping off point for your own fur-ther explorations.
As you can see in Figure 6, my application consists of three main regions. The listview at the top of the window presents general information about the type library I've loaded, including its external file name, its help file (if any), its globally unique identifier (GUID), its version, and so on. In the middle of the window, I have two listboxes that enumerate the TypeInfos and their corresponding Members respectively. When I select a TypeInfo in the lefthand listbox, the members exposed by that object are displayed in the righthand listbox. Finally, the bottom of the window displays information about individual entities. When I select an entry in either of the Members or TypeInfos listboxes, details about the selected entity, including the entity's membership, its help description, and its definition prototype are displayed in the Entity Documentation frame.
Figure 6 Type Library Explorer Sample App
Before any of this can happen, though, I first have to select and open a type library. The code in Figure 7 takes care of these tasks. Let's take a look at this code to see what's going on. The Form_Load procedure is quite straightforward. Apart from carrying out some very logical initialization, the only other thing happening here is that I instantiate the TypeLibInfo object from which all other type library explorations will proceed. In addition, the AppObjString property is set to a value of "<Global>". This allows all the classes in a selected type library which are auto-instantiated (that is, globally available without qualification in Visual Basic) to be retrieved.
The code that executes when the user selects Open from the File menu is equally uncomplicated (it's early yet!). First, I turned on inline error handling. Second, the Open dialog box solicits the user's selection of a type library. Then, assuming the user doesn't choose to cancel out of the Open dialog, I attempt to open the type library by setting the ContainingFile property of the TypeLibInfo object to the selected file name. Obviously, it is possible that the user may choose a file that doesn't contain any valid type library information. In this event, the error tliErrCantLoadLibrary is raised and helpful feedback is displayed. Otherwise, the ProcessTypeLibrary procedure executes.
The ProcessTypeLibrary function begins by changing the caption of the form to indicate the name of the loaded type library. Following this, a series of information entries is added to the listview at the top of the windows (as shown in Figure 6). This code is not terribly difficult to follow; I am simply making reference to several properties about the type library that are exposed by the TypeLibInfo object. With only minor massaging, the values of these properties are, for the most part, suitable for direct display. My next step is to clear the two listboxes in the center of the window, then populate the first of these with all the available TypeInfos in the loaded type library.
Although only a single line is responsible for loading the available TypeInfos into the corresponding listbox, it is a line worth probing in detail. Remember from my earlier discussion that I have basically two general ways to traverse the available TypeInfos available for a type libraryâ"I can use the generic TypeInfos collection or I can use individual and specialized subcollections such as CoClasses and Constants. There is actually another way to retrieve the collection of objects that I am interested in: I can call one of several GetTypesXxx methods of the TypeLibInfo object. In this case, I am using GetTypesDirect.
The GetTypesDirect method retrieves available TypeInfos of a specified TypeKind and dumps them directly into a listbox or combobox. The method takes three parameters. The first is a handle to the listbox or combobox window into which the TypeInfos need to be enumerated. The second is the window type: listbox or combobox. The third is the TypeKind I am interested in. So in this case, I've provided the hWnd of the TypeInfos listbox, lstTypeInfos. I don't need to specify the window type, since a listbox is the default, and I've indicated that I want all TypeInfos by passing the constant tliStAll.
In addition to dumping the TypeInfos into the listbox, GetTypesDirect also performs all the necessary type resolutions to work out the sometimes complex relationships between TypeKinds without any effort on my part. For example, Property Get and Property Let procedures are resolved into a single TypeInfo. This is quite handy for this sample application, although it may not be suitable to every application of TLI, because in some cases you may want to delve down to the naked roots of the type library. Another helpful benefit of the GetTypesDirect method is that it automatically loads each TypeInfo's SearchData property into the ItemData property of each entry in the listbox. Think of the SearchData property as a key to a particular TypeInfo object. I'll make use of this property when I need to retrieve the Members associated with the TypeInfo.
At this point, my application is running, displaying general type library information in the top portion of the window, dutifully showing a list of available TypeInfos (conveniently resolved into familiar Visual Basic terms), and waiting for the user's next action. Logically, this next action will be to select an item from the TypeInfos listbox and then to choose an item from that TypeInfo's corresponding Members in the other listbox. Code for these two event procedures is shown in Figure 8.
The Real Work Begins When the user selects a TypeInfo in lstTypeInfos, I have two tasks to accomplish. First, I use the GetTypeInfo method of the global TypeLibInfo to get a specific TypeInfo object for the selected entry in the list. GetTypeInfo takes as its sole parameter an Index to the desired TypeInfo. This Index corresponds to either the Name or TypeInfoNumber property of the TypeInfo. In this case, I am using the name. A caveat: although it isn't extremely common, multiple TypeInfos of different TypeKinds can have the same name, so this approach may not always be suitable. For my purposes, however, it will do just fine. Once the TypeInfo object is retrieved, I can populate the informational labels in the Entity Documentation frame at the bottom of the window. For the name, I simply display the selected item from the listview. For the TypeInfo's membership, all I need to do is reference its Parent property. Showing the TypeInfo's helpful description is as simple as displaying the HelpString property. Finally, to get an entity prototype for a TypeInfo, I set the value of the corresponding textbox to a blank string.
The second thing I need to do is populate the lstMembers listbox with all the Members exposed by the selected TypeInfo. I do this in much the same way I obtained the TypeInfos in the first place, this time using the similar GetMembersDirect method. The SearchData property is passed to the GetMembersDirect method for the chosen TypeInfo (remember, this was automatically stored in the listbox's ItemData property array) along with the handle to the lstMembers listbox window. With that, TLI loads the available Members into the listbox.
When a Member is selected from the lstMembers listbox, the goal is similar, but much more complicated. Here, as with the TypeInfos listbox, I want to provide information about the user's selected entity, but this time I need to derive a prototype string for the Member. This proves to be a methodical, if intricate, process. The first step is to capture the entity name and InvokeKinds property value. I get its name from the selected entry in the listbox. Like the SearchData property for the collection of TypeInfos, the InvokeKinds property for the retrieved Members is placed in the ItemData property array automatically by GetMembersDirect. These two settings are passed to the PrototypeMember procedure (see Figure 9), where the real work is done.
The PrototypeMember function, along with its two helper functions, ProduceDefaultValue and GetSearchType (not shown), is an adaptation of sample code provided by Microsoft with the TLI documentation. Let's take a look at it, hitting just the high points, to see how to develop a member prototype string suited to Visual Basic. This is one of the more useful and informative tasks you can accomplish with TLI, and you will probably use it again and again in your own applications.
As you can see in Figure 9, I first declare a host of variables that the function will use internally. Many of these are Booleans whose purpose is to keep track of where I am in the construction of my prototype string and what sort of parameters and return values I am dealing with.
Next, I must determine what sort of TypeInfo the selected member belongs toâ"constant, class, and so on. I make this determination by performing a bit of math on the SearchData property (from the ItemData property array in lstTypeInfos) and then calling GetMemberInfo to retrieve an explicit MemberInfo object corresponding to the member selected in the lstMembers listbox. Depending upon whether it's a constant or class and whether (if the latter) it returns a value or doesn't, I begin my prototype string with the appropriate identifier, followed by the name of the MemberInfo object itself.
Next, I need to look at the MemberInfo's Parameters collection. If there are any Parameters, I open a parenthetical list in my prototype string. The next item of major consequence is the iteration through each of the ParameterInfo objects in the Parameters collection of the MemberInfo in question. For each ParameterInfo, I make further determinations regarding whether it's optional or has a default value for the proper formatting of the prototype output string.
Now, I need to determine the type of each parameter by using the VarTypeInfo property of the ParameterInfo object to resolve the parameter type into either a native type, such as integer, or into an external type. Following this, I determine whether the parameter is being passed ByVal or ByRef and indicate this in the prototype string. Next, I add the Name of the ParameterInfo object to the growing output string. The dozen or so lines that follow continue the tedious process of resolving the parameter type into an intrinsic, external, or unknown data type and adding this to the prototype string.
Following the processing of parameters, if the selected MemberInfo object is a member of an enum TypeInfo, I want to display that value. If not, I'll continue on to process and resolve the MemberInfo's ReturnType, if it has one. Just as I have done for each ParameterInfo object, I now carry out the tedious process of resolving the type as either intrinsic, external, or the unknown data type for the return parameter of the MemberInfo object.
Once I've finished with this multifaceted process that requires the careful analysis of data types, constants, default values, and optional parameters, I can set the function value PrototypeMember equal to the output string that has been constructed. This will be placed in a scrollable textbox in the bottom portion of my application window. At the same time, I set the descriptive help and membership labels according to properties discussed previously. And with that, I've generated the Entity Documentation for a MemberInfo object. The process is considerably more involved than it is for a TypeInfo object, but this is to be expected, given that the MemberInfo object represents quite a bit more information.
Conclusion Naturally, there are many more directions in which I could take my discussion of TLI, but my simple type library explorer application will hopefully open your eyes to the possibilities. I've basically reproduced the functionality of the Visual Basic Object Browser, and where I've sacrificed one or two bells and whistles for the sake of brevity, I've also added a few features not offered by the native Object Browser. This article only scratched the surface, but you now have the foundation you need to begin crawling through type libraries.
| For related articles see:|
Obtain Built-in Constant Values for an Office Application
For background information see:
Help Files for tlbinf32.dll
| Jason Fisher has been programming in one language or another for almost 20 years. He's currently the editor of Active Server Developer's Journal. Jason is working on a book on WMI that's due out next year from Apress. E-mail him at firstname.lastname@example.org.|
From the December 2000 issue of MSDN Magazine