Tcl logo


Home
Getting-Started
Download
Mailing-Lists
Manual
FAQ
Links

i



Getting Started with Tcl/Java

Setting up Jacl


See the README file supplied with Jacl for the most up to date information about installing and running Jacl on your system. Jacl is significantly easier to install and get running when compared to Tcl Blend, since Jacl requires only a JVM.


Setting up Tcl Blend

Setting up and running Tcl Blend can be significantly more difficult than Jacl, since it depends on installed versions of both Tcl and Java. Make sure to read the README file included with Tcl Blend before installing.


The Java Package

Both Jacl and Tcl Blend provide a Tcl package named java. The package is loaded using the package Tcl command, it returns the version of the package that was just loaded. When the java package is loaded, it will create the commands used to interact with Java objects from Tcl (java::new, java::call, java::null, and so on).

    % package require java
    1.3.0

Interact with a Java Object in Tcl

In this example, a Java object of type java.lang.String is allocated using the java::new command and the Java object handle java0x1 is saved in a variable. The String object's charAt method is then invoked on the object using the saved handle. Note that the char primitive type returned by the charAt method is implicitly converted to a Tcl string value. The unset command is called on the variable that holds the handle to indicate that we are finished with the object so that it can be garage collected. This example assumes that the java package has already been loaded.

    % set jstr [java::new String "A Java String"]
    java0x1
    % $jstr charAt 2
    J
    % unset jstr

Evaluating a Tcl command from Java

Often, one is interested in executing a Tcl command from Java code. In this example, a Tcl interpreter is created and the Tcl string command is executed from Java. The interp.eval() method parses the input string and executes the string command. An integer result is then extracted from the interpreter result. The only tricky part of this example is properly handling the various types of TclExceptions that might be thrown.

import tcl.lang.*;

public class StringLengthTest {
    public static void main(String[] args) {
        int thestr_len = -1;
        String thestr = "noggy";
        Interp interp = new Interp();
        try {
            interp.eval("string length \"" + thestr + "\"");
            thestr_len = TclInteger.get(interp, interp.getResult());
        } catch (TclException ex) {
            int code = ex.getCompletionCode();
            switch (code) {
                case TCL.ERROR:
                    System.err.println(interp.getResult().toString());
                    break;
                case TCL.BREAK:
                    System.err.println(
                            "invoked \"break\" outside of a loop");
                    break;
                case TCL.CONTINUE:
                    System.err.println(
                            "invoked \"continue\" outside of a loop");
                    break;
                default:
                    System.err.println(
                            "command returned bad error code: " + code);
                    break;
            }
        } finally {
            interp.dispose();
        }

        System.out.println("string length was " + thestr_len);
    }
}

Executing a Tcl command from another Java Thread

One area where people often run into problems involves executing a Tcl command in another Java thread. One can't just call interp.eval() as we did in the example above. To understand why, we need to cover how the Tcl threading and event processing approach works. Typically, the main thread would create the Tcl interpreter and then go into a loop that would process Tcl events.

import tcl.lang.*;

public class MainLoop {
    public static void mainLoop() {
        Interp interp = new Interp();
        Notifier notifier = interp.getNotifier();
        while(true) {
	    // process events until "exit" is called.
	    notifier.doOneEvent(TCL.ALL_EVENTS);
        }
    }
}

Calling the mainLoop() method would use the calling thread to process Tcl events from that point forward. Now assume that we are running in another thread, and we want to be able to evaluate a Tcl command. It is tempting to just call interp.eval() from this other thread, but that would actually be a serious error. The eval method is not thread-safe, so invoking it from another thread could cause a crash. This strikes Java programmers as strange, they figure one could just add the synchronized keyword to the eval method and the problem would be solved. That is not the case however, as the following example shows.

import tcl.lang.*;

public class TwoEvals {
    public static void evalOne(Interp interp) {
        try {
            interp.eval("cmd_one");
            System.out.println("result one was " + interp.getResult());
        } catch (TclException ex) {
            // Handle Tcl exceptions here ...
        }
    }
    public static void evalTwo(Interp interp) {
        try {
            interp.eval("cmd_two");
            System.out.println("result two was " + interp.getResult());
        } catch (TclException ex) {
            // Handle Tcl exceptions here ...
        }
    }
}

Assuming that the eval() method was synchronized, we would still have a subtle problem if two different threads called the evalOne and evalTwo methods at the same time. The first thread could grab the monitor while the second thread would have to wait. When the first thread releases the monitor at the end of the eval() method it would create a race condition since the second thread might execute its eval() and change the interpreter result before the first thread can call interp.getResult(). Making the eval() method synchronized could also create deadlocking problems if the Tcl command that was executed called into other Java code which then tried to execute another Tcl command using eval(). The solution to both problems is to use the provided thread-safe event queue. The following example shows how to queue Tcl events in a thread safe way.

import tcl.lang.*;

public class TwoEvalEvents {
    public static void evalOne(final Interp interp) {
        TclEvent event = new TclEvent() {
            public int processEvent(int flags) {
                try {
                    interp.eval("cmd_one");
                    System.out.println("result one was " + interp.getResult());
                } catch (TclException ex) {
                    // Handle Tcl exceptions here ...
                }
                return 1;
	    }
        };
        interp.getNotifier().queueEvent(event, TCL.QUEUE_TAIL);
        event.sync();
    }
    public static void evalTwo(final Interp interp) {
        TclEvent event = new TclEvent() {
            public int processEvent(int flags) {
                try {
                    interp.eval("cmd_two");
                    System.out.println("result two was " + interp.getResult());
                } catch (TclException ex) {
                    // Handle Tcl exceptions here ...
                }
                return 1;
	    }
        };
        interp.getNotifier().queueEvent(event, TCL.QUEUE_TAIL);
        event.sync();
    }
}

The queueEvent() call will add the event to the queue of events that will be processed by the Notifier's doOneEvent() loop that we covered earlier. Since the eval() call will actually be made from the main thread when the event is processed, thread safety is ensured. Note that the optional sync() method simply suspends the calling thread until the event has been processed. If there is no reason to suspend the calling thread, then don't invoke the sync() method.