Chapter 18. Using Java with Other Languages

18.0 Introduction

Java has several methods of running programs written in other languages. You can invoke a compiled program or executable script using Runtime.exec(), as I’ll describe in Recipe 18.1. There is an element of system dependency here, because you can only run external applications under the operating system they are compiled for. Alternatively, you can invoke one of a number of scripting languages (or “dynamic languages”)—running the gamut: awk, bsh, Clojure, Ruby, Perl, Python, Scala—using javax.script, as illustrated in Recipe 18.3. Or you can drop down to C level with Java’s “native code” mechanism and call compiled functions written in C/C++; see Recipe 18.6. From native code, you can call to functions written in just about any language. Not to mention that you can contact programs written in any language over a socket (see Chapter 13), with HTTP services (see Chapter 13), or with Java clients in RMI or CORBA clients in a variety of languages.

There is a wide range of other JVM languages, including:

  • BeanShell, a general scripting language for Java.

  • Groovy is a Java-based scripting language that pioneered the use of closures in the Java language ecosystem. It also has a rapid-development web package called Grails and a build tool called Gradle (see Recipe 1.8). Gradle is also used as the build tool in modern Android development.

  • Jython, a full Java implementation of Python.

  • JRuby, a full Java implementation of the Ruby language.

  • Scala, a “best of functional and OO” language for the JVM.

  • Clojure, a predominantly-functional Lisp-1 dialect for the JVM.

  • Renjin (pronounced “R Engine”), a fairly complete open source clone of the R statistics package with the ability to scale to the cloud. See Recipe 11.5 for an example using renjin.

These are JVM-centric and some can be called directly from Java to script or vice versa, without using javax.script. A list of these can be found at Wikipedia.

18.1 Running an External Program from Java

Problem

You want to run an external program from within a Java program.

Solution

Use one of the exec() methods in the java.lang.Runtime class. Or set up a ProcessBuilder and call its start() method.

Discussion

The exec() method in the Runtime class lets you run an external program. The command line you give is broken into strings by a simple StringTokenizer (see Recipe 3.1) and passed on to the operating system’s “execute a program” system call. As an example, here is a simple program that uses exec() to run kwrite, a windowed text editor program.1 On Windows, you’d have to change the name to notepad or wordpad, possibly including the full pathname; for example, c:/windows/notepad.exe (you can also use backslashes, but be careful to double them because the backslash is special in Java strings):

public class ExecDemoSimple {
    public static void main(String av[]) throws Exception {

        // Run the "notepad" program or a similar editor
        Process p = Runtime.getRuntime().exec("kwrite");

        p.waitFor();
    }
}

When you compile and run it, the appropriate editor window appears:

$ javac -d . ExecDemoSimple.java
$ java otherlang.ExecDemoSimple # causes a KWrite window to appear.
$

This version of exec() assumes that the pathname contains no blanks because these break proper operation of the StringTokenizer. To overcome this potential problem, use an overloaded form of exec(), taking an array of strings as arguments. Example 18-1 runs the Windows or Unix version of the Firefox web browser, assuming that Firefox was installed in the default directory (or another directory that is on your PATH). It passes the name of a help file as an argument, offering a kind of primitive help mechanism, as displayed in Figure 18-1.

Example 18-1. main/src/main/java/otherlang/ExecDemoNS.java
public class ExecDemoNS extends JFrame {
    private static final String BROWSER = "firefox";

    Logger logger = Logger.getLogger(ExecDemoNS.class.getSimpleName());

    /** The name of the help file. */
    protected final static String HELPFILE = "./help/index.html";

    /** A stack of process objects; each entry tracks one external running process */
    Stack<Process> pStack = new Stack<>();

    /** main - instantiate and run */
    public static void main(String av[]) throws Exception {
        String program = av.length == 0 ? BROWSER : av[0];
        new ExecDemoNS(program).setVisible(true);
    }

    /** The path to the binary executable that we will run */
    protected static String program;

    /** Constructor - set up strings and things. */
    public ExecDemoNS(String program) {
        super("ExecDemo: " + program);
        this.program = program;

        Container cp = getContentPane();
        cp.setLayout(new FlowLayout());
        JButton b;
        cp.add(b=new JButton("Exec"));
        b.addActionListener(e -> runProgram());
        cp.add(b=new JButton("Wait"));
        b.addActionListener(e -> doWait());
        cp.add(b=new JButton("Exit"));
        b.addActionListener(e -> System.exit(0));
        pack();
    }

