[Up: Using C++ objects with Tcl]
[Previous: Introduction] [Next: The Tcl Domain]

The C++ Domain

This section describes how to adapt your classes to work with the tclobj package and the available extensions that you might find useful.

The Class Declaration

All classes that you want to be visible from the Tcl level must be derived from the common base class Tcl_Object, which is defined in the include file tclobj.h. This derivation may be direct or indirect (if Tcl_Object is inherited through one of the base classes).

If you want your classes to still compile without Tcl, you can use the following preprocessor directives:

#ifdef TCL_OBJECT
class HelloWorld : public Tcl_Object
#else
class HelloWorld
#endif

None of the member declarators need to be modified, but three lines must be added. To continue with our declaration of the HelloWorld class:

#ifdef TCL_OBJECT
public:
  HelloWorld   (Tcl_Interp *, int, char *[], int);
  int Tcl_Proc (Tcl_Interp *, int, char *[], int);
  TCL_OBJECT_DECL_CLASS(HelloWorld);
#endif

The first line declares a special constructor, which will be called when the new operator is invoked by a Tcl program. The second line declares a special member function which will be called whenever an operation on the object is invoked by a Tcl program. These two functions act as the interface between the two worlds and must both be public members of the class.

The TCL_OBJECT_DECL_CLASS macro adds some common declarations. (The reason that Tcl_Proc is not considered common is that you will have to write that function yourself.) The macro takes as parameter the (unquoted) name of the class.

A class can be derived from more than one class, but can only inherit Tcl-level procedures from one of them. For more information, see the later chapter on ``Multiple Inheritance''.gif

Note that both functions, the constructor and Tcl_Proc, must not be inline. The macro declares both public and private members. Do not assume that it ends with a particular kind of access specifier (add your own public:, private: or protected: for following declarations).

You do not need to declare a constructor for abstract base classes. In theory, you could also choose not to declare a Tcl-type constructor for non-abstract classes. Such classes could then never be instantiated on the Tcl level. Abstract base classes do however need the Tcl_Proc function, because they can offer some functionality to derived classes, for example generic functions using pure virtual members.

The Class Implementation

To the class's implementation we must add the constructor and the Tcl_Proc function, and a call to yet another macro. There are two incarnations of the macro, one for abstract classes that do not provide a Tcl-style constructor, and one for non-abstract classes. Either the one or the other must be called at global level.

TCL_OBJECT_DECL_BODY(class, derived-from,
                     command-table, init-function);

class
The class's type (unquoted). For our example, this would be HelloWorld.
derived-from
The base class's type (also unquoted).
command-table
A table detailing the operations that can be performed on objects of this class from the Tcl level, and their parameter lists. See below.
init-function
If non-NULL, this function is called once when the class is initialized (but after the initialization of the module's global objects) and can perform custom initialization. The function receives as its sole argument a Tcl_Interp* pointer to the current interpreter, must be of ``C'' linkage, and return the integer TCL_OK upon success, else it should set an appropriate error message and return TCL_ERROR.

Abstract base classes, or classes that you want to appear abstract from the Tcl level, must use the TCL_OBJECT_DECLARE_ABSTRACT_BODY macro instead. It has the same arguments as above, but sets up some internal information differently. You can find a complete example in figure 1.

Command Table

The command-table is a null-terminated array with elements of type const TclObj_Comtab. It defines the signatures of the constructors and member functions available on the Tcl level. The information is used by the library functions upon invokation of a member function to check whether the parameters match an available signature. Each item of the array is composed of four strings:

number
A positive serial number of this entry. It should be unique among the constructors or the member functions within a table - you'll see later why. (There may be cases when re-using a number could make sense; so it's not explicitely forbidden.)
name
The name of the member function, or ``new'' if this entry defines a constructor. More than one entry may exist with the same name if their argument lists differ (overloading).
args
The argument list for this function (see below).
desc
An optional short description of this operation (can be NULL).

The argument list ``args'' can be the empty string if the constructor or member function does not take any parameters, or a list of types separated by whitespace and commas (like in a function prototype). The following basic types are supported

int
An integer value.
uint
Unsigned integer value.
long
Long integer.
ulong
Unsigned long integer.
double
A double-precision floating-point value.
string
A string.
bool
A boolean value.
ptr
A void* pointer.
type
An object of class type or some class derived from type.

