[Previous] [Contents] [Next]

Creating a Type Library

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);

Adding Type Information

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.

Click to view at full size.

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