    /** Start the help, in its own Thread. */
    public void runProgram() {

        new Thread() {
            public void run() {

                try {
                    // Get a "file:" URL for the Help File
                    URL helpURL = this.getClass().getClassLoader().
                        getResource(HELPFILE);

                    // Start the external browser from the Java Application.

                    String osname = System.getProperty("os.name");
                    String run;
                    if ("Mac OS X".equals(osname)) {
                        run = "open -a " + program;
                        // "if" allows for other OSes needing special handling
                    } else {
                        run = program;
                    }

                    pStack.push(Runtime.getRuntime().exec(run + " " + helpURL));

                    logger.info("In main after exec " + pStack.size());

                } catch (Exception ex) {
                    JOptionPane.showMessageDialog(ExecDemoNS.this,
                        "Error" + ex, "Error",
                        JOptionPane.ERROR_MESSAGE);
                }
            }
        }.start();

    }

    public void doWait() {
        if (pStack.size() == 0) {
            logger.info("Nothing to wait for.");
            return;
        }
        logger.info("Waiting for process " + pStack.size());
        try {
            Process p = pStack.pop();
            p.waitFor();
            // wait for process to complete
            // (may not work as expected for some old Windows programs)
            logger.info("Process " + p + " is done.");
        } catch (Exception ex) {
            JOptionPane.showMessageDialog(this,
                "Error" + ex, "Error",
                JOptionPane.ERROR_MESSAGE);
        }
    }

}
jcb4 1801
Figure 18-1. ExecDemoNS in action

A newer class, ProcessBuilder, replaces most nontrivial uses of Runtime.exec(). This ProcessBuilder uses Generic Collections to let you modify or replace the environment, as shown in Example 18-2.

Example 18-2. main/src/main/java/otherlang/ProcessBuilderDemo.java
        List<String> command = new ArrayList<>();            1
        command.add("notepad");
        command.add("foo.txt");
        ProcessBuilder builder = new ProcessBuilder(command);2
        builder.environment().put("PATH",
                "/windows;/windows/system32;/winnt");        3
        final Process godot = builder.directory(
            new File(System.getProperty("user.home"))).      4
            start();
        System.err.println("Waiting for Godot");             5
        godot.waitFor();                                     6
1

Set up the command-line argument list: editor program name and filename.

2

Use that to start configuring the ProcessBuilder.

3

Configure the builder’s environment to a list of common MS Windows directories.

4

Set the initial directory to the user’s home, and start the process!

5

I always wanted to be able to use this line in code.

6

Wait for the end of our little play.

For more on ProcessBuilder, see the javadoc for java.lang.ProcessBuilder.

18.2 Running a Program and Capturing Its Output

Problem

You want to run a program but also capture its output.

Solution

Use the Process object’s getInputStream(); read and copy the contents to System.out or wherever you want them.

Discussion

The original notion of standard output and standard error was that they would always be connected to “the terminal”; this notion dates from an earlier time when almost all computer users worked at the command line. Today, a program’s standard and error output do not always automatically appear anywhere. Arguably there should be an automatic way to make this happen. But for now, you need to add a few lines of code to grab the program’s output and print it:

public class ExecDemoLs {

    private static Logger logger =
        Logger.getLogger(ExecDemoLs.class.getSimpleName());

    /** The program to run */
    public static final String PROGRAM = "ls"; // "dir" for Windows
    /** Set to true to end the loop */
    static volatile boolean done = false;

    public static void main(String argv[]) throws IOException {

        final Process p;         // Process tracks one external native process
        BufferedReader is;    // reader for output of process
        String line;

        p = Runtime.getRuntime().exec(PROGRAM);

        logger.info("In Main after exec");

        // Optional: start a thread to wait for the process to terminate.
        // Don't just wait in main line, but here set a "done" flag and
        // use that to control the main reading loop below.
        Thread waiter = new Thread() {
            public void run() {
                try {
                    p.waitFor();
                } catch (InterruptedException ex) {
                    // OK, just quit.
                    return;
                }
                System.out.println("Program terminated!");
                done = true;
            }
        };
        waiter.start();

        // getInputStream gives an Input stream connected to
        // the process p's standard output (and vice versa). We use
        // that to construct a BufferedReader so we can readLine() it.
        is = new BufferedReader(new InputStreamReader(p.getInputStream()));

        while (!done && ((line = is.readLine()) != null))
            System.out.println(line);

        logger.info("In Main after EOF");

        return;
    }
}

This is such a common occurrence that I’ve packaged it up into a class called ExecAndPrint, which is part of my com.darwinsys.lang package. ExecAndPrint has several overloaded forms of its run() method (see the documentation for details), but they all take at least a command and optionally an output file to which the command’s output is written. Example 18-3 shows the code for some of these methods.

