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