[Up: Using C++ objects with Tcl]
[Previous: Introduction] [Next: The Tcl Domain]
This section describes how to adapt your classes to work with the tclobj package and the available extensions that you might find useful.
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''.
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.
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);
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.
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:
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
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.
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.
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.
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.
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]