Example 18-3. darwinsys-api/src/main/java/com/darwinsys/lang/ExecAndPrint.java
    /** Need a Runtime object for any of these methods */
    protected final static Runtime r = Runtime.getRuntime();

    /** Run the command given as a String, output to System.out
     * @param cmd The command
     * @return The command's exit status
     * @throws IOException if the command isn't found
     */
    public static int run(String cmd) throws IOException {
        return run(cmd, new OutputStreamWriter(System.out));
    }

    /** Run the command given as a String, output to "out"
     * @param cmd The command and list of arguments
     * @param out The output file
     * @return The command's exit status
     * @throws IOException if the command isn't found
     */
    public static int run(String cmd, Writer out) throws IOException {

        Process p = r.exec(cmd);

        FileIO.copyFile(new InputStreamReader(p.getInputStream()), out, true);
        try {
            p.waitFor();    // wait for process to complete
        } catch (InterruptedException e) {
            return -1;
        }
        return p.exitValue();
    }

As a simple example of using exec() directly along with ExecAndPrint, I’ll create three temporary files, list them (directory listing), and then delete them. When I run the ExecDemoFiles program, it lists the three files it has created:

-rw-------  1 ian  wheel  0 Jan 29 14:29 file1
-rw-------  1 ian  wheel  0 Jan 29 14:29 file2
-rw-------  1 ian  wheel  0 Jan 29 14:29 file3

Its source code is in Example 18-4.

Example 18-4. main/src/main/java/otherlang/ExecDemoFiles.java
        // Get and save the Runtime object.
        Runtime rt = Runtime.getRuntime();

        // Create three temporary files (the slow way!)
        rt.exec("mktemp file1");
        rt.exec("mktemp file2");
        rt.exec("mktemp file3");

        // Run the "ls" (directory lister) program
        // with its output sent into a file
        String[] args = { "ls", "-l", "file1", "file2", "file3" };
        ExecAndPrint.run(args);

        rt.exec("rm file1 file2 file3");

A process isn’t necessarily destroyed when the Java program that created it exits or bombs out. Simple text-based programs will be, but window-based programs like kwrite, Netscape, or even a Java-based JFrame application will not. For example, our ExecDemoNS program started Netscape, and when ExecDemoNS’s Exit button is clicked, ExecDemoNS exits but Netscape stays running. What if you want to be sure a process has completed? The Process object has a waitFor() method that lets you do so, and an exitValue() method that tells you the “return code” from the process. Finally, should you wish to forcibly terminate the other process, you can do so with the Process object’s destroy() method, which takes no argument and returns no value. Example 18-5 is ExecDemoWait, a program that runs whatever program you name on the command line (along with arguments), captures the program’s standard output, and waits for the program to terminate.

Example 18-5. main/src/main/java/otherlang/ExecDemoWait.java
        // A Runtime object has methods for dealing with the OS
        Runtime r = Runtime.getRuntime();
        Process p;         // Process tracks one external native process
        BufferedReader is;    // reader for output of process
        String line;

        // Our argv[0] contains the program to run; remaining elements
        // of argv contain args for the target program. This is just
        // what is needed for the String[] form of exec.
        p = r.exec(argv);

        System.out.println("In Main after exec");

        // getInputStream gives an Input stream connected to
        // the process p's standard output. Just use it to make
        // a BufferedReader to readLine() what the program writes out.
        is = new BufferedReader(new InputStreamReader(p.getInputStream()));

        while ((line = is.readLine()) != null)
            System.out.println(line);

        System.out.println("In Main after EOF");
        System.out.flush();
        try {
            p.waitFor();    // wait for process to complete
        } catch (InterruptedException e) {
            System.err.println(e);    // "Can'tHappen"
            return;
        }
        System.err.println("Process done, exit status was " + p.exitValue());

See Also

You wouldn’t normally use any form of exec() to run one Java program from another in this way; instead, you’d probably create it as a thread within the same process, because this is generally quite a bit faster (the Java interpreter is already up and running, so why wait for another copy of it to start up?). See Chapter 16.

When building industrial-strength applications, note the cautionary remarks in the Java API docs for the Process class concerning the danger of losing some of the I/O due to insufficient buffering by the operating system.

18.3 Calling Other Languages via javax.script

Problem

You want to invoke a script written in some other language from within your Java program, running in the JVM, with the ability to pass variables directly to/from the other language.

Solution

If the script you want is written in any of the two-dozen-plus supported languages, use javax.script. Languages include awk, perl, python, Ruby, BeanShell, PNuts, Ksh/Bash, R (“Renjin”), several implementations of JavaScript, and more.

Discussion

One of the first tasks when using this API is to find out the installed scripting engines, and then pick one that is available. The ScriptEnginesDemo program in Example 18-6 lists the installed engines, and runs a simple script in the default language, ECMAScript (aka JavaScript).

Example 18-6. main/src/main/java/otherlang/ScriptEnginesDemo.java
public class ScriptEnginesDemo {

