[Up: Using C++ objects with Tcl]
[Previous: Dynamic Loading] [Next: About this document ]
In the example/ subdirectory, you can find five classes that can be made available in Tcl. I suggest to look at their code while reading the descriptions, and to try out the examples for yourself. All these examples have their purpose; each highlights a different feature and different problems of the package.
This is a very tiny ``Hello, World'' class. It provides the two member functions hello, which returns the string ``hello'' and world, which returns ``world''. Both are made available to Tcl as operations without parameter. See figure 5 for a short sample session.
Because the class name is mixed case, we have put the class into a package of its own. Therefore, you must load the class using the package name pkg_helloworld.
Figure 5: Sample session with the ``HelloWorld'' class
An arithmetic class, providing a fraction data type with an integer numerator and denominator. The Tcl interface provides basic operations for addition, substraction, multiplication and division, plus a number of comparison operators. The ``value'' member function retrieves the object's value as a floating-point value, and ``numden'' produces a list with two members, the numerator and denominator.
Figure 6: Sample session with the ``fraction'' class
Function overloading is demonstrated with the assignment operator, which can take three different kinds of arguments, either it can copy the value of another fraction object, a single integer value (setting the denominator to 1) or two integers. See figure 6 for a short sample session.
You will notice that while the assignment kind of operators (like ``+='') are exported, the binary operators (``+'') are not. Why? The binary arithmetic operators have to return an object with the result value. However, the Tcl interface can only return references to objects. Consider the following potential code fragment to implement an addition operator:
fraction *other = TCL_OBJECT_ARG_OBJECT(interp, argv[2], fraction); fraction result = operator+ (*other); return TCL_OBJECT_RETURN_OBJECT(interp, &result);
However, because the ``result'' object is local, the reference returned to the Tcl level will be invalid, because the object it points to has been destroyed upon return from the function.
A different solution would be to create the return object on dynamic storage:
fraction *other = TCL_OBJECT_ARG_OBJECT(interp, argv[2], fraction); fraction *result = new fraction(operator+ (*other)) return TCL_OBJECT_RETURN_OBJECT(interp, result);
This would work. However, the Tcl program must then be aware of this fact, and must remember to destroy the returned ``temporary'' object sooner or later. Otherwise, more and more garbage would pile up.
The third idea is to return not the object, but its contents, similar to the ``numden'' operator. The other functions could then be overloaded with versions that take the two integer values as parameter. Still, this solution is not perfect, but it prevents garbage. But to split up the result of an operation (a list of two integers) into the two integers required by the parameter, an eval call is needed.
$x = [eval $y / $z]
This line of code would then divide the fraction object in ``y'' by the object in ``z'', and then assign the result to ``x''. Still, the workaround of having the binary operators return the object's contents, and having the operators take these contents as an argument, is not exactly state-of-the-art, and could be quite a difficult task for more complicated objects. If the object contains non-trivial members, this method would in fact have the same problems as the solution of returning a dynamically allocated object: the Tcl programmer would have to make sure that the destructor is called on the object.
Because a solution cannot yet be offered, only references to object are supported at this time.
This example demonstrates abstract base classes, virtual functions and inheritance. We define a generic class ``storage'', which implements a simple storage cell.
The only function common to all kinds of storage is that its value can be queried. Hence, this class defines a pure virtual function ``query'' which is supposed to return the value as a string. On the Tcl level, however, we provide a ``query'' member function for the storage base class, which returns the value returned from the virtual function.
We then derive three kinds of storage cells, int_storage, double_storage and string_storage. Each class provides an implementation of the query() function and a custom set function that takes a type-specific argument.
On the Tcl level, however, no implementation of query is needed, because it is inherited from the base class. Note that only the base class is derived from Tcl_Object, the others inherit the necessary functionality through indirect inheritance by being derived from the base class.
Figure 7: Sample session with the ``storage'' classes
See figure 7 for an example of using the storage classes from Tcl. While the set implementation is different for each of the objects, the query function is inherited from the base class, but can be invoked like any other member function.
Because the base class and the three derived classes are defined in the same module, a package is defined that initializes all of them. Load the module using a package name of pkg_storage.
The other examples were, well, examples. They were custom-designed to fit the necessary requirements. The question is, how well does this approach work with existing classes that were designed without having Tcl in mind?
As a demonstration, we add the necessary hooks to the ``ftp'' class from the socket++ library. In fact, this class turned out to be a perfect example of what you might be confronted with: completely undocumented and slightly buggy. Still, it was possible to integrate the class into Tcl without a single code change (apart from the bugfix). After learning how the class worked, the work was done in slightly more than one hour, not counting lunch.
Usually, some code must be added to a class to allow its integration into Tcl. We use a different approach here. In order to not change the existing code, we derive a new class from the original one, inheriting all its function. We then add the necessary modifications to the derived class.
Figure 8: Sample session with the ``tclftp'' classes
The tclftp class exports all the functions that make sense to ftp operations. You can open and close connections, browse directories, retrieve and upload files. For some reason, re-opening an object does not work; I suspect this is because of a bug in the original class. See figure 8 for a sample session.
This method of integrating derived classes allows you to work with classes without touching the original sources; you do not even need the source code at all. Still, there is a catch. Usually, objects could be passed from and to the Tcl level without spending a thought of where they came into existence. But imagine that in the existing and unchanged code, ftp objects are created.
This is not a problem as long as the object is only used in the C++ domain. But because the ftp base class does not inherit the necessary Tcl hooks, which are only available through the second derivation of tclftp, such an object could not be passed to the Tcl level. This is a rare problem, and in some cases where this problem arises, it is possible to ``repackage'' the base object into the derived object.
[Previous: Dynamic Loading] [Next: About this document ]
[Up: Using C++ objects with Tcl]