The last argument in the list can also be three dots ``...'' which is the same as in C, meaning that any number (including zero) of arguments of any type may follow.

  
Figure 1: Command table for the ``test'' class

Figure 1 shows the command table from one of the examples, the ``test'' object, which is some kind of a string class. First, there are two constructors, one without any arguments (the ``default constructor''), and one constructor that takes a parameter of its own class as parameter (the ``copy constructor''). You are not restricted by this choice of constructors; you need to define neither, and constructors are free to use any number and kinds of parameters.

The set and add member functions take any number of arguments. get just retrieves the object's value and does not need any parameters. copy copies the value of another object and, like the copy constructor, takes an argument of its own class.

You will notice that we define the parameters of member functions, but not their return types. This is because with Tcl, our return type is fixed to strings. However, helper functions exist that allow you to return numeric results or references to objects as well.

The purpose of these command tables is to relieve you from the effort of argument checking. Your Tcl-style constructor or the Tcl_Proc procedure will only be called if the arguments check out okay. At that time, a matching table entry will be determined; the number of this entry is also passed to your function, and you can then simply enter a switch statement with the table entry number.

You need to be careful when overloading member functions with different kinds of numeric arguments, for example when defining a function that can either take an int or double parameter. Because Tcl does not know about prototyping, we can only look at the argument string. For example, the string ``1'' can be bool, double, any of the integer types, and even a string.

The algorithm that tries to match an available signature dives down a tree, following the matching nodes, but doesn't do backtracking. Consider the following entries in a command table:

  { 1, "proc", "uint, int",    NULL},
  { 2, "proc", "long, double", NULL}

Calling this member with the parameters ``1 0.5'' would fail. Because the ``1'' is an unsigned integer, the algorithm narrows its search to all members that require an unsigned integer as first parameter. But in the next step, the algorithm notices that ``0.5'' is not an integer value and fails. Because the algorithm believes the first argument to be an unsigned integer, the second entry is not considered. If, however, the member was called with the parameters ``-1 0.5'', the second entry would be used, because ``-1'' is not an unsigned integer.

Such cases are usually easy to avoid. Member functions that take different kinds of numeric arguments tend to implement the same behaviour anyway, where it doesn't matter what kind of integer an argument is actually intended to be.

Interface functions

Both the constructor and the Tcl_Proc procedure take the same four arguments. The constructor by definition has no return value, Tcl_Proc must return an int. Hence, the declarations look like

HelloWorld::HelloWorld (Tcl_Interp *interp,
                        int argc, char *argv[],
                        int command)

The first argument is the Tcl interpreter. The second and third arguments are the parameter list. The first parameter is actually in argv[2]; the zeroth element is the name of the object, and the first element is the command name that is invoked. Fourth argument to the function is the number of the command that is to be executed, taken from the constructor or command table, respectively.

You will usually perform a switch on the command parameter, cast the arguments to the desired types, call the responsible member function, and return the return value.

Helper functions (or macros) help you to do the job of argument casting. Because the argument lists have already checked out okay, there is no need to check if these operations fail. The first argument to them all is the pointer to the current Tcl interpreter, the second one is the argument argv[i], and the return value is of the type in question.

TCL_OBJECT_ARG_INT(Tcl_Interp*, char*)

Casts the argument to an integer value.
TCL_OBJECT_ARG_UINT(Tcl_Interp*, char*)

Casts the argument to an unsigned integer.
TCL_OBJECT_ARG_LONG(Tcl_Interp*, char*)

Casts the argument to a long integer.
TCL_OBJECT_ARG_ULONG(Tcl_Interp*, char*)

Casts the argument to an unsigned long integer.
TCL_OBJECT_ARG_DOUBLE(Tcl_Interp*, char*)

Casts the argument to a floating-point value.
TCL_OBJECT_ARG_BOOL(Tcl_Interp*, char*)

Casts the argument to boolean (returned as a true or false integer).
TCL_OBJECT_ARG_STRING(Tcl_Interp*, char*)

Casts the argument to a string pointer. Actually, this macro simply returns its second parameter.
TCL_OBJECT_ARG_PTR(Tcl_Interp*, char*)

Casts the argument to a void* pointer and requires an argument that was returned by a previous call to TCL_OBJECT_RETURN_PTR() (see below).
TCL_OBJECT_ARG_OBJECT(Tcl_Interp*, char*, type)

