LENS

Programmer's Guide: How To Create Fields in an Object or Extension


Each major Lens object, the Network, Group, Unit, Block, ExampleSet, Example, and Event, has an extension record defined in extension.h. Most additions to the Lens object structures should be made to the extension record and any code that uses those extensions should be placed in extension.c so that future updates to the other Lens modules can be incorporated painlessly.

The default extension structures look something like this:

    typedef struct netExt {
      char removeMe;
    } *NetExt;

The field removeMe is just a dummy there to prevent warnings in some compilers. If any fields are added, the dummy should be removed.

Once you plan to make use of an extension structure, you will need to fill in the initialization structure for that extension. When an instance of the main object is created, it will be passed to this function so that the extension can be added. The default initialization functions look something like this:

    flag initNetworkExtension(Network N) {
      FREE(N->ext);
      return TCL_OK;
    }

This just takes a network and sets the extension field to NULL (freeing it if it exists). Once some fields are added to the extension, you will want to change it to something like this:

    flag initNetworkExtension(Network N) {
      N->ext = safeCalloc(1, sizeof(struct netExt), 
         "initNetworkExtension:N->ext");
      return TCL_OK;
    }

Remember to use safeMalloc() or safeCalloc(), rather than malloc() or calloc(). You can then initialize extension fields as necessary.

Making Fields Accessible

Lens uses a rather complex mechanism for making the C object hierachy accessible from within the Tcl shell. Each container structure and primitive data type, such as an int, will have an ObjInfo structure defined for it. This describes each field or member of the object using MemInfo structures. MemInfo structures contain information such as the name of the field, where it is located in the object, and whether it is write-protected. ObjInfo and MemInfo are defined in object.c.

To build an ObjInfo description, a pointer to one is first declared globally, then allocated with newObject(), and then members are added to it with addMember(). When adding to object extensions, global ObjInfo structures will have already been created for you. These are defined in extension.c and are as follows:

    ObjInfo RootInfo;
    ObjInfo NetInfo;
    ObjInfo GroupInfo;
    ObjInfo GroupProcInfo;
    ObjInfo UnitInfo;
    ObjInfo BlockInfo;
    ObjInfo LinkInfo;
    ObjInfo ExampleSetInfo;
    ObjInfo ExampleInfo;
    ObjInfo EventInfo;
    ObjInfo RangeInfo;

    ObjInfo IntInfo;             /* int */
    ObjInfo RealInfo;            /* real */
    ObjInfo FlagInfo;            /* flag */
    ObjInfo MaskInfo;            /* mask */
    ObjInfo StringInfo;          /* char * */
    ObjInfo TclObjInfo;          /* TclObj */

Member fields should be added within the functions that have names such as initNetExtInfo() and initExSetExtInfo(). Here is a sample ObjInfo init function:

    int getNetExtRows(void *NX) {
      return ((NetExt) NX)->rows;
    }
    int getNetExtCols(void *NX) {
      return ((NetExt) NX)->cols;
    }

    void initNetExtInfo(void) {
      NetExt NX;

      addObj   (NetExtInfo, "name", OFFSET(NX, name), TRUE, StringInfo);

      addObjP  (NetExtInfo, "theGroup", OFFSET(NX, theGroup), FALSE, 
                GroupInfo);
      addSpacer(NetExtInfo);

      addObj   (NetExtInfo, "rows", OFFSET(NX, rows), FALSE, IntInfo);

      addObj   (NetExtInfo, "cols", OFFSET(NX, cols), FALSE, IntInfo);

      addObjPA (NetExtInfo, "array", OFFSET(NX, array), FALSE, getNetExtRows,
                UnitInfo);
      addObjAA (NetExtInfo, "array2", OFFSET(NX, array2), TRUE, getNetExtRows,
                getNetExtCols, RealInfo);
    }

The function must begin by declaring a pointer to the type of extension you are describing, in this case "NetExt NX;". The rest of the function will be made up of calls to field-defining functions. The field-defining functions are as follows:

void addObj(ObjInfo O, char *name, int offset, flag writable, ObjInfo info)
The field is either a primary data type, such as an integer, flag, mask, real, or string, or another structure embedded within this one. An example of the latter is the pipeExample structure embedded within an ExampleSet.
void addObjP(ObjInfo O, char *name, int offset, flag writable, ObjInfo info)
The field is a pointer to an object. This is used for such things as the group field within a unit or for a pointer to a primitive type such as a real.
void addObjA(ObjInfo O, char *name, int offset, flag writable, int (*rows)(void *), ObjInfo info)
The field is an array of primitive objects or an array of structures. An example is the eventHistory array within a network or the unit array within a group.
void addObjPA(ObjInfo O, char *name, int offset, flag writable, int (*rows)(void *), ObjInfo info)
The field is an array of pointers to objects. An example is the group array within a network.
void addObjAA(ObjInfo O, char *name, int offset, flag writable, int (*rows)(void *), int (*cols)(void *), ObjInfo info)
The field is a two-dimensional array of objects.
void addObjPAA(ObjInfo O, char *name, int offset, flag writable, int (*rows)(void *), int (*cols)(void *), ObjInfo info)
The field is a two-dimensional array of pointers to objects.
void addSpacer(ObjInfo O)
This produces a space between fields when the object is viewed with the Object Viewer.
The arguments to the above functions are:
ObjInfo O
This is the object info structure you are describing. For example, NetExtInfo.
char *name
This is the name of the field as it will appear on the Object Viewer and as it will be referred to in the shell.
int offset
This is the byte offset between this field and the start of the object. This OFFSET() macro is used to set that.
flag writable
This determines whether the user can change the value of this field. For arrays, this determines whether the individual elements of the array are writable.
int (*rows)(void *)
This is only used for arrays. It is a function that takes a pointer to the object (cast as a void *) and returns an integer giving the size of the first dimension of the array.
int (*cols)(void *)
This is only used for two-dimensional arrays. It is a function that takes a pointer to the object (cast as a void *) and returns an integer giving the size of the second dimension of the array.
ObjInfo info
This is the ObjInfo structure that defines the specific type of the field or elements of the array.

In the initNetExtInfo() example above, the first member is a writable string called name. The second is a pointer to a group. The third and fourth are write-protected integers. The fifth is an array of unit pointers with dimension rows. The last field is a two-dimensional array of writable real numbers.


Douglas Rohde
Last modified: Mon Nov 13 23:07:04 EST 2000