|
JNIPort for Dolphin Smalltalk |
|
Back to Goodies |
Calling from Java into SmalltalkJNIPort has a limited ability to allow Java code to callback into Smalltalk. I must emphasise that the operative word here is limited; the feature is not suitable for creating a tight integration between the two worlds because:
However, it is better than nothing.
An example of using it to display the Smalltalk class hierarchy in a Swing
The Callback QueueJava's “native”, as it were, way of calling out from Java to another language is to use “native method”, which are like normal Java methods except that a programmer uses JNI to implement the method body, rather than writing it in Java. Because of the problem with threads (which you should read if you are thinking of using callbacks at all), JNIPort does not directly support this way of working (although it is used internally by JNIPort). Instead it provides Java classes that maintain a queue of Requests and Notifications; you create a request or notification (collectively a callback, or callback object) and place it on the queue, Dolphin takes requests off the queue and processes them, then returns the answer (if any is required) to the caller. The section on threads talks about the implementation of this queue, this section is about how to use it. The section on the problem with threads provides much more detail, but it is important to realise that the way that the queue works depends crucially on the operating system thread that is using it. Dolphin executes all Smalltalk code on one OS thread, which I call the Smalltalk thread, and if that thread is executing Java code then no Smalltalk code can run at all. This has implications for anyone programming with callbacks. One consequence is that you have to be careful about deadlocks (the threads section has the details), another is that callbacks made from the Smalltalk thread are handled differently from those made by other threads. If Java code running on some other thread issues a callback, then the callback object is placed on the queue and then that thread either waits for a response, or just goes about its other business. Dolphin, running on the Smalltalk thread, will see the request, process it, and (optionally) return an answer to the caller. On the other hand, if the Java code was running on the Smalltalk thread in the first place, then it places the callback on the queue in the normal way, and then calls back (a JNI callback) into Dolphin where all the requests on the requests on the queue are handled before control returns to the original caller. (So the caller is blocked, as far as Java is concerned, even if it did not want to wait for an answer.) Callback Objects
Callback objects are instances of the Java classes
You supply the tag, originator, and parameter when you create a notification object
(there are also a bunch of convenience constructors that package
arguments up into the parameter object in various ways). You send a notification
by invoking its
The pattern is similar for requests, the important difference is that
Dolphin will supply either a return value or will record the fact that
an exception was thrown from the handler. The simplest way to get the return
value is to call the request object's
You can, instead, call the request object's The Callback RegistryPlease note: I am still unhappy with the details of how handlers are set and invoked — expect changes in future versions of JNIPort.
On the other side of the wall, in Smalltalk space, Java callbacks are
managed by the callback registry. It takes callback objects off
the queue, and then uses each request's tag to find the corresponding
handler. The handler is an object that understands
If the callback was a Notification, then the handler is evaluated in a context where all Smalltalk or Java exceptions are trapped and ignored. The result of evaluating the handler is also discarded.
If the callback was a request then the handler is evaluated in a
context where all Smalltalk or Java exceptions are trapped and recorded
in the request object. If no exception is thrown, then the result of
evaluating the handler is recorded in the request. This must
be a Java object or Once the handler has returned, JNIPort notifies the callback object that it is now “complete” which wakes up any Java thread that was blocked waiting for the result.
You find the callback registry by sending Java Events
Fortunately Java API's do not often make very much use of coding patterns
that would require callbacks from Java to Smalltalk; for example Java's
idiomatic pattern of iteration uses external iterators
( There is one glaring exception to this tendency: AWT/Swing “events”. Java (since version 1.4) has provided some support for handling events without having to code explicit subclasses (like inner classes, for instance), and JNIPort builds on that to provide a way to translate Java events into Smalltalk events. This part of Java is new enough to warrant a little background explanation.
In Java, an event is represented by a method in an interface. For instance the
window closing event is represented by the
The normal way that some other object “receives” the event is that
it (or one of its inner-class objects) implements the
Java 1.4 introduced a new way of creating handlers that is less inflexible,
Proxies and Invocation Handlers. A proxy is an adaptor
that is created to implement some interface by forwarding the event methods
to an invocation handler. The invocation handler is a object with the single
method,
JNIPort uses this to translate Java events into Smalltalk events.
To observe (in Smalltalk) events generated by some Java object, you first
find the event method represented as a
Next you ask it for an event forwarder. JNIPort uses the new Java features
to create a listener for the event (i.e. an object implementing the interface)
which forwards the event data (the
A side effect of Please note that the event's source must be represented by a canonical Smalltalk object, or else the event will be triggered off a newly created reference to the Java object each time, not from whatever one your code is observing. The last step is to add the forwarder object to the list of listeners of any Java objects that you are interested in. Once it has been added (you can and should re-use it for all the event sources) it will translate the event from Java space into Smalltalk space.
For example, say we have references to two Java objects, both of which
generate the event In Smalltalk we can create a forwarder for the ping event by finding the right method:
and asking it for an event forwarder:
We can then add the forwarder to both source objects' lists of listeners:
Which is enough to forward events into Smalltalk space. However we have to ensure that the Smalltalk events are triggered off the right wrapper objects:
before we can start observing the triggered events:
One warning: JNIPort uses the method object as the tag to identify the callback, which means that you can't have more than one forwarder for the same event. I can't think of any reason why you'd need more than one forwarder (since they all do the same thing), but if I ever do then I may remove the restriction. |
Copyright © Chris Uppal, 2003-2005
Java, JNI (probably), JVM (possibly), and God knows what else, are trademarks of Sun Microsystems, Inc.