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.
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 6Type 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.
|