    public static void main(String[] args) throws ScriptException {
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();

        // Print list of supported languages
        scriptEngineManager.getEngineFactories().forEach(factory ->
            System.out.println(factory.getLanguageName()));

        // Run a script in the JavaScript language
        String lang = "JavaScript";
        ScriptEngine engine =
            scriptEngineManager.getEngineByName(lang);
        if (engine == null) {
            System.err.println("Could not find engine");
            return;
        }
        engine.eval("print("Hello from " + lang + "");");
    }
}

Example 18-7 is a very simple demo of calling Python from Java using javax.scripting. We know the name of the scripting engine we want to use - Python. We’ll use the in-vm implementation known as jython - this was originally called JPython but was changed due to a trademark issue. Once we put the `jython-standalone-2.nnn.jar onto our classpath, the script engine is automatically detected. Just in case it fails, we print a verbose message including a list of the engines that are available.

Example 18-7. main/src/main/java/otherlang/PythonFromJava.java
/**
 * Scripting demo using Python (jython) to get a Java variable, print, and change it.
 * @author Ian Darwin
 */
public class PythonFromJava {
    private static final String PY_SCRIPTNAME = "pythonfromjava.py";

    public static void main(String[] args) throws Exception {
        ScriptEngineManager scriptEngineManager = new ScriptEngineManager();

        ScriptEngine engine = scriptEngineManager.getEngineByName("python");
        if (engine == null) {
            final String message =
                "Could not find 'python' engine; add its jar to CLASSPATH";
            System.out.println(message);
            System.out.println("Available script engines are: ");
            scriptEngineManager.getEngineFactories().forEach(factory ->
                System.out.println(factory.getLanguageName()));
            throw new IllegalStateException(message);
        }

        final Bindings bindings = engine.getBindings(ScriptContext.ENGINE_SCOPE);
        bindings.put("meaning", 42);

        // Let's run a python script stored on disk (well, on classpath):
        InputStream is =
            PythonFromJava.class.getResourceAsStream("/" + PY_SCRIPTNAME);
        if (is == null) {
            throw new IOException("Could not find file " + PY_SCRIPTNAME);
        }
        engine.eval(new InputStreamReader(is));
        System.out.println("Java: Meaning is now " + bindings.get("meaning"));
    }
}

See Also

You can get information about the scripting mechanism at the Oracle Scripting page.

Before Oracle destroyed java.net, there used to be a list of many languages (see this archived list; the links don’t work but it shows the extent of the languages that were available). Back then, you could download the “script engines” from that site. I am not aware of a current official list of engines, unfortunately. However, the list maintained as part of the scripting project per se can be found in an unofficial source code repository, by viewing https://github.com/scijava/javax-scripting, from which it should in theory be possible to build the one you want. A dozen or so other engines are maintained by others outside this project; for example, there is a Perl5 script engine from Google Code.

There is a also a list of Java-compatible scripting languages (not necessarily all using javax.script).

It is possible to roll your own scripting engine; see my writeup at https://darwinsys.com/java/scriptengines.html.

18.4 Mixing Languages with GraalVM

Problem

GraalVM aims to be multi-language, and you’d like to use different languages in the VM.

Solution

Use gu (graal utility?) to install additional language packs, and call other languages.

Discussion

While GraalVM positions itself as able to support a wide variety of programming languages, the number currently supported is small, but growing. Let’s try invoking Python code from within Java. Assuming you’ve installed Graal itself as per Recipe 1.2, you should have gu on your executable path, so try the following:

$ gu install python
Downloading: Component catalog from www.graalvm.org
Processing component archive: Graal.Python
Downloading: Component python: Graal.Python  from github.com
Installing new component: Graal.Python (org.graalvm.python, version 19.2.0.1)

IMPORTANT NOTE:
---------------
Set of GraalVM components that provide language implementations have changed. The Polyglot native image and polyglot native C library may be out of sync:
- new languages may not be accessible
- removed languages may cause the native binary to fail on missing resources or libraries.
To rebuild and refresh the native binaries, use the following command:
        /Library/Java/JavaVirtualMachines/graalvm-ce-19.2.0.1/Contents/Home/bin/gu rebuild-images

You may need to install "native-image" component which provide the rebuild tools.

Then the code in Example 18-8 can be used.

Example 18-8. graal/src/JavaCallPython.java
import java.io.*;
import java.util.stream.*;
import org.graalvm.polyglot.*;

/**
 * GraalVM polyglot: calling Python from Java/
 */
// tag::main[]
public class JavaCallPython {

    public static void main(String[] args) throws java.io.IOException {

         try (Context context = Context.create("jython")) {
            Value result = context.execute("2 + 2");
            System.out.println(result.asString());
        }
    }
}
// end::main[]

18.5 Marrying Java and Perl

Problem

You want to call Java from Perl, or vice versa.

Solution

To call Java from Perl, use the Perl Inline::Java module. To go the other way—calling Perl from Java—use javax.script, as in Recipe 18.3.

Discussion

Perl is often called a “glue language” that can be used to bring together diverse parts of the software world. But, in addition, it is a full-blown language in its own right for creating software. A wealth of extension modules provide ready-to-run solutions for quite diverse problems, and most of these modules are available free from CPAN, the Comprehensive Perl Archive Network. Also, as a scripting language, it is ideally suited for rapid prototyping. On the other hand, although building graphical user interfaces is definitely possible in Perl, it is not exactly one of the language’s strengths. So you might want to construct your GUI using Java Swing, and, at the same time, reuse business logic implemented in Perl.

Fortunately, among the many CPAN modules, Inline::Java makes the integration of Perl and Java a breeze. Let’s assume first that you want to call into Java from Perl. For business logic, I have picked a CPAN module that measures the similarity of two strings (the so-called Levenshtein edit distance). Example 18-9 shows the complete source. You need at least version 0.44 of the module Inline::Java; previous versions did not support threaded applications properly, so use of Swing wasn’t possible.

Using the module this way requires that the Java source be included in the Perl script with special delimiters, as shown in Example 18-9.

Example 18-9. Swinging.pl
#! /usr/bin/perl
# Calling Java from Perl

use strict;
use warnings;

use Text::Levenshtein qw( );
  # Perl module from CPAN to measure string similarity

use Inline 0.44 "JAVA" =&gt; "DATA";  # pointer to the Inline java source
use Inline::Java qw(caught);  # helper function to determine exception type

my $show = new Showit;     # construct Java object using Perl syntax
$show-&gt;show("Just another Perl hacker");            # call method on that object

eval {
  # Call a method that will call back to Perl;
  # catch exceptions, if any.
  print "matcher: ", $show-&gt;match("Japh", shift||"Java"),
  " (displayed from Perl)
";
};
if ($@) {
  print STDERR "Caught:", caught($@), "
";
  die $@ unless caught("java.lang.Exception");
  print STDERR $@-&gt;getMessage( ), "
";
}

__END_  _

__JAVA_  _
// Java starts here
import javax.swing.*;
import org.perl.inline.java.*;

class Showit extends InlineJavaPerlCaller {
  // extension only neeeded if calling back into Perl

  /** Simple Java class to be called from Perl, and to call back to Perl
   */
  public Showit( ) throws InlineJavaException { }

  /** Simple method */
  public void show(String str) {
    System.out.println(str + " inside Java");
  }

  /** Method calling back into Perl */
  public int match(String target, String pattern)
      throws InlineJavaException, InlineJavaPerlException {

    // Calling a function residing in a Perl Module
    String str = (String)CallPerl("Text::Levenshtein", "distance",
          new Object [] {target, pattern});

    // Show result
    JOptionPane.showMessageDialog(null, "Edit distance between '" + target +
        "' and '" + pattern + "' is " + str,
        "Swinging Perl", JOptionPane.INFORMATION_MESSAGE);
    return Integer.parseInt(str);
  }

}

Since this uses the Text::Levenshtein and the Inline::Java modules you will have to install that. The standard way is:

$ perl -MCPAN -e shell
> install Text::Levenshtein
> install Inline::Java
> quit

On some systems there may be an OS-specific module, e.g., on OpenBSD Unix,

$ doas pkg_add p5-Text-LevenshteinXS

In a simple Perl+Java program like this, you don’t even need to write a separate Java source file: you combine all the code, Perl and Java alike, in one single file. You do not need to compile anything, either; just execute it by typing:

perl Swinging.pl

(You can also add a string argument.) After a little churning, a Java message box pops up, telling you that the distance between “Japh” and “Java” is 2. At the same time, your console shows the string “Just another Perl hacker inside Java.” When you close the message box, you get the final result “matcher: 2 (displayed from Perl).”

In between, your Perl program has created an instance of the Java class Showit by calling its constructor. It then called that object’s show() method to display a string from within Java. It then proceeded to call the match() method, but this time, something more complicated happens: the Java code calls back into Perl, accessing method distance of module Text::Levenshtein and passing it two strings as arguments. It receives the result, displays it in a message box, and finally, for good measure, returns it to the Perl main program that it had been called from.

Incidentally, the eval { } block around the method call is the Perlish way of catching exceptions. In this case, the exception is thrown from within Java.

If you restart the program, you will notice that startup time is much shorter, which is always good news. Why is that so? On the first call, Inline::Java took the input apart, precompiled the Java part, and saved it to disk (usually, in a subdirectory called _Inline). On subsequent calls, it just makes sure that the Java source has not changed and then calls the class file that is already on disk. (Of course, if you surreptitiously changed the Java code, it is recompiled just as automagically.) Behind the scenes, even stranger things are going on, however. When the Perl script is executed, a Java server is constructed and started unbeknownst to the user, and the Perl part and the Java bits communicate through a TCP socket (see Chapter 13).

Marrying two platform-independent languages, like Perl and Java, in a portable way skirts many portability problems. When distributing inlined applications, be sure to supply not just the source files but also the contents of the _Inline directory. (It is advisable to purge that directory and to rebuild everything just before distribution time; otherwise, old compiled versions left lying around might make it into the distribution.) Each target machine needs to repeat the magic steps of Inline::Java, which requires a Java compiler. In any case, the Inline::Java module must be installed.

Because Perl has Inline modules for a number of other languages (ordinary languages like C, but others as exotic as Befunge), one might even consider using Perl as glue for interoperation between those other languages, jointly or separately, and Java. I am sure many happy hours can be spent working out the intricacies of such interactions.

See Also

You can find full information on Inline::Java on CPAN or in the POD (plain old documentation) that is installed along with the module itself.

18.6 Calling Other Languages via Native Code

Problem

You wish to call native C/C++ functions from Java, either for efficiency or to access hardware- or system-specific features.

Solution

Use JNI, the Java Native Interface. Or, use GraalVM.

Discussion

Java lets you load native or compiled code into your Java program. Why would you want to do such a thing? The best reason would probably be to access OS-dependent functionality, or existing code written in another language. A less good reason would be speed: native code can sometimes run faster than Java, though this is becoming less important as computers get faster and more multi-core. Like everything else in Java, the “native code” mechanism is subject to security restrictions; for example, applets were not allowed to access native code.

The native code language bindings are defined for code written in C or C++. If you need to access a language other than C/C++, write a bit of C/C++ and have it pass control to other functions or applications, using any mechanism defined by your operating system.

Due to such system-dependent features as the interpretation of header files and the allocation of the processor’s general-purpose registers, your native code may need to be compiled by the same C compiler used to compile the Java runtime for your platform. For example, on Solaris you can use SunPro C or maybe gcc. On Win32 platforms, use Microsoft visual C++ Version 4.x or higher (32 bit). For Linux and Mac OS X, you should be able to use the provided gcc-based compiler. For other platforms, see your Java vendor’s documentation.

Also note that the details in this section are for the Java Native Interface (JNI) of Java 1.1 and later, which differs in some details from 1.0 and from Microsoft’s native interface.

The first step is to write Java code that calls a native method. To do this, use the keyword native to indicate that the method is native, and provide a static code block that loads your native method using System.loadLibrary(). (The dynamically loadable module is created in Step 5.) Static blocks are executed when the class containing them is loaded; loading the native code here ensures it is in memory when needed!

Object variables that your native code may modify should carry the volatile modifier. The file HelloJni.java, shown in Example 18-10, is a good starting point.

Example 18-10. main/src/main/java/jni/HelloJni.java
/**
 * A trivial class to show Java Native Interface 1.1 usage from Java.
  */
public class HelloJni {
  int myNumber = 42; // used to show argument passing

  // declare native class
  public native void displayHelloJni();

  // Application main, call its display method
  public static void main(String[] args) {
    System.out.println("HelloJni starting; args.length="+
                       args.length+"...");
    for (int i=0; i<args.length; i++)
                       System.out.println("args["+i+"]="+args[i]);
    HelloJni hw = new HelloJni();
    hw.displayHelloJni();// call the native function
    System.out.println("Back in Java, "myNumber" now " + hw.myNumber);
  }

  // Static code blocks are executed once, when class file is loaded
  static {
    System.loadLibrary("hello");
  }
}

The second step is simple; just use javac HelloJni.java as you normally would. You probably won’t get any compilation errors on a simple program like this; if you do, correct them and try the compilation again.

Next, you need to create an .h file. Use javah to produce this file:

javah jni.HelloJni           // produces HelloJni.h

The .h file produced is a “glue” file, not really meant for human consumption and particularly not for editing. But by inspecting the resulting .h file, you’ll see that the C method’s name is composed of the name Java, the package name (if any), the class name, and the method name:

JNIEXPORT void JNICALL Java_HelloJni_displayHelloWorld(JNIEnv *env,
    jobject this);

Then create a C function that does the work. You must use the same function signature as is used in the .h file.

This function can do whatever it wants. Note that it is passed two arguments: a JVM environment variable and a handle for the this object. Table 18-1 shows the correspondence between Java types and the C types (JNI types) used in the C code.

Table 18-1. Java and JNI types
Java type JNI Java array type JNI

byte

jbyte

byte[]

jbyteArray

short

jshort

short[]

jshortArray

int

jint

int[]

jintArray

long

jlong

long[]

jlongArray

float

jfloat

float[]

jfloatArray

double

jdouble

double[]

jdoubleArray

char

jchar

char[]

jcharArray

boolean

jboolean

boolean[]

jbooleanArray

void

jvoid

Object

jobject

Object[]

jobjectArray

Class

jclass

String

jstring

array

jarray

Throwable

jthrowable

Example 18-11 is a complete C native implementation. Passed an object of type HelloJni, it increments the integer myNumber contained in the object.

Example 18-11. main/src/main/java/jni/HelloJni.c
#include <jni.h>
#include "HelloJni.h"
#include <stdio.h>
/*
 * This is the Java Native implementation of displayHelloJni.
 */
JNIEXPORT void JNICALL Java_HelloJni_displayHelloJni(JNIEnv *env, jobject this) {
  jfieldID fldid;
  jint n, nn;

  (void)printf("Hello from a Native Method
");

  if (this == NULL) {
    fprintf(stderr, "'this.' pointer is null!
");
    return;
  }
  if ((fldid = (*env)->GetFieldID(env,
        (*env)->GetObjectClass(env, this), "myNumber", "I")) == NULL) {
    fprintf(stderr, "GetFieldID failed");
    return;
  }

  n = (*env)->GetIntField(env, this, fldid);/* retrieve myNumber */
  printf(""myNumber" value is %d
", n);

  (*env)->SetIntField(env, this, fldid, ++n);/* increment it! */
  nn = (*env)->GetIntField(env, this, fldid);

  printf(""myNumber" value now %d
", nn); /* make sure */
  return;
}

Finally, you compile the C code into a loadable object. Naturally, the details depend on platform, compiler, etc. For example, on Windows:

> set JAVA_HOME=C:java              # or wherever
> set INCLUDE=%JAVA_HOME%include;%JAVA_HOME%includeWin32;%INCLUDE%
> set LIB=%JAVA_HOME%lib;%LIB%
> cl HelloJni.c -Fehello.dll -MD -LD

And on Unix:

$ export JAVAHOME=/local/java   # or wherever
$ cc -I$JAVAHOME/include -I$JAVAHOME/include/solaris 
	-G HelloJni.c -o libhello.so

Example 18-12 is a makefile for Unix.

Example 18-12. main/src/main/java/jni/Makefile (Unix version)
# Configuration Section

CFLAGS_FOR_SO = -G # Solaris
CFLAGS_FOR_SO = -shared
CSRCS        = HelloJni.c
# JAVA_HOME should be been set in the environment
#INCLUDES    = -I$(JAVA_HOME)/include -I$(JAVAHOME)/include/solaris
#INCLUDES    = -I$(JAVA_HOME)/include -I$(JAVAHOME)/include/openbsd
INCLUDES    = -I$(JAVA_HOME)/include

all:        testhello testjavafromc

# This part of the Makefile is for C called from Java, in HelloJni
testhello:        hello.all
        @echo
        @echo "Here we test the Java code "HelloJni" that calls C code."
        @echo
        LD_LIBRARY_PATH=`pwd`:. java HelloJni

hello.all:        HelloJni.class libhello.so

HelloJni.class: HelloJni.java
        javac HelloJni.java

HelloJni.h:    HelloJni.class
        javah -jni HelloJni

HelloJni.o::    HelloJni.h

libhello.so:    $(CSRCS) HelloJni.h
    $(CC) $(INCLUDES) $(CFLAGS_FOR_SO) $(CSRCS) -o libhello.so

# This part of the Makefile is for Java called from C, in javafromc
testjavafromc:    javafromc.all hello.all
    @echo
    @echo "Now we test HelloJni using javafromc instead of java"
    @echo
    ./javafromc HelloJni
    @echo
    @echo "That was, in case you didn't notice, C->Java->C. And,"
    @echo "incidentally, a replacement for JDK program "java" itself!"
    @echo


javafromc.all:    javafromc

javafromc:    javafromc.o
    $(CC) -L$(LIBDIR) javafromc.o -ljava -o $@

javafromc.o:    javafromc.c
    $(CC) -c $(INCLUDES) javafromc.c

clean:
    rm -f core *.class *.o *.so HelloJni.h
clobber: clean
    rm -f javafromc

And you’re done! Just run the Java interpreter on the class file containing the main program. Assuming that you’ve set whatever system-dependent settings are necessary (possibly including both CLASSPATH and LD_LIBRARY_PATH or its equivalent), the program should run as follows:

C> java jni.HelloJni
Hello from a Native Method      // from C
"myNumber" value is 42          // from C
"myNumber" value now 43         // from C
Value of myNumber now 43        // from Java

Congratulations! You’ve called a native method. However, you’ve given up portability; the Java class file now requires you to build a loadable object for each operating system and hardware platform. Multiply {Windows, Mac OS X, Sun Solaris, HP/UX, Linux, OpenBSD, NetBSD, FreeBSD} times {Intel, Intel-64, AMD64, SPARC, PowerPC, HP-PA} and you begin to see the portability issues.

Beware that problems with your native code can and will crash the runtime process right out from underneath the Java Virtual Machine. The JVM can do nothing to protect itself from poorly written C/C++ code. Memory must be managed by the programmer; there is no automatic garbage collection of memory obtained by the system runtime allocator. You’re dealing directly with the operating system and sometimes even the hardware, so, “Be careful. Be very careful.”

See Also

If you need more information on Java Native Methods, you might be interested in the comprehensive treatment found in Essential JNI: Java Native Interface by Rob Gordon (Prentice Hall).

18.7 Calling Java from Native Code

Problem

2416.11bYou need to “go the other way,” calling Java from C/C++ code.

Solution

Use JNI again.

Discussion

JNI (Java Native Interface) provides an interface for calling Java from C, with calls to:

  1. Create a JVM

  2. Load a class

  3. Find and call a method from that class (e.g., main)

JNI lets you add Java to legacy code. That can be useful for a variety of purposes and lets you treat Java code as an extension language.

The code in Example 18-13 takes a class name from the command line, starts up the JVM, and calls the main() method in the class.

Example 18-13. main/src/main/java/jni/javafromc.c - Calling Java from C
/*
 * This is a C program that calls Java code.
 * This could be used as a model for building Java into an
 * existing application as an extention language, for example.
 */

#include <stdio.h>
#include <jni.h>

int
main(int argc, char *argv[]) {
    int i;
    JavaVM *jvm;        /* The Java VM we will use */
    JNIEnv *myEnv;        /* pointer to native environment */
    JDK1_1InitArgs jvmArgs; /* JNI initialization arguments */
    jclass myClass, stringClass;    /* pointer to the class type */
    jmethodID myMethod;    /* pointer to the main() method */
    jarray args;        /* becomes an array of Strings */
    jthrowable tossed;    /* Exception object, if we get one. */

    JNI_GetDefaultJavaVMInitArgs(&jvmArgs);    /* set up the argument pointer */
    /* Could change values now, like: jvmArgs.classpath = ...; */

    /* initialize the JVM! */
    if (JNI_CreateJavaVM(&jvm, &myEnv, &jvmArgs) < 0) {
        fprintf(stderr, "CreateJVM failed
");
        exit(1);
    }

    /* find the class named in argv[1] */
    if ((myClass = (*myEnv)->FindClass(myEnv, argv[1])) == NULL) {
        fprintf(stderr, "FindClass %s failed
", argv[1]);
        exit(1);
    }

    /* find the static void main(String[]) method of that class */
    myMethod = (*myEnv)->GetStaticMethodID(
        myEnv, myClass, "main", "([Ljava/lang/String;)V");
    /* myMethod = (*myEnv)->GetMethodID(myEnv, myClass, "test", "(I)I"); */
    if (myMethod == NULL) {
        fprintf(stderr, "GetStaticMethodID failed
");
        exit(1);
    }

    /* Since we're calling main, must pass along the command line arguments,
     * in the form of Java String array
     */
    if ((stringClass = (*myEnv)->FindClass(myEnv, "java/lang/String")) == NULL){
        fprintf(stderr, "get of String class failed!!
");
        exit(1);
    }

    /* make an array of Strings, subtracting 1 for progname & 1 for the
     * java class name */
    if ((args = (*myEnv)->NewObjectArray(myEnv, argc-2, stringClass, NULL))==NULL) {
        fprintf(stderr, "Create array failed!
");
        exit(1);
    }

    /* fill the array */
    for (i=2; i<argc; i++)
        (*myEnv)->SetObjectArrayElement(myEnv,
            args, i-2, (*myEnv)->NewStringUTF(myEnv, argv[i]));

    /* finally, call the method. */
    (*myEnv)->CallStaticVoidMethodA(myEnv, myClass, myMethod, &args);

    /* And check for exceptions */
    if ((tossed = (*myEnv)->ExceptionOccurred(myEnv)) != NULL) {
        fprintf(stderr, "%s: Exception detected:
", argv[0]);
        (*myEnv)->ExceptionDescribe(myEnv);    /* writes on stderr */
        (*myEnv)->ExceptionClear(myEnv);    /* OK, we're done with it. */
    }

    (*jvm)->DestroyJavaVM(jvm);    /* no error checking as we're done anyhow */
    return 0;
}

1 kwrite is Unix-specific; it’s a part of the K Desktop Environment (KDE).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.227.72.15