Type information is a resource that both objects and clients of those objects can load directly. Objects usually load type information to give it to a client through an interface such as IProvideClassInfo, which we'll see later. In most cases, the client is the consumer of the type information because it is trying to learn about the object—trying to make sense of its owner's manual. If the object is not running, the client can load the library itself; if the object is running, the client can ask the object to do the honors.
There are four loading-related functions in the OLE API. The first two rely on an object's registry entries and assume that the caller knows the LIBID of the type library (which the caller can load from the object's TypeLib entry under its CLSID). The first, QueryPathOfRegTypeLib, returns the full path-name of a type library given a version number and a language ID (both of which must match what is in the library itself), the path being the concatenation of the DIR entry and the appropriate name under Win16 or Win32 (depending on the platform). The second, LoadRegTypeLib, takes the same arguments and calls QueryPathOfRegTypeLib internally to retrieve the name of the library. It then calls one of the other two loading functions that do not depend on the registry. These are LoadTypeLib and LoadTypeLibFromResource. LoadTypeLib loads the library given a filename, which is used if the library is in a stand-alone file or in a compound file. LoadTypeLibFromResource, on the other hand, extracts a library from a module's resources.
Both LoadTypeLib and LoadTypeLibFromResource also have one other nice side effect: they call the OLE API function RegisterTypeLib to patch up any problems in the registry for this library. In light of this, when a running object wants to load its own type library, as it usually does on behalf of clients, it attempts LoadRegTypeLib first. If that fails because of a corrupt registry, the object can load the library directly using one of the other functions as a backup. If that backup loading succeeds, OLE will automatically correct the registry entries so that the next LoadRegTypeLib call succeeds.7
If you read the documentation for the QueryPathRegTypeLib function, you'll be told that the string it returns is a system-allocated string called a BSTR, for "Basic STRing." This string type is used particularly in OLE Automation because it is compatible with the strings used in Basic-language OLE Automation controllers, such as Microsoft Visual Basic. This data type will be described in more detail in Chapter 14; all you need to know now is that there are a number of OLE API functions of the form Sys<action>String, such as SysAllocString and SysFreeString, that you use to manipulate BSTRs. These functions internally use the task allocator from CoGetMalloc and require, like everything else, that at least CoInitialize (if not OleInitialize) has been called. The most important function to remember now is SysFreeString, which is used to free any BSTR that comes back from a function you might call.
So now the big question: what does loading give you back to represent the loaded library? You can probably guess it: an interface pointer—specifically, a pointer to an interface called ITypeLib. In other words, loading a type library means loading a type library object and getting your first interface pointer to it. Through this interface, you can retrieve the library's attributes or navigate to individual elements in the library and retrieve their information. Whereas the library as a whole is handled by ITypeLib, each element is manipulated as a separate object through ITypeInfo. Through ITypeInfo, you can retrieve anything you can create through ICreateTypeInfo. A consequence of having different objects is that you have to always call Release through any of the pointers you obtain. Calling ITypeLib::Release effectively closes the type library file if all ITypeInfo pointers have also been released.
A simple representation of the relationship between the library, a few elements, and their interfaces is shown in Figure 3-3. You'll see that the ITypeInfo pointers for the interfaces and dispinterfaces that are specified as part of a coclass can be obtained either by navigating through the ITypeInfo interface for the coclass or by retrieving the ITypeInfo pointer for the IID directly.
Figure 3-3.
A type library has an ITypeLib interface through which you can navigate to ITypeInfo interfaces that represent the various parts of the library.
A practical use of type information outside the scope of an instantiated object is for things called type information browsers. Type information browsers can look inside type libraries and present the available objects, interfaces, methods, and properties to an end user. These browsers can be the basis for powerful environments that might use drop-down list boxes and drag and drop in their programming user interfaces, greatly reducing the amount of typing that a programmer must do manually. I have not seen this sort of tool at the time of writing this text; I hope to see such innovative work in the future. However, two tools in the OLE SDK provide some sort of browsing capability. The first is TIBROWSE, for which the source code is provided as a sample. (It is also in Microsoft Visual C++.) This is a pretty simple tool, but it gives you the idea. A more complete tool, for which sources might not be provided, is OLE2VIEW. This tool allows you to peek into type libraries, among other things, through its File/View Type Library command or by locating a type library in OLE2VIEW's display of the registry. OLE2VIEW will show you just about everything that's in a library and can be very useful as a browser.
However you want to make use of all the type information available (and you're free to use it in any way you see fit—for precompiled headers, for example), you'll use the ITypeLib and ITypeInfo interfaces to navigate through the information. The following sections describe these interfaces in more detail. As a prelude, though, note that much of this information is expressed through the different data structures described in Table 3-7, many of which are used in some capacity through ICreateTypeLib and ICreateTypeInfo. Only TLIBATTR and TYPEKIND are available through ITypeLib; all other data structures are available only through ITypeInfo.
Structure | Description |
TLIBATTR | Contains a type library's LIBID (GUID), LCID, version number, flags ("restricted" being the only one), and the target operating system (Win16, Win32, Mac). |
TYPEKIND | Enumerates the type of a particular element in the library: it could be TKIND_RECORD (typedef struct), TKIND_MODULE, TKIND_INTERFACE, TKIND_DISPATCH (dispinterface), TKIND_COCLASS, TKIND_ALIAS (typedef), TKIND_ENUM, or TKIND_UNION. This is very useful for filling in items such as tree lists with the contents of a type library.* |
TYPEATTR | Contains the attributes of any TYPEKIND element: GUID (IID, CLSID, etc.), LCID, member IDs of constructor and destructor methods (interface and dispinterface), size of an instance of the type, count of methods (dispinterface), count of properties, variables, and data members (interface and dispinterface, typedef), count of interfaces (for coclass), size of the vtable (interface and dispinterface), byte alignment, and version number. Also included are TYPEDESC, TYPEFLAGS, and IDLDESC fields. (See below.) |
TYPEDESC | A structure that describes the type of a variable or argument or the return type of a method. Contains a VARTYPE (see Chapter 14, "VARIANT and VARIANTARG Structures") and a union that can contain either a TYPEDESC, an ARRAYDESC, or an HREFTYPE. (See below.) Nested TYPEDESCs describe nested structures. |
ARRAYDESC | Describes an array of some type (TYPEDESC) with a specific number of dimensions and the bounds of each dimension. |
HREFTYPE | A handle (unsigned long) that identifies a TYPEDESC. |
TYPEFLAGS | An enumeration that contains TYPEFLAG_FAPPOBJECT, identifying a coclass with the appobject attribute; and TYPEFLAG_FCANCREATE, which indicates whether the function ITypeInfo::CreateInstance will work. See "The ITypeInfo Interface" on page 180. |
IDLDESC | A structure containing information used in marshaling an argument; at present, this specifies only IDLFLAGS and contains a reserved BSTR. |
IDLFLAGS | An enumeration that identifies an argument as having the in or the out attribute, both attributes, or neither attribute. |
ELEMDESC | A structure that describes a typedef enum using a TYPEDESC and an IDLDESC. |
FUNCDESC | A structure that describes a method, including its dispID, an array and count of legal return SCODEs, the type of function (FUNCKIND), flags (FUNCFLAGS), the invocation style (INVOKEKIND), the calling convention (CALLCONV), the number of total arguments, the number of optional arguments, an array of ELEMDESC structures for each argument, the ELEMDESC of the return type, and the offset of this method in a virtual function table. |
FUNCFLAGS | An enumeration that contains FUNCFLAG_FRESTRICTED, FUNCFLAG_FSOURCE, FUNCFLAG_FBINDABLE, FUNCFLAG_FREQUESTEDIT, FUNCFLAG_FDISPLAYBIND (bindable and displayed to user), and FUNCFLAG_FDEFAULTBIND (the method in an interface that best represents the object). |
FUNCKIND | An enumeration that describes the function as either FUNC_VIRTUAL (called by an offset in a vtable), FUNC_PUREVIRTUAL, FUNC_NONVIRTUAL (called by address and takes a this pointer), FUNC_STATIC (called by address and takes no this pointer), or FUNC_DISPATCH (member of a dispinterface called through IDispatch::Invoke). |
CALLCONV | An enumeration that identifies the calling convention as CC_CDECL, CC_PASCAL, CC_MACPASCAL, CC_STDCALL, or CC_SYSCALL. |
INVOKEKIND | An enumeration that identifies the types of operations available through IDispatch::Invoke: INVOKE_FUNC, INVOKE_PROPERTYGET, INVOKE_PROPERTYPUT, INVOKE_PROPERTYPUTREF. These map exactly to DISPATCH_* values. |
IMPLTYPE | An enumeration containing the implementation type of interface or dispinterface in a coclass: IMPLTYPE_FDEFAULT (default), IMPLTYPE_SOURCE (source), and IMPLTYPE_FRESTRICTED (restricted). |
VARDESC | A structure that describes a variable, argument, constant, or data member and contains a dispID (MEMBERID), an offset or the variable within an object instance or a VARIANT with the actual value of a constant, an ELEMDESC, a value from VARFLAGS, and a type from VARKIND. |
VARFLAGS | An enumeration that contains VARFLAG_FREADONLY, VARFLAG_FSOURCE, VARFLAG_FBINDABLE, VARFLAG_FREQUESTEDIT, VARFLAG_FDISPLAYBIND, and VARFLAG_FDEFAULTBIND. |
VARKIND | An enumeration identifying a kind of variable or constant: VAR_PERINSTANCE (where the offset into an object in VARDESC makes sense), VAR_CONST (where the VARIANT in the VARDESC makes sense), VAR_STATIC (a single-instance global), or VAR_DISPATCH (a property in a dispinterface). |
MEMBERID | Same as a dispID. |
* Using the Tree List control of Windows 95, you can easily imagine a very slick type library browser that would have top-level items with each of these names and a little + next to the name. If you clicked on the +, that item would expand to show the names of each individual element of that type, and the information you can get from ITypeInfo would allow further levels of expansion.
Table 3-7.
Data structures and enumerations obtainable through ITypeLib and ITypeInfo member functions. The descriptions in this table are not exhaustive; see the OLE Programmer's Reference, for complete details.
7 RegisterTypeLib can also be useful for development environments that can both create objects and run client code against them if only just to test the objects. The developer could create and register an object and its type library and then immediately run a client script that would use that object as any other. This would provide a nice level of integration between object development and testing especially if the way a developer specifies the object's interfaces would allow the tool to automatically write a fair amount of the test script. Now there's a tool I'd like to see! |