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.
You want to run an external program from within a Java program.
Use one of the exec()
methods in the java.lang.Runtime
class.
Or set up a ProcessBuilder
and call its start()
method.
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.
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
);
}
}
}
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.
List
<
String
>
command
=
new
ArrayList
<
>
(
)
;
command
.
add
(
"notepad"
)
;
command
.
add
(
"foo.txt"
)
;
ProcessBuilder
builder
=
new
ProcessBuilder
(
command
)
;
builder
.
environment
(
)
.
put
(
"PATH"
,
"/windows;/windows/system32;/winnt"
)
;
final
Process
godot
=
builder
.
directory
(
new
File
(
System
.
getProperty
(
"user.home"
)
)
)
.
start
(
)
;
System
.
err
.
println
(
"Waiting for Godot"
)
;
godot
.
waitFor
(
)
;
Set up the command-line argument list: editor program name and filename.
Use that to start configuring the ProcessBuilder
.
Configure the builder’s environment to a list of common MS Windows directories.
Set the initial directory to the user’s home, and start the process!
I always wanted to be able to use this line in code.
Wait for the end of our little play.
For more on ProcessBuilder
, see the javadoc for java.lang.ProcessBuilder
.
You want to run a program but also capture its output.
Use the Process
object’s getInputStream()
; read and copy the contents to System.out
or wherever you want them.
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.
/** 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.
// 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.
// 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
());
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.
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.
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.
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).
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.
/**
* 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"
));
}
}
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.
GraalVM aims to be multi-language, and you’d like to use different languages in the VM.
Use gu
(graal utility?) to install additional language packs, and call other languages.
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.
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[]
You want to call Java from Perl, or vice versa.
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.
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.
#! /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" => "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->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->match("Japh", shift||"Java"), " (displayed from Perl) "; }; if ($@) { print STDERR "Caught:", caught($@), " "; die $@ unless caught("java.lang.Exception"); print STDERR $@->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.
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.
You wish to call native C/C++ functions from Java, either for efficiency or to access hardware- or system-specific features.
Use JNI, the Java Native Interface. Or, use GraalVM.
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.
/**
* 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.
Java type | JNI | Java array type | JNI |
---|---|---|---|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
float |
jfloat |
float[] |
jfloatArray |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
||
|
|
|
|
|
|
||
|
|
||
|
|
||
|
|
Example 18-11 is a complete C native implementation. Passed an object of type HelloJni
, it increments the integer myNumber
contained in the object.
#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.
# 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.”
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).
2416.11bYou need to “go the other way,” calling Java from C/C++ code.
Use JNI again.
JNI (Java Native Interface) provides an interface for calling Java from C, with calls to:
Create a JVM
Load a class
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.
/*
* 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).
18.227.72.15