A treasure trove of information: The type library
The container can interact with the OCX in three different ways. The first way is by calling code in the OCX. This code is encapsulated in an entity called Method. Method is nothing but another word for function.
Similarly, the OCX can also have several Properties such as color, caption etc. associated with it. These properties can be read and/or changed by the user of the OCX. Each property is just a variable of the appropriate data type. For example, if we have an OCX that handles graphs, we would want to change the type of graph. So one property of this OCX could be a variable graph type say of the data type integer. Depending on the value of this variable, a bar graph, a pie graph or a line graph could be displayed.
Finally, associated with an OCX are Events. An event is any happening that can be detected by the OCX. `Click' is an example. This event occurs whenever there is a mouse click over the OCX. Typically, we associate some code with each event. This code will be executed when the event occurs.
A user needs to know of all such features; be it events, methods or properties; that the OCX has decided to let him interact with. So, it became necessary to develop a technique that would indicate to the user all the accessible events, properties and methods of the OCX. Since, OLE itself is designed to be programming language independent this technique too had to work irrespective of the language used to access the information. In a sense, it also had to be distinct from the OCX itself.
Taking into account all these conditions Microsoft designed the Object Description Language (ODL) as an aid. Just like the resource programming language is used to define the resources of an application in a `.rc' file, ODL is used to create an `.odl' file. This is an ASCII file and it holds the names of the events, methods and properties of the OCX.
It has to be compiled separately using the MkTypLib compiler. On compilation it will generate a `.tlb' file; the type library. The type library file is considered to be a resource; much like a menu or a dialog. The RC file holds a line that indicates the type library for the OCX. The ID for the type library resource is always to be 1. The line would typically read:
1 TYPELIB NameOfFile.tlb
The `.tlb' file will be a part of the final DLL.
The ODL files for an OCX are always of the following format. This is an elementary ODL file that we had created.
[uuid(12345678-9012-3456-7890-123456789012)]
library zzz
{
importlib("stdole.tlb");
[uuid(23456789-0123-4567-8901-234567890123)]
dispinterface www
{
properties:
[id(1)] long j;
methods:
[id(2)] void aa();
[id(3)] long bb(short a, BSTR b);
};
[uuid(34567890-1234-5678-9012-345678901234)]
dispinterface xxx
{
properties:
methods:
[id(10)] void Click();
};
[uuid(45678901-2345-6789-0123-456789012345)]
coclass yyy
{
[default] dispinterface www;
[default,source] dispinterface xxx;
};
};
Every distinct entity in the ODL file is assigned it's own unique identification number. This identifier or uuid is nothing but a GUID structure. (What's in a name?). The very first uuid that we encounter in the ODL file is that of the type library itself. Just like every interface, each type library is also registered with `reg.dat'. It is this uuid that we use to access the type library.
The type libraries may want to reference type infos that have already been declared in other type libraries. importlib() is used to include the type libraries to be referenced.
The data within the type library could be of different types. We could have a structure, a union, an IDispatch interface etc. Each is a distinct element and is referred to by the generic name Type Info.
For an OCX, there are always three type infos per type library. The first type info always defines an IDispatch interface that holds information about the methods and properties while the second is always the IDispatch interface that allows access to the OCX's events. This second type info will never have any properties associated with it. There would only be the events listed in it.
The third type info is a CoClass. The following is the definition given in the OLE 2.0 Programmer's Reference, Volume 2:
"Description of a component object class description. Specifies an implementation of one or more OLE interfaces and one or more IDispatch interfaces."
We challenge you to make sense of this not-too-eloquent statement.
Each type info is provided with it's own uuid. The uuid is stated immediately above the type info. Thus, in the type library, every uuid de-lineates a new type info. Immediately following the uuid is a line that identifies the nature of the type info. The IDispatch interfaces are distinguished by the ODL keyword dispinterface. Following the keyword is the name assigned to the type info. This name can be used to refer to the type info later in the ODL file. The IDispatch interface type infos have the following format:
dispinterface NameOfInterface
{
properties:
.
. // List Of Properties;
.
methods:
.
. // List Of Methods;
.
};
Each property is listed in the format:
[id(IDOfProperty)] TypeOfProperty NameOfProperty;
while each method is listed in the format:
[id(IDOfMethod)] ReturnType NameOfMethod(Param 1,..., Param n);
Every element of an OCX that can be accessed by others is assigned an unique ID. This is true irrespective of whether we are talking of events, methods or properties. It is using this ID that we access the members rather than their names. We will get into the specifics of accessing them later in this newsletter. First, let us read a type library.
On executing the following program, you will not see anything different from the previous one. But the important thing in this program is that we show you how to read a type library. In the process we also initialize the global structures and create link- lists to store the information from the type library.
Program 6
void MFrameWnd::ObjCreate(CLSID z_TempClsid)
{
// Retain all previously existing code
InitArray();
}
void MFrameWnd::InitArray()
{
IProvideClassInfo *z_pProvideClassInfo = 0;
ITypeLib *z_pTypeLib = 0;
ITypeInfo *z_pTypeInfo = 0;
VARDESC *z_pVarDesc = 0;
FUNCDESC *z_pFuncDesc = 0;
UINT z_nIndex = 0;
TYPEATTR *z_pTypeAttr = 0;
z_pOleObject-> QueryInterface(IID_IProvideClassInfo,(void**)& z_pProvideClassInfo);
z_pProvideClassInfo-> GetClassInfo(& z_pTypeInfo);
z_pTypeInfo-> GetContainingTypeLib(& z_pTypeLib,& z_nIndex);
for(UINT z_nTypeCount = 0; z_nTypeCount < 2; z_nTypeCount++)
{
PData *z_pPDPrev = 0;
FData *z_pFDPrev = 0;
z_pTypeLib-> GetTypeInfo(z_nTypeCount,& z_pTypeInfo);
z_pTypeInfo-> GetTypeAttr(& z_pTypeAttr);
// if it is the Events dispinterface store the CLSID. It is required later.
if(z_nTypeCount)
z_ClsidEvents = z_pTypeAttr-> guid;
// Initialization of the link-list that holds information on properties
if(!z_nTypeCount)
{
for(UINT z_nVars = 0; z_nVars < z_pTypeAttr-> cVars; z_nVars++)
{
VARTYPE z_vt;
UINT z_nCopied;
if(!z_pPDPrev)
{
z_pPDPrev = new PData;
z_pPDFirst = z_pPDPrev;
}
else
{
PData *z_pNext;
z_pNext = new PData;
z_pPDPrev-> z_pNext = z_pNext;
z_pPDPrev = z_pNext;
}
z_pPDPrev-> z_pNext = 0;
z_pPDPrev-> z_nPtr = FALSE;
z_pTypeInfo-> GetVarDesc(z_nVars,& z_pVarDesc);
if(z_pVarDesc-> elemdescVar.tdesc.vt> 13)
z_vt = z_pVarDesc-> elemdescVar.tdesc.vt -2;
else
z_vt = z_pVarDesc-> elemdescVar.tdesc.vt ;
z_pPDPrev-> z_nPropType = z_vt;
if(!strcmp(z_VarType[z_pPDPrev-> z_nPropType],"VT_USERDEFINED"))
{
UserDefined(z_pTypeInfo, & z_pVarDesc-> elemdescVar.tdesc,& z_vt);
z_pPDPrev-> z_nPropType = z_vt;
}
else if(!strcmp(z_VarType[z_pPDPrev-> z_nPropType],"VT_PTR"))
{
z_pPDPrev-> z_nPtr = TRUE;
Ptr(z_pTypeInfo,& z_pVarDesc-> elemdescVar.tdesc,& z_vt);
z_pPDPrev-> z_nPropType = z_vt;
}
z_pPDPrev-> z_lDispID = z_pVarDesc-> memid;
char *z_sPName = (char*) malloc(25);
z_pTypeInfo-> GetNames(z_pVarDesc-> memid,& z_sPName,
1,& z_nCopied);
z_pPDPrev-> z_sPropName = (char*) malloc(strlen(z_sPName) + 1);
strcpy(z_pPDPrev-> z_sPropName,z_sPName);
if(z_pVarDesc)
z_pTypeInfo-> ReleaseVarDesc(z_pVarDesc);
free(z_sPName);
}
}
// Initialization of the link-list that holds information on methods or events
for(UINT z_nFuncs = 0;z_nFuncs < z_pTypeAttr-> cFuncs;z_nFuncs++)
{
char **z_sNames;
UINT z_nCopied;
VARTYPE z_vt;
if(!z_pFDPrev)
{
z_pFDPrev = new FData;
if(!z_nTypeCount)
z_pFDFirst = z_pFDPrev;
else
z_pEDFirst = z_pFDPrev;
}
else
{
FData *z_pFDNext;
z_pFDNext = new FData;
z_pFDPrev-> z_pNext = z_pFDNext;
z_pFDPrev = z_pFDNext;
}
z_pFDPrev-> z_nPtr = FALSE;
z_pFDPrev-> z_pNext = 0;
z_pTypeInfo-> GetFuncDesc(z_nFuncs,& z_pFuncDesc);
z_sNames = (char**) malloc((z_pFuncDesc-> cParams + 1) * sizeof(char*));
z_pTypeInfo-> GetNames(z_pFuncDesc-> memid,z_sNames,
(z_pFuncDesc-> cParams + 1),& z_nCopied);
if(z_pFuncDesc-> elemdescFunc.tdesc.vt > 13)
z_vt = z_pFuncDesc-> elemdescFunc.tdesc.vt - 2;
else
z_vt = z_pFuncDesc-> elemdescFunc.tdesc.vt;
if(!strcmp(z_VarType[z_vt],"VT_PTR"))
{
z_pFDPrev-> z_nPtr = TRUE;
Ptr(z_pTypeInfo,& z_pFuncDesc-> elemdescFunc.tdesc,& z_vt);
}
z_pFDPrev-> z_nRType = z_vt;
z_pFDPrev-> z_lDispID = z_pFuncDesc-> memid;
z_pFDPrev-> z_nNumParams = z_pFuncDesc-> cParams ;
z_pFDPrev-> z_sFName = (char*) malloc(strlen(z_sNames[0]) + 1);
strcpy(z_pFDPrev-> z_sFName,z_sNames[0]);
if(z_pFuncDesc-> cParams )
{
z_pFDPrev-> z_pPTypes = (VARTYPE*) malloc(z_pFuncDesc-> cParams * sizeof(VARTYPE));
z_pFDPrev-> z_sPName = (char**) malloc(sizeof(char*) * z_pFuncDesc->
cParams);
for(int z_nParms = 0; z_nParms < z_pFuncDesc-> cParams ; z_nParms++)
{
z_pFDPrev-> z_sPName[z_nParms] = (char*) malloc(strlen(z_sNames[z_nParms+1]) + 1);
strcpy(z_pFDPrev-> z_sPName[z_nParms],z_sNames[z_nParms +1]);
z_pFDPrev-> z_pPTypes[z_nParms] = z_pFuncDesc-> lprgelemdescParam[z_nParms].tdesc.vt;
}
}
free(z_sNames);
if(z_pFuncDesc)
z_pTypeInfo-> ReleaseFuncDesc(z_pFuncDesc);
}
if(z_pTypeAttr)
z_pTypeInfo-> ReleaseTypeAttr(z_pTypeAttr);
}
if(z_pProvideClassInfo)
z_pProvideClassInfo-> Release();
if(z_pTypeLib)
z_pTypeLib-> Release();
if(z_pTypeInfo)
z_pTypeInfo-> Release();
}
void MFrameWnd::UserDefined(ITypeInfo *z_pTInfo,TYPEDESC * z_pTDesc,VARTYPE* z_nVt)
{
ITypeInfo *z_pRefTypeInfo;
TYPEATTR *z_pRefTypeAttr;
z_pTInfo-> GetRefTypeInfo(z_pTDesc-> hreftype,& z_pRefTypeInfo);
z_pRefTypeInfo-> GetTypeAttr(& z_pRefTypeAttr);
if(z_pRefTypeAttr-> typekind == TKIND_ALIAS)
{
if(z_pRefTypeAttr-> tdescAlias.vt > 13)
*z_nVt = z_pRefTypeAttr-> tdescAlias.vt -2;
else
*z_nVt = z_pRefTypeAttr-> tdescAlias.vt;
if(!strcmp(z_VarType[*z_nVt],"VT_USERDEFINED"))
UserDefined(z_pRefTypeInfo,& z_pRefTypeAttr-> tdescAlias,z_nVt);
}
if(z_pRefTypeInfo)
z_pRefTypeInfo-> ReleaseTypeAttr(z_pRefTypeAttr);
}
void MFrameWnd::Ptr(ITypeInfo *z_pTInfo,TYPEDESC *z_pTDesc, VARTYPE *z_nVt)
{
if(z_pTDesc-> lptdesc-> vt > 13)
*z_nVt = z_pTDesc-> lptdesc-> vt - 2;
else
*z_nVt = z_pTDesc-> lptdesc-> vt;
if(!strcmp(z_VarType[*z_nVt],"VT_USERDEFINED"))
UserDefined(z_pTInfo,z_pTDesc-> lptdesc,z_nVt);
}
The standard interface ITypeLib is a collection of functions which enable us to read data from the type library. So, we need to obtain a pointer to an ITypeLib interface.
The interface ITypeInfo has functions that can deal with details about the individual type infos within the type library. If we have a pointer to an ITypeInfo interface, then the ITypeLib pointer of the type library to which this type info belongs can be determined. The reverse is also true. In this program we have initially obtained an ITypeInfo pointer and then fetched an ITypeLib pointer from it.
An ITypeInfo pointer to the CoClass within the type library of the OCX can be obtained easily. This information can be had from the implementation of the interface IProvideClassInfo in the OCX. Thus, we start with a QueryInterface() to obtain a pointer to the interface IProvideClassInfo.
We use the IProvideClassInfo pointer to call the function GetClassInfo(). This function returns a pointer to the ITypeInfo that describes the CoClass. GetContainingTypeLib() is the ITypeInfo method that is used to obtain a pointer to ITypeLib interface for the type library.
Each type library has a number of type infos. GetTypeInfo() of ITypeLib will retrieve a pointer to the required type info within the type library. The first parameter to this function is the index of the type info whose ITypeInfo pointer is needed.
This ITypeInfo pointer is used to obtain more information about the type info. The information is returned via a TYPEATTR structure. The function GetTypeAttr() of ITypeInfo is used to requisition a pointer to this structure.
The TYPEATTR structure holds information such as the uuid, number of variables and number of functions. The TYPEATTR structure also has a variable that indicates the nature of the type info; i.e. whether it is a structure, a union ...
The first type info in the type library of an OCX will always hold the property and the method details for the OCX. Thus, it's TYPEATTR structure will indicate the number of properties and methods accessible through it. This information is stored in the element cVars and cFuncs respectively of the TYPEATTR structure.
The information about each variable can be had via a VARDESC structure. The function GetVarDesc() of ITypeInfo accepts two parameters: the first an index to the property whose details are sought and the address of a VARDESC structure that will hold the information about the variable.
The VARDESC structure holds the ID of the property and has an ELEMDESC structure in addition to some other information. It is the ELEMDESC structure that has to be used to obtain additional facts about the property.
The structure ELEMDESC contains two structures: TYPEDESC and IDLDESC. We ignore the IDLDESC structure in the discussion to follow. The TYPEDESC structure contains the information on the data type of the property. It consists of just two elements: one of the type VARTYPE and the second a union.
Confusing, isn't it? Let's take up specific examples to indicate how data about variables is read. Consider the first type info in the type library to have the variables listed as shown below under the heading properties:
[id(10)] int h
[id(15)] long* i;
[id(20)] int j[10][12];
[id(25)] OLE_COLOR k;
CASE 1: [id(10)] int h
The following procedure will hold true for any variable that is of a standard data type, is not a pointer or an array of any sort.
. The function GetVarDesc() will return a VARDESC structure for this variable when it's first parameter is 0. The member memid of the VARDESC structure will be 10. This is the ID of the integer variable h as seen from the above example. The ELEMDESC structure within VARDESC is known by the name elemdescVar. Accessing this ELEMDESC structure is the next step in determining the data type of a variable. The actual information is contained in tdesc; the TYPEDESC structure within the ELEMDESC structure. The TYPEDESC structure has a variable vt that holds the data type.
If z_pVarDesc is our pointer to a VARDESC structure, then the above explanation can be represented by the code `z_pVarDesc-> elemdescVar.tdesc.vt'.The acceptable data types are stored in the enum VARENUM. The variable vt is assigned the appropriate member of the enum. For an integer variable; vt will be equal to VT_I2. A listing of VARENUM can be had from variant.h.
CASE 2: [id(15)] long* i;
Accessing any pointer is a multi-level process. The GetVarDesc() with 1 as it's first parameter will return a VARDESC structure for this variable. Once again the element memid of VARDESC will return the ID of the property; in this case 15.
.
The ELEMDESC within the VARDESC structure is accessed. Using the ELEMDESC structure of the VARDESC, we gain access to the TYPEDESC structure. The member variable vt of TYPEDESC returns VT_PTR. This just indicates that the variable is a pointer. We have to know what is it that it points to.
Within the TYPEDESC structure is a union. One of the members of the union; lptdesc; is a pointer to yet another TYPEDESC structure. It is this TYPEDESC structure that will hold the data type of the pointer. Thus, we use the TYPEDESC pointer to access the second TYPEDESC structure. The variable vt in the second TYPEDESC structure returns the actual data type of the pointer. `z_pVarDesc-> elemdesc.tdesc.lptdesc-> vt' is the code equivalent of the above explanation.
In our case, the property was a pointer to a long. Hence, vt is equal to VT_I4.
What if the line had read `[id(15)] long** j', instead? In such a case, the member vt of the second TYPEDESC structure will also read VT_PTR. We then use the TYPEDESC pointer within it to access a third TYPEDESC structure. The variable vt of the third structure will return the data type of the variable; in this case; VT_I4. `z_pVarDesc-> elemdesc.tdesc.lptdesc-> lptdesc-> vt' would be the code.
.
Thus, if we have a a variable long*** j; then a fourth TYPEDESC structure is to be accessed and so on. In practice, we use a recursive function to deal with such situations.
CASE 3: [id(20)] int j[10][12]
The process of accessing the VARDESC structure and the ID remains the same. The member variable vt of TYPEDESC returns VT_CARRAY as the data type for this variable. The TYPEDESC structure has a union. One of the three elements of this union is a pointer to an ARRAYDESC structure; lpadesc. The ARRAYDESC structure holds information about the array.
The TYPEDESC structure; tdescElem; within ARRAYDESC is used to determine the data type. In our case, the variable vt within the TYPEDESC structure will return VT_I2. The variable cDims of the ARRAYDESC structure will return the number of dimensions in the array; in our case it is 2.
.
Within the ARRAYDESC structure is an array of SAFEARRAYBOUND structures. There is one SAFEARRAYBOUND structure dedicated to each dimension of the array variable. The size of the dimension is stored in the element cElements of the corresponding SAFEARRAYBOUND structure.
CASE 4: [id(25)] OLE_COLOR k;
In this case, OLE_COLOR, is a data type unique to OCXes. This is treated as a user-defined data type. The variable vt within the TYPEDESC structure will return VT_USERDEFINED. The third element of the union within the TYPEDESC structure is used to access more information about user defined data types. This a variable of the type HREFTYPE. Each user-defined data type will have an ITypeInfo of it's own.
A pointer to this type info can be accessed using the function GetRefTypeInfo() of the ITypeInfo interface and the HREFTYPE variable of the TYPEDESC structure. The function GetRefTypeInfo() requires two parameters. The first is an HREFTYPE variable that points to the type info to be accessed. The second is the address of an ITypeInfo pointer variable.
Using the newly acquired ITypeInfo pointer, we access a TYPEATTR structure. We now check the value of the TYPEATTR variable tkind. Typically, for a variable of the data type VT_USERDEFINED, this variable will return the value TKIND_ALIAS. The TYPEATTR structure holds a TYPEDESC structure. It is this structure that holds the information about the data type of the property. It holds the actual data type and not the alias. For example, OLE_COLOR is actually a long. Thus, VT_I4 is the value returned to us.
While these are the basic methods that are used to access the variables from the type library, a combination of these may also have to be used.
Before we move ahead a nugget. The TYPEDESC structure has a union of pointers. One of the pointers in the union is to a TYPEDESC structure while the other is to the ARRAYDESC structure. Such a union makes sense if and only if one of these structures is a part of the other. In this case, the TYPEDESC structure is a part or a member of the ARRAYDESC structure. Much like a derived class, which is the base class plus more.
All the above cases describe how the data type of the variable is determined but they are silent on how the name of the variable is recovered. The name of the variable can be had by calling the function GetNames() of the ITypeInfo interface. In the case of methods GetNames() will fetch the name of the function and those of the parameters too. The first parameter to this function is the ID of element whose name is to be recovered. GetNames() will retrieve the name into the user allocated buffer.
The third parameter to GetNames() indicates the number of strings to be read into the buffer; in the case of variables this parameter is 1. The last parameter is filled with the number of names actually copied into the buffer.
After dealing with properties of an OCX, we have to deal with methods and events. Information for the methods and the events are retrieved exactly in the same way. The only difference is that the methods are held in the first type info within the type library while the events are held in the second.
In the following discussion, we have assumed that we are dealing with methods. But the entire process also holds true for events.
The data about each method is retrieved using the ITypeInfo method GetFuncDesc(). This returns a FUNCDESC which holds the required information.
The FUNCDESC structure holds the ID of the method. It also indicates the number of parameters that the method has and other information. There is an ELEMDESC structure within the FUNCDESC structure. This structure holds the information on the return type of the method.
The FUNCDESC structure also holds an array of ELEMDESC structures. These structures are used to obtain information about the parameters. There is one to one correspondence between the elements in this array and the order of the parameters in the method.
Don't you get an eerie feeling of deja vu? We won't tire you with how to use TYPEDESC to obtain the data type of the return value and the parameters. We bet you could recite all the options in your sleep.
In our example, we have created two structures that will hold information read from the type library. The first is called PData and will store information about the properties of an OCX. It will store the name, the ID and the data type of the property. VARTYPE is nothing but a typedef and stands for unsigned short. A variable of the type VARTYPE can be used to hold the data type of the variable under consideration. We create a link-list of PData structures with the details about each particular property being stored in a separate structure.
The structure FData is used to store particulars about both the events as well as the methods. This structure holds the name of the event/method, the data type of the return value, number of parameters, names and data type of the parameters and ID of the event/method. A link-list is formed and the particulars of each of the events and methods are stored as individual members of this link-list. Event and method details are stored in separate link-lists.
The initialization of the link-lists is a one-time task carried out at the start of the application. The function InitArray() is responsible for this chore. It is called from within ObjCreate(). The starting addresses of the link-lists that hold the property, method and event details are stored in z_pPDFirst, z_pFDFirst and z_pEDFirst respectively.
The functions Ptr() and UserDefined() deal with the situations where a variable is a pointer and an user-defined data type respectively.
Continue