Objective-Everything Release 5.  Copyright ©1994-1998 by TipTop Software, Inc.  All Rights Reserved.

  Objective-Tcl

This section discusses the Objective-Tcl object-oriented extension to Tcl.

Messaging

The Objective-Tcl runtime facilitates messaging Objective-C objects from an instance of an Objective-Tcl interpreter. Tcl has been extended with a message ``statement'' which allows the programmer to send messages to Objective-C objects.  The message statement syntax is similar to the syntax used in Objective-C:

ObjC: obj is an id Tcl: obj is a Tcl variable
[obj message] $obj message
[obj message:a1] $obj message: $a1
[obj message:a1 arg2:a2] $obj message: $a1 arg2: $a2

In Tcl the only data type is character string.  All Tcl arguments are converted into appropriate ObjC types before a message is sent, and the value returned is converted back into a Tcl value.

Here is an example:

 tcl% set obj [[NSObject alloc] init] 
 @NSObject@11e23c 
 tcl% $obj class 
 NSObject 
 tcl% $obj superclass 
 nil 
 tcl% $obj respondsToSelector: isEqual: 
 1 
 tcl% $obj isEqual: $obj 
 1 
 tcl% $obj release 
 tcl% $obj release 
 tcl% $obj release 
 receiver is illegal object: "@NSObject@11e23c" 

Objects being messaged can be either local or distributed (remote) objects.  (Distributed objects can live within the same process, or, more importantly, within another process, possibly running on a different host machine with a different architecture and a different operating system.)

Defining Classes, Categories, and Protocols

In addition to the messaging syntax described above, the core of Objective-Tcl consists of the following five commands which are used to define classes, categories, and protocols:

 class classname superclass ?protocols? ivars ?methods? 

The class command declares and defines a new class object.  It corresponds to a set of @interface/@implementation declarations in Objective-C.  ivars is an ObjC-style instance variable list specification.  methods is a list of method definitions.  See the method command below.  protocols is an optional list of protocols that the class is supposed to implement.

 category class ?protocols? methods 
 category object methods 

The category command adds and/or overrides methods of an already defined class.  protocols is an optional list of protocols that the category is supposed to implement.

In the second form, object is an instance object.  In this case this specific instance is extended with the methods in the list.  Method list should not contain any class methods.

Note: extend is a synonym for category.

 protocol protname ?subprotocols? methods 

The protocol command defines a protocol.  subprotocols is an optional list of protocols that the protocol includes.

 method prototype #body# 
 + prototype #body# 
 - prototype #body# 

The method command is valid only within the class, category, and protocol method lists.  When the method command is used within the protocol method list, the body argument must not be used.

The prototype is specified using ObjC syntax.  When a method is invoked, variables $self and $_cmd, as well as all the instance variables of the receiving object are accessible as local variables within the method body.

Note: You may omit the method keyword if you include a space " " character immediately after+ or -.

 super message... 

Invokes the specified method in a superclass.  Messages to super are only allowed within a method body.  Use msg_send_super outside method bodies.

 ex message... 

Invokes the previous method implementation.  For example, if you override a method in a category, you can use ex to invoke the previous method implementation.  Messages to ex are only allowed within a method body.  Use msg_send_ex outside method bodies.

 forget class ?selectors...? 

Undefines the most recent implementation of specified selectors in class.  If no selectors are specified, all ObjTcl-defined methods defined in class are forgotten.  The first character of each selector can be + or - to indicate whether the selector refers to a class or an instance method.  If you don't specify +, - is assumed.

Note: You can also define classes, categories, protocols, etc. using the cdef command.

