Getting Started with Tcl/Java
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 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.
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.
|