Casts the argument to a pointer to a class of the given type, requiring an argument of that type, or any compatible type (a derived class) returned by a previous call to TCL_OBJECT_RETURN_OBJECT() (see below). Returns a type* pointer.


Performs a number of casts, similar to sscanf. arglist is list of argument types like the args element of a command table. The argument list in argv is interpreted according to the types found there. For each parameter, a pointer to the result type must be given as additional parameter: The argument list passed here must be zero-based, meaning that the first parameter must be found in argv[0]. When processing arguments in Tcl_Proc, the actual parameter list starts at argv[2], so you would call the function with argc-2 and argv+2.

Similar functions exist to return values of different kinds. Like their above counterparts, they take as first parameter the current interpreter. The functions produce a string representation of the second parameter, and set this string as the interpreter's result. All of them return TCL_OK.

TCL_OBJECT_RETURN_INT(Tcl_Interp*, int)

Returns an integer value.
TCL_OBJECT_RETURN_UINT(Tcl_Interp*, unsigned int)

Returns an unsigned integer value.
TCL_OBJECT_RETURN_LONG(Tcl_Interp*, long)

Returns a long integer value.
TCL_OBJECT_RETURN_ULONG(Tcl_Interp*, unsigned long)

Returns an unsigned long value.
TCL_OBJECT_RETURN_BOOL(Tcl_Interp*, int)

Returns a boolean value (true or false).
TCL_OBJECT_RETURN_DOUBLE(Tcl_Interp*, double)

Returns a floating-point value.
TCL_OBJECT_RETURN_STRING(Tcl_Interp*, char*)

Returns a copy of a string (so the pointer may point to volatile memory).
TCL_OBJECT_RETURN_PTR(Tcl_Interp*, void*)

Returns an arbitrary pointer; useful for structures and classes that are not themselves derived from Tcl_Object but are needed here and there as parameter or return value.
TCL_OBJECT_RETURN_OBJECT(Tcl_Interp*, Tcl_Object*)

Returns a reference to the given object (Tcl_Object is a base class for every Tcl-visible class).
TclObj_MkResult(Tcl_Interp*, char *arglist, ...)

Composes a return value from one or more values according to the list of data types given in arglist, which is of the same kind as in a command table. For each argument given in the arglist, an appropriate value must be given in the parameter list: The result will be a list of all the arguments; the Tcl program can use the list commands to extract the individual items.

  
Figure 2: A copy operator for the ``HelloWorld'' class

  
Figure 3: Alternative implemenation of the copy operator

Using these helpers, the two necessary interface functions are easy to implement. Figure 2 shows a complete example, the Tcl_Proc function of the HelloWorld class. It provides the ``hello'' and ``world'' members, which just return the strings returned from the corresponding member function. The third member, a copy operator, is a little longer. First, it casts the argument to the desired type. Then, the (compiler-generated) assignment operator is invoked, and finally a reference to the object itself is returned.

To demonstrate the usage of the more generic functions, an alternative implementation of the copy operator is given in figure 3.

You are not restricted to use the mentioned helper functions. You are free to use other means of extracting the data out of the arguments, and you can also set the result yourself, using the functions provided by Tcl.

Although you are guaranteed that Tcl_Proc is never called with a command value different from the values of the corresponding command table, you should call the macro TCL_OBJECT_NO_MATCH(Tcl_Interp*) with the current Tcl interpreter as parameter at the end of the function. It serves as emergency exit and prevents a compiler warning.

Initializer Function

The fourth argument of the TCL_OBJECT_DECL_BODY() macro can be NULL, or the name of an initializer function to call when the class is loaded into the interpreter. Apart from setting up your data structures, you can also use this function to make static objects available to the Tcl level.

  
Figure 4: Initializer for the ``fraction'' class

For example, the ``fraction'' class wants the constant of one seventh to be available by default. Figure 4 shows how this is done. The initializer function must be declared with ``C'' linkage and receives the current interpreter as parameter.

From the class Tcl_Object, the Tcl_Enable member is inherited, which makes the object visible on the Tcl level. Usually, this member is called automatically when an object is returned from Tcl_Proc(), but here you need to call the function explicitely. The second parameter is the name you want to assign to the object.


[Previous: Introduction] [Next: The Tcl Domain]
[Up: Using C++ objects with Tcl]

Frank Pilhofer
Wed Mar 12 14:37:08 MET 1997