Examples

 class MyObject Object { 
 tcl% class MyObject NSObject { 
 %-->   NSString *aString; 
 %-->   id anObject; 
 %-->   char *aCString; 
 %--> } { 
 %-->   method -(void)dealloc { 
 %-->     puts "$self $_cmd" 
 %-->     set aString [nil]   ; # release aString 
 %-->     set anObject [nil]  ; # release anObject 
 %-->     set aCString [NULL] ; # free aCString 
 %-->     super dealloc 
 %-->   } 
 %--> 
 %-->   method -(void)setStringValue:(NSString*)s { 
 %-->     puts "$self $_cmd $s" 
 %-->     set aString $s 
 %-->   } 
 %--> 
 %-->   method -(NSString*)stringValue { 
 %-->     puts "$self $_cmd" 
 %-->     return $aString 
 %-->   } 
 %--> 
 %-->   # etc. 
 %--> } 
 MyObject 
 # [MyObject object] is short for: [[[MyObject alloc] init] autorelease] 
 tcl% set obj [MyObject object] 
 @MyObject@a9ab4 
 tcl% $obj setStringValue: "Hello World!" 
 @MyObject@a9ab4 setStringValue: Hello World! 
 tcl% $obj stringValue 
 @MyObject@a9ab4 stringValue 
 Hello World! 
 tcl% unset obj 
 @MyObject@a9ab4 dealloc 

Memory management

Objective-Tcl takes care of automatically retaining and releasing objects for you.  Hence, you rarely need to be concerned about explicitly retaining and releasing objects.  Note, however, that when you define a class like in the example above, you should set all instance variables to [nil] so that they are released.

Autoconversion

If you have a Tcl string which you want to convert to a string (an NSString) object, you would normally do:

 [@ value] 

As described in the Type Conversion section, when a Tcl string value is converted to a C value of type id, if the Tcl value represents an object, that is the object value that is used.  The question is what happens if you pass in a Tcl value which does not represent an object.  Depending on what the Tcl_AutoConvert config variable is set to, the following will happen:

Note that this creates a possible ambiguity.  For example, what does string "nil" convert to?  Is it the nil object, or a [@ "nil"] NSString?  What does "NSObject" convert to? The NSObjectclass, or a [@ "NSObject"]NSString?

In the case this ambiguity arises, if Tcl_AutoConvertCroak is ON, an error is raised. If Tcl_AutoConvertCroak is OFF (the default behavior), a warning is printed, and [value]is assumed.

Finally note that you can always avoid this ambiguity by explicitly using [@ value] or [value].  However,the default behavior will, in most cases, do exactly what you intend to do and let you get away without giving much consideration to the underlying conversion process.

Interactors and Shells

The Objective-Tcl system introduces the concept of interactors.  An interactor is an object which ``asks'' an interpreter to evaluate some Objective-Tcl text; i.e., an interactor invokes an eval method of an Objective-Tcl interpreter object.  On the other side, interactors typically take user keystrokes, pack keystrokes into text strings to be evaluated, and present results of the evaluation back to the user.

Currently, two interactors are implemented: the TTY and the AppKit interactor.  The TTY interactor (TTInteractor) simply reads text from the standard input, evaluates it, and prints the evaluation results.  The local Objective-Tcl shell, objtclsh which is the equivalent of tclsh, simply creates an instance of the Objective-Tcl interpreter and invokes a TTY interactor on it (this is done in ObjTcl_Main()).  Hence, the minimal shell main() would look like:

 int main(int argc, char **argv) 
 { 
   id interp=[[TTObjTclInterp alloc] init]; 
   id interactor=[[TTInteractor alloc] init]; 
   [interactor setInterp:interp]; 
   [interactor runModal:YES]; 
   return 0; 
 } 
See Examples/sh for more info.

The AppKit interactor (TTWInteractor) is an interactor which runs in a window, which is appropriate for AppKit-based applications.  It supports command advanced editing and command history.

Distributed Objective-Tcl Shell

The remote (distributed) Objective-Tcl shell, called robjtclsh, uses the TTY interactor just like objtclsh. However, whereas objtclsh creates an interpreter, robjtclsh connects to an interpreter within an already-running application and uses the TTY interactor to send evaluation requests to the interpreter within the host application.  See Examples/rsh for more info.

The capability to automatically export an Objective-Tcl interpreter as a distributed object server is provided in the system.  This allows one application (e.g., such as robjtclsh) to connect to an interpreter within another already-running application, and to directly interact with the objects within the remote application through the interpreter.

For example, to connect to the ObjTcl interpreter in InterfaceBuilder (make sure that IB is running and that ObjTclPalette.palette is loaded):

 robjtclsh IBInterp 

Then, you can access objects within IB:

 $NSApp showInfoPanel: nil 
 $NSApp appName 
 unparse $NSApp 

You can also define classes, etc.  NOTE: To get out of robjtclsh hit Ctrl-C.

If you want to export a Tcl interpreter, here is how you do it.  The following example is a simple ObjTcl server to which you can connect from robjtclsh:

 set rv [TTReceptionist sharedInstance] 
 $rv setConnectionName: [@ "TestConn"] 
 $rv setObject: $interp forKey: [@ "ObjTclInterp"] 
 # If you are not running from AppKit, you have to run explicitly: 
 [NSRunLoop currentRunLoop] run 

Now, to connect to the ObjTcl interpreter:  robjtclsh TestConn

Multi-Interpreter Support

Tcl is not thread-safe.  As a consequence, Objective-Tcl is not thread-safe.  However, Objective-Tcl allows multiple interpreters to be used within a single single-threaded process.  When you define methods in ObjTcl, the scope of method definition is the interpreter in which it is defined.

At runtime, one interpreter is designated as the default interpreter.  Usually, this is the first interpreter instance that gets created.  To get this instance from your ObjC code, and create it if it does not exist, simply call:

 [TTObjTclInterp getDefaultInterp] 

Dynamic Loading

In general, Objective-Tcl programs are mixtures of compiled and interpreted code.  In addition to class libraries which are linked into the main executable and the interpreted Objective-Tcl class/category definitions, separately compiled Objective-C code can be loaded at runtime.  The dyload Tcl command and the corresponding ObjTcl_LoadClass() C-function load the specified modules and link the compiled classes and categories from the modules into the runtime system.

 dyload module ?module ...? 

In addition, the Tcl autoloading mechanism is extended to support the loading of Objective-Tcl-defined classes and compiled classes (.a, .bundle, or .so files).  If an undefined class is referenced, either from Objective-Tcl or from Objective-C, the appropriate Objective-Tcl class definition will be loaded using the source command, or an object module containing the compiled class will be linked with the runtime system using the dyload command.  The auto_mkindex command in $tcl_library/init.tcl generates an appropriate tclIndex file with dyload commands.  See the Tcl documentation for more info about the Tcl autoloading mechanism.

There are several ways how a class definition (ObjC or ObjTcl) can be loaded from Objective-C:

     [[TTObjTclInterp getDefaultInterp] evalFile: filePath]; 
     with explicit filePath. 

     TTClass_Load("MyClass"); 

This will attempt to autoload MyClass in ObjTcl and any other languages currently loaded.

     set auto_path "$auto_path dir1 dir2 ..." 

Then, simply force your classes to be loaded by evaluating:

     MyClass 

in ObjTcl, or by calling:

     objc_getClass("MyClass"); 

in ObjC.  Both of these actually invoke TTClass_Load().

Note that the directories need to be "indexed" so that the ObjTcl autoload mechanism can find the appropriate ObjTcl file for each undefined class.

Also note that when a NIB file is loaded, all classes are obtained using the objc_getClass() function.  Hence, when you load a NIB file and you want to take advantage of the autoload mechanism, just make sure that $auto_path is correct and that the directories listed in $auto_path are indexed.

Debugging Support

In release 5 of Objective-Tcl, a debugger is not bundled with ObjTcl.  This way you are not restricted by the bundled debugger, but instead you can use your favorite Tcl debugger.

Interface Builder Support

You can use Objective-Tcl with InterfaceBuilder exactly the same way you use ObjC with IB.  See Examples/Drawing.  In addition, Objective-Tcl contains some additional IB support.

Most notably there is a palette (ObjTclPalette.palette).  Note that you must load ObjCorePalette prior to loading ObjTclPalette.  The IB palette contains an interpreter object which you can drag into your nib file and that way create an instance of TTObjTclInterp.

When the ObjTcl palette is loaded, file ObjTclPalette.palette/paletterc.tcl is executed.  You can add your own ObjTcl code into this file.

1. Default interp

ObjTcl palette contains an ObjTcl interpreter object which you can drag into your nib file.  Each instance of TTObjTclInterp that you create this way will be allocated and initialized at runtime, i.e., when you go into the test mode Document->TestInterface (Cmd-r) or when you load the nib file in your application.

However, most often you will just want to use the default application interpreter.  In your code you use [TTObjTclInterp getDefaultInterp] to obtain the default interpreter.  In IB, to insert the default application interpreter in your nib file simply drag an interpreter from the palette, and make sure that the "Default Interp" switch is ON.

Now, when you go into the test IB mode, the default application interp in your nib file is actually the IB application interp (you are running your nib in IB!).  This is the same interp that you connect in robjtclsh by typing: robjtclsh IBInterp.

When you load your nib file from your code, the default application interp object in the nib file will be the same as your app's default interp.

2. Connecting Tcl globals

To connect an ObjTcl global variable to an object, simply Ctrl-drag from an ObjTcl interp to the object and type a global variable name.  Here is an example:

Hit Cmd-n to open a new nib file, and drag-and-drop the default application interp into object suitcase.  Drag a button and a text field into the app window.  Ctrl-drag from the default interp to the button, type "but" for "Tcl var name:" and hit Return.  Ctrl-drag from DefaultInterp to the text field, type "tf" and hit Return.  Hit Cmd-r to go into the test interface mode.  Remotely connect to IBInterp (type robjtclsh IBInterp in a shell) and type:

 $tf setStringValue: "Hello World!"
 $but setTitle: "XYZ" 

When you connect a global to a control-type object, and the object does not have its target or actions defined, you can conveniently define its action method in the interpreter startup script.  Simply double-click on the connection in the connection inspector, and write your action method implementation.

3.Archiving interpreters

When a NIB file is saved, interpreter objects are archived in the nib file; for each interpreter its global variable connections are saved.  At runtime, when you load a NIB file into your application, global variable connections stored in the NIB file are reestablished.  Also, any startup scripts associated with the interpreter will be executed at this time.


[previous][contents][next]