You can enter the exclusive world of type information if you know the magic words. Too many developers treat type information as a kind of black box that only the MIDL compiler knows how to create. It is both enlightening and interesting to investigate how MIDL goes about creating this information. A type library file created by MIDL is simply a collection of type information about the interfaces and coclasses supported by a component. You call the CreateTypeLib24 function to create a type library in the modern format5 and obtain a pointer to the system implementation of the ICreateTypeLib2 interface. The following code fragment creates a type library file named mylib.tlb:
// Create the type library file. ICreateTypeLib2* pCreateTypeLib2; CreateTypeLib2(SYS_WIN32, L"C:\\mylib.tlb", &pCreateTypeLib2); |
With the interface pointer obtained from CreateTypeLib2, you can call all the methods of the ICreateTypeLib2 interface. The ICreateTypeLib2 interface is shown below in IDL notation:
interface ICreateTypeLib2 : ICreateTypeLib { // Deletes the specified type information from the // type library HRESULT DeleteTypeInfo([in] LPOLESTR szName); // Sets a value to custom data HRESULT SetCustData([in] REFGUID guid, [in] VARIANT* pVarVal); // Sets the Help string context number HRESULT SetHelpStringContext( [in] ULONG dwHelpStringContext); // Sets the DLL name to be used for Help string lookup // (for localization purposes) HRESULT SetHelpStringDll([in] LPOLESTR szFileName); } |
You can also use the interface pointer returned by CreateTypeLib2 to call the methods of the ICreateTypeLib interface, as shown below in IDL notation:
interface ICreateTypeLib : IUnknown { // Creates a new type information object within the // type library HRESULT CreateTypeInfo([in] LPOLESTR szName, [in] TYPEKIND tkind, [out] ICreateTypeInfo ** ppCTInfo); // Sets the name of the type library HRESULT SetName([in] LPOLESTR szName); // Sets the major and minor version numbers of the // type library HRESULT SetVersion([in] WORD wMajorVerNum, [in] WORD wMinorVerNum); // Sets the GUID associated with the type library HRESULT SetGuid([in] REFGUID guid); // Sets the documentation string associated with the // type library HRESULT SetDocString([in] LPOLESTR szDoc); // Sets the name of the help file HRESULT SetHelpFileName([in] LPOLESTR szHelpFileName); // Sets the help context ID for retrieving general help for // the type library HRESULT SetHelpContext([in] DWORD dwHelpContext); // Sets the locale identifier associated with the // type library HRESULT SetLcid([in] LCID lcid); // Sets general type library flags HRESULT SetLibFlags([in] UINT uLibFlags); // Save all the type information to the type library HRESULT SaveAllChanges(void); } |
The ICreateTypeLib::SetGuid method sets the globally unique identifier (GUID) of a type library, as shown here:
// (1) Set the library LIBID to // {10000003-0000-0000-0000-000000000001}. GUID LIBID_Component = {0x10000003,0x0000,0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}}; pCreateTypeLib2->SetGuid(LIBID_Component); |
The ICreateTypeLib::SetName method sets the name of our type library to Component, as shown in the following code. Note that this is not the name of the type library (.tlb) file but rather the name of the type library stored in the type library file.
// (2) Set the library name to Component. pCreateTypeLib2->SetName(L"Component"); |
ICreateTypeLib2::SetVersion sets the version number for the type description. The following code sets this value to 1.0:
// (3) Set the library version number to 1.0. pCreateTypeLib2->SetVersion(1, 0); |
The helpstring attribute in IDL corresponds to the
ICreateTypeLib2::
SetDocString method. The following code sets the helpstring
attribute to "Inside COM+ Component Type Library":
// (4) Set helpstring to "Inside COM+ Component Type Library". pCreateTypeLib2->SetDocString( L"Inside COM+ Component Type Library"); |
ICreateTypeLib2::SetLcid sets the locale identifier of the type library, which is used to designate the national language for which this type library is created. By default, MIDL generates a language-neutral type library; the following code has the same effect:
pCreateTypeLib2->SetLcid(LANG_NEUTRAL); |
After you configure the basic attributes of the type library, you can use the ICreateTypeLib::CreateTypeInfo method to insert additional kinds of fundamental type information into the type library. The supported kinds of type information are defined by the TYPEKIND enumeration; these types, along with their IDL equivalents, are described in the table below.
TYPEKIND Value | IDL Keyword | Description |
---|---|---|
TKIND_ALIAS | typedef | A type that is an alias for another type. |
TKIND_COCLASS | coclass | A set of implemented component object interfaces. |
TKIND_DISPATCH | dispinterface | A set of methods and properties that are accessible through IDispatch:: Invoke. By default, dual interfaces return TKIND_DISPATCH. |
TKIND_ENUM | enum | A set of enumerators. |
TKIND_INTERFACE | interface | A type that has virtual functions, all of which are pure. |
TKIND_MODULE | module | A module that can have only exported functions and data (for example, a DLL). |
TKIND_RECORD | struct | A structure with no methods. |
TKIND_UNION | union | A union, all of whose members have an offset of 0. |
TKIND_MAX | enum | End of an enum marker. |
These values are the eight fundamental elements you can describe using type information. The following code calls the ICreateTypeLib::CreateTypeInfo method to create type information for an interface (TKIND_INTERFACE) named ISum. If successful, CreateTypeInfo returns a pointer to the system implementation of the ICreateTypeInfo interface.
// (5) Create the ISum interface. ICreateTypeInfo* pCreateTypeInfoInterface; pCreateTypeLib2->CreateTypeInfo(L"ISum", TKIND_INTERFACE, &pCreateTypeInfoInterface); |
Using the methods of ICreateTypeInfo, you can set the attributes of the type information in much the same way that the ICreateTypeLib2 interface operates on the type library as a whole. The ICreateTypeInfo interface is shown below in IDL notation:
interface ICreateTypeInfo: IUnknown { // Sets the GUID associated with the type information HRESULT SetGuid([in] REFGUID guid); // Sets type flags of the type information being created HRESULT SetTypeFlags([in] UINT uTypeFlags); // Sets the documentation string displayed by type browsers HRESULT SetDocString([in] LPOLESTR pStrDoc); // Sets the Help context ID of the type information HRESULT SetHelpContext([in] DWORD dwHelpContext); // Sets the major and minor version number of the // type information HRESULT SetVersion([in] WORD wMajorVerNum, [in] WORD wMinorVerNum); // Adds a reference to other type information HRESULT AddRefTypeInfo([in] ITypeInfo* pTInfo, [in] HREFTYPE* phRefType); // Adds a method to the type information HRESULT AddFuncDesc([in] UINT index, [in] FUNCDESC* pFuncDesc); // Specifies an interface implemented by a coclass HRESULT AddImplType([in] UINT index, [in] HREFTYPE hRefType); // Sets the attributes for an implemented interface // of a type HRESULT SetImplTypeFlags([in] UINT index, [in] INT implTypeFlags); // Specifies the data alignment for a structure HRESULT SetAlignment([in] WORD cbAlignment); // Reserved HRESULT SetSchema([in] LPOLESTR pStrSchema); // Adds a variable or data member description to the // type information HRESULT AddVarDesc([in] UINT index, [in] VARDESC * pVarDesc); // Sets the name of a function and the names of // its parameters to the names in the array of // pointers rgszNames HRESULT SetFuncAndParamNames([in] UINT index, [in, size_is((UINT) cNames)] LPOLESTR * rgszNames, [in] UINT cNames); // Sets the name of a variable HRESULT SetVarName([in] UINT index, [in] LPOLESTR szName); // Sets the type description for which this type description // is an alias HRESULT SetTypeDescAlias([in] TYPEDESC * pTDescAlias); // Associates a DLL entry point with the function that has // the specified index number HRESULT DefineFuncAsDllEntry([in] UINT index, [in] LPOLESTR szDllName, [in] LPOLESTR szProcName); // Sets the documentation string for a method HRESULT SetFuncDocString([in] UINT index, [in] LPOLESTR szDocString); // Sets the documentation string for the variable with the // specified index HRESULT SetVarDocString([in] UINT index, [in] LPOLESTR szDocString); // Sets the Help context ID for the function with the // specified index HRESULT SetFuncHelpContext([in] UINT index, [in] DWORD dwHelpContext); // Sets the Help context ID for the variable with the // specified index HRESULT SetVarHelpContext([in] UINT index, [in] DWORD dwHelpContext); // Sets the marshaling opcode string associated with the // type description or the function HRESULT SetMops([in] UINT index, [in] BSTR bstrMops); // Sets IDL attributes (not supported) HRESULT SetTypeIdlDesc([in] IDLDESC * pIdlDesc); // Assigns virtual function table (VTBL) offsets for // virtual functions and instance offsets for per-instance // data members HRESULT LayOut(void); } |
ICreateTypeInfo2, an extension of the ICreateTypeInfo interface, offers methods for deleting items that were added using the methods of ICreateTypeInfo. To upgrade to the ICreateTypeInfo2 interface, you simply call QueryInterface using the ICreateTypeInfo interface pointer returned by the ICreateTypeLib::CreateTypeInfo method. The ICreateTypeInfo2 interface is shown below in IDL notation:
interface ICreateTypeInfo2: ICreateTypeInfo { // Deletes a method from the type information by // index number HRESULT DeleteFuncDesc([in] UINT index); // Deletes a method from the type information by member ID HRESULT DeleteFuncDescByMemId([in] MEMBERID memid, [in] INVOKEKIND invKind); // Deletes a variable from the type information by // index number HRESULT DeleteVarDesc([in] UINT index); // Deletes a variable from the type information by member ID HRESULT DeleteVarDescByMemId([in] MEMBERID memid); // Deletes the IMPLTYPE flags for an interface HRESULT DeleteImplType([in] UINT index); // Sets a value for custom data HRESULT SetCustData([in] REFGUID guid, [in] VARIANT* pVarVal); // Sets a value for custom data for the specified method HRESULT SetFuncCustData([in] UINT index, [in] REFGUID guid, [in] VARIANT* pVarVal); // Sets a value for the custom data for the // specified parameter HRESULT SetParamCustData([in] UINT indexFunc, [in] UINT indexParam, [in] REFGUID guid, [in] VARIANT* pVarVal); // Sets a value for custom data for the specified variable HRESULT SetVarCustData([in] UINT index, [in] REFGUID guid, [in] VARIANT* pVarVal); // Sets a value for custom data for the specified // implementation type HRESULT SetImplTypeCustData([in] UINT index, [in] REFGUID guid, [in] VARIANT* pVarVal); // Sets the context number for the specified Help string HRESULT SetHelpStringContext( [in] ULONG dwHelpStringContext); // Sets a Help context value for a specified function HRESULT SetFuncHelpStringContext([in] UINT index, [in] ULONG dwHelpStringContext); // Sets a Help context value for a specified variable HRESULT SetVarHelpStringContext([in] UINT index, [in] ULONG dwHelpStringContext); // Reserved HRESULT Invalidate(void); // Sets the name of the type information HRESULT SetName([in] LPOLESTR szName); } |
The following code obtains the ICreateTypeInfo2 interface pointer using QueryInterface, followed by a call to the ICreateTypeInfo::SetGuid method to set the GUID of the ISum interface:
ICreateTypeInfo2* pCreateTypeInfo2 = 0; pCreateTypeInfoInterface->QueryInterface(IID_ICreateTypeInfo2, (void**)&pCreateTypeInfo2); IID IID_ISum = { 0x10000001, 0x0000, 0x0000, { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x01 } }; // (6) Set the ISum IID to // {10000001-0000-0000-0000-000000000001}. pCreateTypeInfo2->SetGuid(IID_ISum); |
You can use the ICreateTypeInfo::SetTypeFlags method to configure numerous attributes of the type information you are creating. The following table lists the TYPEFLAGS values:
TYPEFLAGS Value | IDL Keyword | Description |
---|---|---|
TYPEFLAG_FAPPOBJECT | appobject | A type description that describes an application object. |
TYPEFLAG_FCANCREATE | The default* | Instances of this type can be created by ITypeInfo::CreateInstance. |
TYPEFLAG_FLICENSED | licensed | This type is licensed. |
TYPEFLAG_FHIDDEN | hidden | This type should not be displayed to browsers. |
TYPEFLAG_FCONTROL | control | This type is a control from which other types are derived and should not be displayed to users. |
TYPEFLAG_FDUAL | dual | The types in this interface derive from IDispatch and are fully compatible with Automation. |
TYPEFLAG_FNONEXTENSIBLE | nonextensible | This interface cannot add members at run time. |
TYPEFLAG_FOLEAUTOMATION | oleautomation | The types used in this interface are fully compatible with Automation and can be displayed in an object browser. Specifying dual on an interface sets this flag automatically. |
TYPEFLAG_FRESTRICTED | restricted | The item on which this flag is specified is not accessible from high-level languages such as Visual Basic. |
TYPEFLAG_FAGGREGATABLE | aggregatable | The class supports aggregation. |
TYPEFLAG_FREPLACEABLE | replaceable | The interface has default behaviors. |
TYPEFLAG_FDISPATCHABLE | (none) | The interface derives from IDispatch. |
* The MIDL compiler automatically sets the TYPEFLAG_FCANCREATE flag; no IDL keyword is necessary. If you don't want this behavior, use the IDL keyword noncreatable.
Because the ISum interface is fully Automation-compatible, you set the TYPEFLAG_FOLEAUTOMATION flag using the SetTypeFlags method, as shown in the code below. This flag offers the additional benefit of causing the LoadTypeLibEx function to automatically set up type library marshaling for the ISum interface when the component is registered. (See Chapter 12 for more information.) Notice that the TYPEFLAG_FOLEAUTOMATION flag is different from the TYPEFLAG_FDISPATCHABLE flag, which indicates that the interface is derived from IDispatch. TYPEFLAG_FOLEAUTOMATION simply indicates that the interface restricts itself to Automation-compatible types.
// (7) Set the oleautomation flag. pCreateTypeInfoInterface->SetTypeFlags(TYPEFLAG_FOLEAUTOMATION); |
At this stage, the basic structure of the ISum interface is specified. Still missing from the type information for ISum, however, is the Sum method. Before we add this method, let's turn our attention to the InsideCOM coclass. Using the pointer to the ICreateTypeLib2 interface originally obtained from the call to CreateTypeLib2, you can create a new bit of type information, this time describing the coclass (TKIND_COCLASS) InsideCOM:
// (8) Create type information for the coclass InsideCOM. ICreateTypeInfo* pCreateTypeInfoCoClass; pCreateTypeLib2->CreateTypeInfo(L"InsideCOM", TKIND_COCLASS, &pCreateTypeInfoCoClass); |
Following the familiar pattern, you call the ICreateTypeInfo::SetGuid method to set the GUID of the InsideCOM coclass, as shown here:
// (9) Set the InsideCOM CLSID to // {10000002-0000-0000-0000-000000000001}. CLSID CLSID_InsideCOM = {0x10000002,0x0000,0x0000, {0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01}}; pCreateTypeInfoCoClass->SetGuid(CLSID_InsideCOM); |
Now the class must be marked so that it can be created by clients using the ITypeInfo::CreateInstance method. Type libraries generated by MIDL automatically have this flag set, but when you create a type description manually as described here, you must call the SetTypeFlags method using the TYPEFLAG_FCANCREATE argument, as shown here:
// Specify that this coclass can be instantiated. pCreateTypeInfoCoClass->SetTypeFlags(TYPEFLAG_FCANCREATE); |
Now the InsideCOM coclass and the ISum interface have been defined. However, no relationship exists between the two. To express the fact that the InsideCOM coclass implements the ISum interface, you must insert the type description for ISum into that of InsideCOM. You use the ICreateTypeInfo::AddImplType method to do this. The second parameter of AddImplType requires a handle that identifies the type information, which is declared as an HREFTYPE. To obtain the HREFTYPE for the ISum interface, you must first call the ICreateTypeInfo::AddRefTypeInfo method. This method in turn requires an ITypeInfo interface pointer. You obtain it in a two-step process by calling QueryInterface to request a pointer to the system implementation of the ITypeLib interface, and then using the resultant ITypeLib interface pointer to call the ITypeLib::GetTypeInfoOfGuid method to obtain an ITypeInfo pointer to the type information describing the ISum interface. This process is shown in the code fragment below:
// Get a pointer to the ITypeLib interface. ITypeLib* pTypeLib; pCreateTypeLib2->QueryInterface(IID_ITypeLib, (void**)&pTypeLib); // Get a pointer to the ITypeInfo interface for ISum. ITypeInfo* pTypeInfo; pTypeLib->GetTypeInfoOfGuid(IID_ISum, &pTypeInfo); |
With the ITypeInfo interface pointer at your disposal, you call the ICreateTypeInfo::AddRefTypeInfo method to obtain the HREFTYPE. The HREFTYPE handle returned by AddRefTypeInfo enables you to finally call the ICreateTypeInfo::AddImplType method to declare that the InsideCOM coclass implements the ISum interface, as shown here:
// Trade in the ITypeInfo pointer for an HREFTYPE. HREFTYPE hRefTypeISum; pCreateTypeInfoCoClass->AddRefTypeInfo(pTypeInfo, &hRefTypeISum); // (10) Insert the ISum interface into the InsideCOM coclass. pCreateTypeInfoCoClass->AddImplType(0, hRefTypeISum); |
The ICreateTypeInfo::SetImplTypeFlags method sets attributes for an implemented interface of a type. You can use this method to set one or more of the flags listed in the following table.
IMPLTYPEFLAG Value | IDL Keyword | Description |
---|---|---|
IMPLTYPEFLAG_FDEFAULT | default | The interface or dispinterface represents the default for the source or sink. |
IMPLTYPEFLAG_FSOURCE | source | This member of a coclass is called rather than implemented. |
IMPLTYPEFLAG_FRESTRICTED | restricted | This member should not be displayed or be programmable by users. |
IMPLTYPEFLAG_FDEFAULTVTABLE | defaultvtbl | Sinks receive events through the v-table. |
The following code calls the SetImplTypeFlags method to set the default (IMPLTYPEFLAG_FDEFAULT) flag for the ISum interface in the InsideCOM coclass. This call is important to languages such as Visual Basic that automatically connect the client with the default interface of an object. Note that MIDL automatically sets the default flag for the first unrestricted interface.
// (11) Specify ISum as the default interface. pCreateTypeInfoCoClass->SetImplTypeFlags(0, IMPLTYPEFLAG_FDEFAULT); |
The next step in the creation of the type library is to express the fact that the ISum interface is derived from IUnknown. To do this, you have two choices: define IUnknown using the methods of the ICreateTypeInfo interface as we did for ISum, or load the existing type description of IUnknown from the Automation type library (stdole32.tlb) supplied by Microsoft. To make things easier and more standard, let's opt for the latter approach. You obtain access to the type information contained in the Automation type library using the LoadRegTypeLib function. You use the ITypeLib pointer returned by LoadRegTypeLib to call the ITypeLib::GetTypeInfoOfGuid method to retrieve an ITypeInfo pointer to type information for the IUnknown interface, as shown here:
// Get a pointer to the ITypeLib interface for Automation. ITypeLib* pTypeLibStdOle; GUID GUID_STDOLE = { 0x00020430, 0x00, 0x00, 0xC0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x46 }; LoadRegTypeLib(GUID_STDOLE, STDOLE_MAJORVERNUM, STDOLE_MINORVERNUM, STDOLE_LCID, &pTypeLibStdOle); // Get a pointer to the ITypeInfo interface for IUnknown. ITypeInfo* pTypeInfoUnknown; pTypeLibStdOle->GetTypeInfoOfGuid(IID_IUnknown, &pTypeInfoUnknown); |
After you call the ICreateTypeInfo::AddRefTypeInfo method to retrieve an HREFTYPE handle to the type information, you call the ICreateTypeInfo:: AddImplType method to declare that ISum derives from IUnknown, as shown here:
// Get the HREFTYPE handle for IUnknown's type information. HREFTYPE hRefType; pCreateTypeInfoInterface->AddRefTypeInfo(pTypeInfoUnknown, &hRefType); // (12) Declare that ISum is derived from IUnknown. pCreateTypeInfoInterface->AddImplType(0, hRefType); |
You need a final bit of type information to describe the
ISum::Sum method, which you create by calling the
ICreateTypeInfo::AddFuncDesc method. Unfortunately, using the
AddFuncDesc interface to manually describe methods is not
tremendously enjoyable—you must examine and describe every aspect
of the method in the most exacting and detailed manner. You use the
FUNCDESC structure to describe all the attributes of a method. Figure
9-1 shows the FUNCDESC structure in IDL notation, along with its
associated structures.
Figure 9-1. The FUNCDESC structure and friends.
One of the more important fields in the FUNCDESC structure is the lprgelemdescParam pointer to an array of ELEMDESC structures, each of which describes a single parameter of the method. As you can see in Figure 9-1, the ELEMDESC structure is basically a container for the TYPEDESC and PARAMDESC structures. The TYPEDESC structure describes the parameter type, while the PARAMDESC structure contains flags that indicate whether the parameter is passed to and/or from the object.
The following code defines an array of three ELEMDESC structures and initializes it for the three parameters (x, y, and retval) of the ISum::Sum method. The first two structures are declared as integers (VT_INT), and the third is declared as a pointer (VT_PTR) to an integer, with special flags (PARAMFLAG_ FRETVAL and PARAMFLAG_FOUT) indicating that this is an [out, retval] parameter.
// Structures for the x, y, and retval parameters of the // Sum method TYPEDESC tdescParams = { 0 }; tdescParams.vt = VT_INT; ELEMDESC myParams[3] = { 0 }; myParams[0].tdesc.vt = VT_INT; // x myParams[0].tdesc.lptdesc = &tdescParams; myParams[1].tdesc.vt = VT_INT; // y myParams[1].tdesc.lptdesc = &tdescParams; myParams[2].tdesc.vt = VT_PTR; // retval myParams[2].tdesc.lptdesc = &tdescParams; myParams[2].paramdesc.wParamFlags = PARAMFLAG_FRETVAL|PARAMFLAG_FOUT; |
Next you allocate and initialize the FUNCDESC structure using a number of settings. You declare the Sum method (INVOKE_FUNC) as a pure virtual function (FUNC_PUREVIRTUAL) using the standard calling convention (CC_STDCALL), which accepts three parameters (cParams) and returns an HRESULT (VT_HRESULT). Then you call the ICreateTypeInfo::AddFuncDesc method to add the Sum method as described by the FUNDESC structure to the ISum interface, as shown here:
// Additional data describing the Sum method and its // return value TYPEDESC tdescUser = { 0 }; FUNCDESC FuncDesc = { 0 }; FuncDesc.funckind = FUNC_PUREVIRTUAL; FuncDesc.invkind = INVOKE_FUNC; FuncDesc.callconv = CC_STDCALL; FuncDesc.elemdescFunc.tdesc.vt = VT_HRESULT; FuncDesc.elemdescFunc.tdesc.lptdesc = &tdescUser; FuncDesc.cParams = 3; FuncDesc.lprgelemdescParam = myParams; // (13) Add the Sum method to the ISum interface. pCreateTypeInfoInterface->AddFuncDesc(0, &FuncDesc); |
Notice that we have not yet provided names for the parameters of the Sum method. To attach names to the Sum method and its parameters, you call ICreateTypeInfo::SetFuncAndParamNames with an array of the names to be attached, as shown here:
// Set names for the Sum function and its parameters. OLECHAR* Names[4] = { L"Sum", L"x", L"y", L"retval" }; pCreateTypeInfoInterface->SetFuncAndParamNames(0, Names, 4); |
You call the ICreateTypeInfo::LayOut method to assign v-table offsets for the virtual functions, as shown here:
// Assign the v-table layout. pCreateTypeInfoInterface->LayOut(); |
Unless you call the ICreateTypeLib2::SaveAllChanges method, all of your efforts will have been for naught, since nothing will have been saved to disk! This call is followed by calls to the Release method for all the objects accumulated during this exercise, as shown here:
// Save changes. pCreateTypeLib2->SaveAllChanges(); // Release all references. pTypeInfoUnknown->Release(); pTypeLibStdOle->Release(); pTypeInfo->Release(); pTypeLib->Release(); pCreateTypeLib2->Release(); pCreateTypeInfoInterface->Release(); pCreateTypeInfoCoClass->Release(); |
Once you have a basic understanding of how to create type libraries using the ICreateTypeLib and ICreateTypeInfo interfaces, you'll have a much better understanding of the information contained in a type library.6