Numerical Algorithms Group: Title
Numerical Algorithms Group: Title
Title: Summary:
Calling C Library Routines from Java Using the Java Native Interface
This paper presents a technique for calling C library routines directly from Java, saving you the trouble of rewriting code in Java and gaining portability via Java Virtual Machines.
The NAG C Library from the Numerical Algorithms Group (www.nag.com) is a mathematical and statistical library containing routines for linear algebra, optimization, quadrature, differential equations, regression analysis, and time-series analysis. Although written in C, the librarys functionality can be accessed from other languages. On PCs, DLL versions of the library can be exploited in many ways, including calling from Microsoft Excel, Visual Basic, or Borland Delphi. Consequently, NAG C Library users often ask if they can call it from Java. One way to perform numerical computations is by creating Java classes that implement the required functionality. However, this is often difficult and time consuming. In this article, I present a technique for calling C Library routines directly from Java. Apart from avoiding rewriting numerical code in Java, using existing C code has another advantage. Java is portable -- compiled programs run on any machine with a Java Virtual Machine (VM). To accomplish this, the Java compiler does not compile to a machinedependent assembler, but to machine-independent Run-Time Error Messages bytecode that is interpreted at run time by the VM. Although the interpreter is efficient, no interpreted If you get a Java error message, such as program runs as fast as programs compiled to assembler interface library CJavaInterface cannot be code. For applications that perform CPU-intensive found or bessely0 function cannot be found operations on large amounts of data, this can be when running your program, you may need to significant, and moving those operations from Java into set an environment variable to tell the system compiled libraries can cut execution time. where to look. Environment variable names
are operating-system dependent. Linux machines. The environment variable LD_LIBRARY_PATH is a colonseparated list of directories to be searched for libCJavaInterface.so. For example, if you use the C shell and your library is in /home, the command setenv LD_LIBRARY_PATH /home ensures that the current directory /home gets searched. Other UNIX machines. The environment variable is likely discussed in the man page for the ld command, or in a command referenced by that man page. Common names include LD_LIBRARY_ PATH and SHLIB_PATH. Microsoft Windows. There is no separate environment variable used for locating DLLs. The PATH variable is searched, so CJavaInterface.dll must reside somewhere in your path. M.P.
compiler, gcc 3.2; a Sun machine running Solaris 8.0 with the Sun Workshop 6 C compiler, cc 5.2; and a PC running Windows 2000 with Visual C++ 5.0. Working on UNIX platforms other than Sun or Linux is similar, the main differences being in the location of Java include files and the method of creating a shared object (dynamic library).
Implementing calls from Java to native functions is a three-step process: 1. Write a declaration in Java for the native method. This declaration includes the keyword native to signify to the Java compiler that it is implemented externally. 2. Create a header file for use by the native (C) code. This header file contains the declaration of the native method as viewed by the C compiler. It includes the extra arguments required for the C function to access Java methods and properties, and has argument types defined in terms of standard C types. 3. Implement the native method in C. This function uses the header file in Step 2, makes calls to library functions it needs (possibly back to Java methods), and returns results to Java. This C code is compiled to build a shareable library. After the native shareable library is built, Java code that uses it is still machine-independent even though the native library is not. Thus, you must build the library on all platforms the Java program runs on, although you dont need to edit or rebuild the Java code.
to a Java System.LoadLibrary( ) call. Even though the interface library may have a different name (depending on the OS), LoadLibrary sorts it out. For example, under Linux or Solaris, the Java System.loadLibrary("CJavaInterface"); searches for a library named libCJavaInterface.so. But under Windows, it searches for CJavaInterface. dll. Listing One (see page 8) is the Java program Bessel.java, including the native method declaration and loadLibrary call. The System.loadLibrary call is placed inside a static initializer so that it is executed when the class gets loaded by Java. The main program gets a value of x from the command line, and calls the native method using that argument and nine other arguments derived from it. Compile the Java program with the command javac Bessel.java. If all goes well, the compiler produces a file named Bessel.class. Note: all source code mentioned in this article is available for download at http://www.nag.com/IndustryArticles/pontjavafiles.zip. With the source code are scripts that you can use to compile and run all the examples under Windows (.bat files) or Linux (.sh files). Once Bessel.java is compiled, use the Java SDKs javah tool to create a header file that the C compiler can use. The command javah -jni Bessel produces the file Bessel.h; see Listing Two (page 9). The header file includes <jni.h> (which comes with the Java SDK); javah extracted this declaration of the native function for use by the C program: JNIEXPORT jdouble JNICALL Java_Bessel_bessely0 (JNIEnv *, jobject, jdouble); The name Java_Bessel_bessely0 shows the Java class in which it is declared, as well as the name bessely0 chosen for the native function. The macros JNIEXPORT and JNICALL are defined via <jni.h> and affect the Windows calling convention (on UNIX, the macros disappear). The types JNIEnv, jobject, and jdouble are also defined via <jni.h>, in terms of machine-dependent C types. For example, the type jdouble is the Java double type, which equates to the C double type. From the C point of view, the first two arguments, of types JNIEnv* and jobject, give C code access to the Java environment. In this case, the third argumentthe argument x of the Y0(x) Bessel function passes to the C Math Library. Once Bessel.h is created, you can write a C implementation of Java_Bessel_bessely0; see Listing Three (page 9). You can write any C code you like here but, in this case, all I do is to call the Y0 function from the Standard Math Library.
When BesselImp.c compiles, turn it into a shareable object using ld -G BesselImp.o -o libCJavaInterface.so -lm -lc -lpthread. The -G flag means create a shareable object. The -o flag names the shareable library as libCJavaInterface.so, the name needed by the loadLibrary( ) call in the Java code. Finally, the -lm, -lc, and -lpthread flags ensure that you link with required system math and run-time libraries. Under Windows, assuming Java is installed in c:\j2sdk1.4.0_01 (modify accordingly if not), compile and build the DLL in one step: cl /Ic:\j2sdk1.4.1_01\include /Ic:\j2sdk1.4.1_01\include\win32 /Gz /LD BesselImp.c /FeCJavaInterface.dll As with UNIX, the two /I switches tell the C compiler where to look for header files. Without the /Gz compiler option (meaning use the _ _stdcall calling convention), the code may compile and link and even start runningbut eventually may cause an access violation. The /LD flag means build a DLL. The /Fe flag names the output file as CJavaInterface.dll. You can run the program using java Bessel 1.0. Listing Four (page 9) is the expected output.
to the elements in situ. The C program, therefore, makes three calls of GetDoubleArrayElements, one for each array argument. It adds each element of array a to array b, putting the results in array c. Then it tells Java that it is finished with the array pointers using three calls of ReleaseDoubleArrayElements, declared in jni.h as: void (JNICALL *ReleaseDoubleArrayElements) (JNIEnv *env, jdoubleArray array, jdouble *elems, jint mode); This ensures that results get copied back to the appropriate Java arrays, and that Java garbage collection can work properly. Compile this code into an interface library in a similar way to the first example. Under Windows: cl /Ic:\j2sdk1.4.1_01\include /Ic:\j2sdk1.4.1_01\include\win32 /Gz /LD VectorAddImp.c /FeCJavaInterface.dll Run the program with java VectorAdd.
The function Java_RootFinder_rootfinder is the C implementation of the Java-declared method rootfinder. Since you cannot pass the Java method that evaluates f(x) directly to the rootlib C Library function rootfinder, you need to wrap it in a C function such as rootFun. Its prototype is double rootFun(double x); and it has the argument type and return type required by the rootlib library function. Inside rootFun, I only call the Java method to evaluate the function. The trick is in knowing how to make this call to Java. You do this using the JNI function CallDoubleMethod, which is declared in jni.h (there are similar functionsCallVoidMethod, CallIntMethod, and othersfor methods with different return types). CallDoubleMethod needs several arguments, including the JNIEnv pointer argument env and the Java object argument, both of which were passed to Java_RootFinder_ rootfinder. It also needs the argument methodID, which is the ID of the Java method to be called. These arguments are known (or can be obtained) by Java_RootFinder_rootfinder, but are not directly known by the function rootFun. Instead, I give these arguments to rootFun via global variables, which I declare like this in C: JNIEnv *globalJavaEnv; jobject globalJavaObject; jmethodID globalMid; Because these variables are global, they can be accessed by both Java_RootFinder_ rootfinder and rootFun. Besides these arguments, rootFun also passes to CallDoubleMethod the actual arguments that the Java method needs to evaluate the function f(x). CallDoubleMethod can accept any number of these arguments, but in this case there is only argument x. I could have written the evaluation code in C (and would not have needed to use CallDoubleMethod and the routines associated with it) instead of calling the Java method from rootFun to evaluate the function f(x). However, an advantage to the method I use is that once the interface library is built, you need never rebuild it even if the evaluation function changesjust supply a different Java evaluation function. Java_RootFinder_rootfinder first copies its arguments env and obj to global variables globalJavaEnv and globalJavaObject. Next, take the name of the Java method passed as the jstring argument funName and convert it into a method ID. Use the JNI function GetStringUTFChars to convert the jstring into a C char pointer named functionName because the jstring cannot be safely accessed directly. Then the JNI functions GetObjectClass and GetMethodID are used to get hold of the method ID of the Java evaluation function: functionName = (*env)->GetStringUTFChars (env, funName, 0); ... cls = (*env)->GetObjectClass(env, obj); globalMid = (*env)->GetMethodID(env, cls, functionName, "(D)D"); (Note that in the example program, the evaluation function is either the method myFunction or myFunction2.) GetMethodIDs second argument, of type jclass, is the class containing the method; the third argument is the name of the method, and the fourth argument is the signature of the method. In this case, the signature (D)D means a method with one double argument that returns a value of type double. Once you have the method ID to be used by rootFun, you no longer need the C string functionName, so free it via a call to JNI function ReleaseStringUTFChars to avoid memory leaks. At this point, you have everything you need to call the C rootlib library function rootfinder. Figure 2 illustrates what happens at run time.
Conclusion
With the techniques presented here, you can pass information between C and Java. Furthermore, you should be able to reuse some of the source code presented here to create interfaces to your own routines, written in C or in a precompiled library.
By Mick Pont. Mick works in the Development Division of the Numerical Algorithms Group and can be contacted at mick@nag.co.uk. Originally published by Dr. Dobbs Journal (www.ddj.com), July 2003.
Listing Two
/* The Bessel.h file generated from the Bessel class by the javah tool. */ /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class Bessel */ #ifndef _Included_Bessel #define _Included_Bessel #ifdef __cplusplus extern "C" { #endif /* Class: Bessel * Method: bessely0 * Signature: (D)D */ JNIEXPORT jdouble JNICALL Java_Bessel_bessely0 (JNIEnv *, jobject, jdouble); #ifdef __cplusplus } #endif #endif
Listing Three
/* The BesselImp.c file, #include <jni.h> /* #include "Bessel.h" /* #include <math.h> /* which implements the native function */ Java Native Interface headers */ Auto-generated header created by javah -jni */ Include math.h for the prototype of function y0 */
/* Our C definition of the function bessely0 declared in Bessel.java */ JNIEXPORT jdouble JNICALL Java_Bessel_bessely0(JNIEnv *env, jobject obj, jdouble x) { double y; /* Call Y0(x) Bessel function from standard C mathematical library */ y = y0(x); return y; }
Listing Four
// Output when running the Java Bessel program Calls of Y0 Bessel function routine bessely0 Y0(1.0) is 0.08825696421567694 Y0(1.25) is 0.2582168515945407 Y0(1.5) is 0.38244892379775886 Y0(1.75) is 0.465492628646906 Y0(2.0) is 0.5103756726497451 Y0(2.25) is 0.5200647624572782 Y0(2.5) is 0.4980703596152316 Y0(2.75) is 0.4486587215691318 Y0(3.0) is 0.3768500100127903 Y0(3.25) is 0.2882869026730869