mkClasses 1.0 - Object orientated programming with Tcl/Tk 8.
Introduction
Commands
Notes
Examples
Installation
Changes
Author
mkClasses is a collection of new Tcl commands that provides a(nother) way of object oriented programming with TCL. With mkClasses, classes and methods can be defined and objects be created with private and class-common variables. In addition, classes can inherit the functionality of other classes.
This extension has not been designed to look as similar to C++ as possible. Also there is no implementation for private or protected member functions. Though possible, it would have decreased performance.
Creates a class. If superclasses are specified their members will be automatically inherited. Arguments may be defined like with a regular procedure. The constructor script will be evaluated every time a new object of this class is created, using the specified arguments. The destructor script will be evaluated every time an object of this class is destroyed without any arguments.
For each created class two commands will be created: Assuming the name of the created class is "MyClass" there will be the commands "MyClass" and "~MyClass".
Objects of class MyClass can be created like "MyClass MyObject Args". A new procedure "MyObject" is created and the constructor of MyClass is evaluated with the given arguments for object MyObject. Within the constructor member functions can be called with "MyObject Member Args" or "[this] Member Args" (see below for the this command).
An object is deleted using "~MyClass MyObject". MyObject must be of type MyClass. The destructor of MyClass is evaluated and the procedure "MyObject" is deleted.
Destroys a class and all defined member functions of this class. The command will return an error if any objects of this class exist or if the class is used as a superclass.
Defines a member function for a given class. Like with a procedure, arguments and a body have to be defined. If a member with the same name has been inherited from a superclass, it is overwritten. Objects of the given class can address their members with the syntax "MyObject Member Args".
Inherited members can always be addressed with "MyObject Superclass:Member Args", regardless if they have been overwritten or not. Since constructor and destructor are also members with the special names "+" and "-", a superclass' constructor or destructor can be called with "MyObject Superclass:+ Args" and "MyObject Superclass:-" resp.
If the member name equals "unknown", this member is automatically called each time an object calls an unknown, i.e. not defined, member function. It is also called, if a member name is ambiguous. The member "unknown" must have exactly two arguments: The first one will contain the name of the unknown member function, the second one all arguments as a list.
Destroys a member of a given class. The command will return an error if any objects of the member's class exist or if the class is used as a superclass.
The this command represents the current object and can only be used "within an object", i.e. within member functions as well as constructors and destructors. It returns the name of the current object.
Sets or retrieves an object variable. Similar to the standard "set" command, it can be called with the name of a variable to retrieve the value of this variable or to set it to a new value. Object variables are specific to the object which manipulates them. They can be accessed in any member function as well as in the constructor and destructor of the object, but they cannot be retrieved from any other object, even if it is of the same class.
Internally object variables are held in a global array which is of the same name as the object. If the object's name is "MyObject" there is also a global array named "MyObject" holding all object variables, if there are any. Accessing this array directly is in most cases not necessary nor recommended. However, it provides a way to access object variables from the "outside", e.g. if a binding script is evaluated, which is done on global level and therefore outside of any object.
Unsets object variables. Although the command's name seams nonsense, it is a nice equivalent to the standard command "unset". Internally the object variables to be unset are just deleted from the global array with the object's name.
Returns a list of all currently defined object variables. If a pattern is provided, it will be interpreted due to the "glob" style rules like in the standard command "string match".
Returns 1 if the given object variable is defined, otherwise 0.
Sets or retrieves a class-common variable. Similar to the standard "set" command, it can be called with the name of a variable to retrieve the value of this variable or to set it to a new value. Class-common variables are specific to the class of the object which manipulates them. They can be accessed in any member function as well as in the constructor and destructor of all objects with the same class, but they cannot be retrieved from an object of a different class.
Internally class-common variables are held in a global array which is of the same name as the object's class. If the object's class is "MyClass" there is also a global array named "MyClass" holding any common variables. Accessing this array directly is in most cases not necessary nor recommended. However, it provides a way to access these variables from the "outside", e.g. if a binding script is evaluated, which is done on global level and therefore outside of any object.
Unsets class-common variables. Like with "unmy", it is an equivalent to the standard command "unset". Internally the variables to be unset are just deleted from the global array with the name of the object's class.
Returns a list of all currently defined class-common variables. If a pattern is provided, it will be interpreted due to the "glob" style rules like in the standard command "string match".
Returns 1 if the given class-common variable is defined, otherwise 0.
Provides information about currently defined classes and member functions, as well as of existing objects or eventually inherited classes. Cinfo accepts the following options:
cinfo class Object
Returns the class name of a given object.
cinfo classes
Returns all currently defined classes.
cinfo objects ?Class?
Returns all currently existing objects. If a class is provided, returns only the objects of this class.
cinfo members Class
Returns all currently defined members of a given class. The constructor is represented as "+",
the destructor as "-". This allows for retrieving information with the options "body" and "args".
cinfo isclass Class
Returns 1 if the given class exists, 0 otherwise.
cinfo isobject Object
Returns 1 if the given object exists, 0 otherwise.
cinfo ismember Class:Member
Returns 1 if the given member exists, 0 otherwise.
cinfo body Class:Member
Returns the body of the specified member function.
cinfo args Class:Member
Returns the arguments of the specified member function.
cinfo superclasses Class
Returns all superclasses of a given class or an empty string, if there are none.
Raises an exception similar to the standard "error" command. It can only be used within member functions, constructors and destructors, since the object's class and the object's name is inserted in front of the error text prior to printing it out and terminating all further execution. As with the "error" command, an exception raised by "throw" can be caught with "catch".
Increments an object variable by 1 or by a given increment. This command is similar to the standard "incr" command, but it operates directly on object variables.
Considers the specified object variable to be a list and returns the element indicated by Index. This command is similar to the standard "lindex" command, but it operates directly on private variables. Index is 0-based and can be the string "end", representing the last element of the list.
Considers the specified object variable to be a list and returns the number of elements in this list. This command is similar to the standard "llength" command, but it operates directly on private variables.
Considers the specified object variable to be a list and searches for Pattern. Pattern matching is indicated by the parameter Mode, which can be either -exact, -glob or -regexp. The default is -exact. The command returns the 0-based index of the first element that matches Pattern, otherwise -1.
Concatenates all given lists to the specified object variable. Unlike the standard "concat" command it directly manipulates the object variable and returns the new value.
Considers the specified object variable to be a list and appends all given values as new elements to this list. This command is similar to the standard "lappend" command, but it operates directly on object variables.
Considers the specified object variable to be a list and inserts all given elements at the position indicated by Index. Index is 0-based and can be the string "end", representing the last element of the list. Unlike the standard "linsert" command it directly manipulates the object variable and returns the new value.
Considers the specified object variable to be a list and returns all elements from the From's element to the To's element. From and To are 0-based and can be the string "end", representing the last element of the list.
Considers the specified object variable to be a list and deletes all elements specified by the Indexes. Indexes are 0-based and can be the string "end". The command directly manipulates the object variable and returns the new value.
Considers the specified object variable to be a list and replaces all elements from First to Last index with the given elements, if any. From and To are 0-based and can be the string "end", representing the last element of the list. Unlike the standard command "lreplace", it directly manipulates the object variable and returns the new value.
Assigns all elements in List to the given object variables, starting from the left. That is, the first element in List is assigned to the first given object variable, and so on. If there are more elements in List than variables specified, the remaining elements will be ignored. If there are more variables specified than elements in list, the remaining variables will be left unchanged.
Returns the values of all specified object variables as a list.
When defining classes and member function and creating objects, one should be aware of the "namespace pollution" that comes with it. Basically these considerations are similar to those that apply to widgets in wish/TK.
Remember that each defined class creates two new commands and eventually a global array for the class-common variables. Each created object creates a new procedure with the object's name and eventually a global array for the object variables. Unlike regular local variables, all created commands are global by nature (within their namespace), so one must be careful in creating and destroying objects.
To avoid naming conflict, it is recommended to make use of the "this" command when creating objects within other objects. E.g. if an object creates other objects by preceeding "[this]-" to their names, there will be no conflict on global level (provided the dash "-" is not used within object names elsewhere). This method is similar to the widget hierarchy in wish/TK.
In addition, objects must be explicitely destroyed, either at the end of the procedure that created the objects, or, if the object has been created by another object, in the creating object's destructor.
A class can inherit the functionality of other classes, called superclasses. In this case the new class will inherit all member functions of the superclasses. Inherited member functions can be overwritten exactly once. By doing so, the member function will execute its own code instead of the inherited member's code. Overwritten member functions can still be called using the fully qualified name, i.e. the member name preceeded by the superclass' name and a colon.
Note that no constructor, destructor or member of a superclass is executed automatically. The class that inherits a superclass has to do it explicitely by calling the superclasses' constructors, destructors and member functions. Remember that constructors and destructors are represented as regular members with the special names "+" and "-".
Because of a bug, inherited members cannot be deleted. It is instead recommended, to redefine the member and let it raise an error.
A class that simply prints out either a default text provided in the constructor or a special text provided with the print function would look like this:
% class PrintText { sDefault } { if { $sDefault == {} } { throw "No empty strings, please!" } my Default $sDefault } {} ;# no destructor code % member PrintText:print { {sText {}} } { if { $sText == {} } { puts [my Default] } else { puts $sText } } % member PrintText:getDefault {} { return [my Default] } % PrintText pt {} PrintText pt: No empty strings, please! % PrintText pt "My default text" My default text % pt print My default text % pt print "Special text" Special text % PrintText pt2 "Another object, another default text" Another object, another default text % pt getDefault My default text % pt2 getDefault Another object, another default text % ~PrintText pt % ~PrintText pt2
A class that uses the functionality of PrintText but extending it by printing out the time ahead of each text and by making it possible to change the default text after the object has been created, could look like this:
% class BetterPrint:PrintText { sDefault } { # calling superclass constructor [this] PrintText:+ $sDefault puts "I am [this] of class [cinfo class [this]]" } { puts "[this] is gone" } % member BetterPrint:setDefault { sDefault } { # here is a new member of BetterPrint my Default $sDefault } % member BetterPrint:print { {sText {}} } { # this overwrites the inherited member PrintText:print puts -nonewline "[clock format [clock seconds] -format %H:%M]: " # but we still can call it: [this] PrintText:print $sText } % BetterPrint bp FirstDefault I am bp of class BetterPrint % bp getDefault FirstDefault % bp setDefault SecondDefault SecondDefault % bp print 08:15: SecondDefault % bp print "Now with time stamp" 08:16: Now with time stamp % ~BetterPrint bp bp is gone
mkClasses is written in C and comes with a DLL for Windows. On Unix, the package needs to be compiled into a shared library first (see below). mkClasses works with Tcl/Tk version 8.0 and higher. It has been stubs-enabled with version 8.2.
To install, place the directory "mkClasses1.0" in one of the directories contained in the global Tcl variable "auto_path". For a standard Tcl/Tk installation, this is commonly "c:/program files/tcl/lib" (Windows) and "/usr/local/lib" (Unix).
To compile the package, just provide the correct path to "tcl.h" and link against "tcl8x.lib" (Windows) or "libtcl8x.so" (Unix) respectively. If you use stubs, define USE_TCL_STUBS and link against "tclstub8x.lib" (Windows) or "libtclstub8x.a" (Unix) instead.
For Visual C++, the following command should work:
cl /I c:/progra~1/tcl/include /D USE_TCL_STUBS /c mkClasses10.c link c:/progra~1/tcl/lib/tclstub83.lib /dll mkClasses10.obj
On Linux 2.2, this here works fine:
gcc -shared -DUSE_TCL_STUBS -ltclstub8.3 -o mkClasses10.so mkClasses10.c
Test the installation by opening a tclsh or wish and entering "package require mkClasses". The string "1.0" should appear. If it fails, "cd" into the directory "mkClasses1.0" and load it directly with "load ./mkClasses10.dll" (Windows) or "load ./mkClasses10.so" (Unix). If no error occured, it succeeded and something must be wrong with the location of "mkClasses1.0".
No changes - Initial version.
Michael Kraus
mailto:michael@kraus5.de
http://mkextensions.sourceforge.net