Introduction to JNIPort

JNIPort is the name for a collection of packages that allow Java code to be invoked from Smalltalk. The implementation builds on the Java “native” interface (called JNI), and then provides the superstructure to let you to manipulate Java objects pretty much as if they were Smalltalk objects. The main advantages of this are:

    • You can supplement Smalltalk's libraries with the rather extensive Java libraries from Sun and other vendors.
    • You can use the normal, interactive, way of playing with objects and code in workspaces to learn about and prototype Java operations. JNIPort lets you talk to your Java objects, just as you can to Smalltalk objects, rather leaving them hidden away behind a dead wall of syntax compounded by the edit-compile-run fallacy.

However, please do read the section on the Problems and Limitations of JNIPort.

JNIPort works by talking to a “real” running JVM, rather than by executing the Java bytecodes in the Smalltalk VM. This approach has both advantages and disadvantages. One of the big advantages is that JNIPort is always complete and up to date with the latest Java version. One downside is that the performance advantages of running Java bytecodes on a high-performance JVM can be lost in the overhead of crossing the “boundary” between the Smalltalk VM and the Java VM. A bigger disadvantage is that JNIPort is very exposed to the the JVM, which is just a DLL sharing the same process space as Smalltalk. Any peculiarities of the way it does things can affect Smalltalk; for instance calling java.lang.System.exit() will duly cause your Smalltalk session to exit! (So don't do that.)

JNIPort comes with a fair amount of documentation. It is not intended to be exhaustive, but is just to help you get oriented with the system. It's less well structured than I would like, but good places to start might be:

    • If you like to start with hands-on examples, then try looking at the walkthroughs of installing JNIPort for Dolphin Smalltalk and for VisualWorks, and this example of using it to read a ZIP file.
    • If you prefer to start with the concepts, then there are three different views of how the system fits together. A description of the layers of JNIPort; a description of the main players in the community of JNIPort objects and how they interact; and a description of the features of JNIPort and how to use it.
    • If you are going to use ghost classes (which is the highest level of JNIPort and the most convenient to use) then you should probably take at least a quick look at the Ghost Classes section. A few more examples are listed here.

The rest of this overview is a very short example of using JNIPort with ghost classes.

First we need a connection to the JVM. There are various ways of configuring and starting the JVM, but here we'll just assume that one is running already.

jvm := JVM current.

The JVM instance is the central point of JNIPort. It is responsible for starting up and shutting down the Java DLL. It also acts as the switchboard through which the various objects in the system can find each other. In particular it is where you go to find a handle on a Java class.

Each Java class is represented by a unique class static. These objects have methods corresponding to the class side (“static”) members of the Java class; they also have methods corresponding to the Java classes' constructors.

We'll start with java.lang.System since that's a well known class with some interesting static methods:

class := jvm findClass: #'java.lang.System'.

That has looked for, and possibly created, the class static that stands for java.lang.System. Now for a couple of method calls, first we get the current time (in milliseconds):

class currentTimeMillis. "--> 1045217556089"

which is equivalent to the Java code:

java.lang.System.currentTimeMillis();

Only, of course, you are invoking it in a interactive workspace — which you can't do normally in Java. What has actually happened is that #currentTimeMillis invoked a ghost method, which is a dynamically-generated method that uses the JNI APIs to forward the call to the underlying Java method. The class object is actually an instance of an ephemeral class that was generated by JNIPort to hold the forwarding methods (these ephemeral classes are called "ghost classes" in JNIPort). You don't have to use ghost classes to use JNIPort, but they are convenient.

Now we'll call a method with a parameter:

string := class getProperty_String: 'java.vm.vendor'. "--> a java.lang.String(Sun Microsystems Inc.)"

This time we called a method that takes a String argument (automatically converted from a Smalltalk String) and answers an instance of java.lang.String. If we wanted to convert the answer into a proper Smalltalk string then we'd send it #asString, but for now we'll leave it as a Java object.

We can send it messages corresponding to the Java methods in java.lang.String, e.g:

string substring_int: 3 int: 7. "--> a java.lang.String( Mic)"

Notice the way we pass multiple integer parameters. Again this is using one of the automatically generated wrapper methods; you are not limited to using the generated methods, you can also set up normal Smalltalk methods. JNIPort already comes with quite a lot of helper methods to give Java Strings (and other aggregates like arrays) a Smalltalk face. For instance Smalltalk-style iteration:

string do: [:each | Transcript print: each; space]. Transcript cr.

which will write $S $u $n $ $M $i $c $r $o $s $y $s $t $e $m $s $ $I $n $c $. to the Transcript.

JNIPort knows how to assign Java instances to the correct wrapper class. In the case of strings, JNIPort includes a class (called, imaginatively enough, JavaLangString) and this is where Smalltalk methods like #do: are defined. In this example we're using ghost classes, so the real class of our string object is a dynamically generated subclass of JavaLangString.

We can ask the string for its Smalltalk class:

string isKindOf: JavaLangString. "--> true"

Or we can ask it for its class static.

class := string static. "--> a java.lang.String.static(java.lang.String)"

The class static, remember, represents the Java class. Class statics are unique, so:

(jvm findClass: #'java.lang.String') == class. "--> true"

The class static is where the constructors live, so we can create a new string, in this case an empty string:

class new. "--> a java.lang.String()"

Or a copy of our original one:

class new_String: string. "--> a java.lang.String(Sun Microsystems Inc.)"

Or a copy of a Smalltalk string:

class new_String: 'Hello!'. "--> a java.lang.String(Hello!)"

There's lots more to tell, but I want to keep this example short. See the rest of the JNIPort documentation for details.