Calling a C Function from the Java Programming Language


Suppose you have a C function that does something you like and, for one reason or another, you don't want to bother reimplementing it in the Java programming language. For the sake of illustration, we assume it is the useful and venerable printf function. You want to be able to call printf from your programs. The Java programming language uses the keyword native for a native method, and you will obviously need to encapsulate the printf function in a class. So, you might write something like this:

 public class Printf {    public native String printf(String s); } 

You actually can compile this class, but when you go to use it in a program, then the virtual machine will tell you it doesn't know how to find printfreporting an UnsatisfiedLinkError. So the trick is to give the run time enough information so that it can link in this class. As you will soon see, under the JDK this requires a three-step process:

1.

Generate a C stub for a function that translates between the Java call and the actual C function. The stub does this translation by taking parameter information off the virtual machine stack and passing it to the compiled C function.

2.

Create a special shared library and export the stub from it.

3.

Use a special method, called System.loadLibrary, to tell the Java runtime environment to load the library from Step 2.

We now show you how to carry out these steps for various kinds of examples, starting from a trivial special-case use of printf and ending with a realistic example involving the registry functions for Windowsplatform-dependent functions that are obviously not available directly from the Java platform.

Working with the printf Function

Let's start with just about the simplest possible situation using printf: calling a native method that prints the message, "Hello, Native World." Obviously we are not even tapping into the useful formatting features of printf! Still, this is a good way for you to test that your C compiler works as expected before you try implementing more ambitious native methods.

You first declare the native method in a class. The native keyword alerts the compiler that the method will be defined externally. Of course, native methods will contain no code in the Java programming language, and the method header is followed immediately by a terminating semicolon. This means, as you saw in the example above, native method declarations look similar to abstract method declarations.

 class HelloNative {    public native static void greeting();    . . . } 

In this particular example, note that the native method is also declared as static. Native methods can be both static and non-static. We start with a static method because we do not yet want to deal with parameter passing.

Next, write a corresponding C function. You must name that function exactly the way the Java runtime environment expects. Here are the rules:

  1. Use the full Java method name, such as HelloNative.greeting. If the class is in a package, then prepend the package name, such as com.horstmann.HelloNative.greeting.

  2. Replace every period with an underscore, and append the prefix Java_. For example, Java_HelloNative_greeting or Java_com_horstmann_HelloNative_greeting.

  3. If the class name contains characters that are not ASCII letters or digitsthat is, '_', '$', or Unicode characters with code greater than '\u007F'replace them with _0xxxx, where xxxx is the sequence of four hexadecimal digits of the character's Unicode value.

NOTE

If you overload native methods, that is, if you provide multiple native methods with the same name, then you must append a double underscore followed by the encoded argument types. We describe the encoding of the argument types later in this chapter. For example, if you have a native method, greeting, and another native method, greeting(int repeat), then the first one is called Java_HelloNative_greeting__, and the second, Java_HelloNative_greeting__I.


Actually, nobody does this by hand; instead, you run the javah utility, which automatically generates the function names. To use javah, first, compile the source file (given in Example 11-3 on page 834).

 javac HelloNative.java 

Next, call the javah utility to produce a C header file. The javah executable can be found in the jdk/bin directory.

 javah HelloNative 

Using javah creates a header file, HelloNative.h, as in Example 11-1.

Example 11-1. HelloNative.h
  1. /* DO NOT EDIT THIS FILE - it is machine generated */  2. #include <jni.h>  3. /* Header for class HelloNative */  4.  5. #ifndef _Included_HelloNative  6. #define _Included_HelloNative  7. #ifdef __cplusplus  8. extern "C" {  9. #endif 10. /* 11.  * Class:     HelloNative 12.  * Method:    greeting 13.  * Signature: ()V 14.  */ 15. JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv *, jclass); 16. 17. ifdef __cplusplus 18. } 19. #endif 20. #endif 

As you can see, this file contains the declaration of a function Java_HelloNative_greeting. (The strings JNIEXPORT and JNICALL are defined in the header file jni.h. They denote compiler-dependent specifiers for exported functions that come from a dynamically loaded library.)

Now, you simply have to copy the function prototype from the header file into the source file and give the implementation code for the function, as shown in Example 11-2.

Example 11-2. HelloNative.c
 1. #include "HelloNative.h" 2. #include <stdio.h> 3. 4. JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env, jclass cl) 5. { 6.    printf("Hello Native World!\n"); 7. } 

In this simple function, ignore the env and cl arguments. You'll see their use later.

C++ NOTE

You can use C++ to implement native methods. However, you must then declare the functions that implement the native methods as extern "C". (This stops the C++ compiler generating C++-specific code.) For example,

 #include "HelloNative.h" #include <stdio.h> extern "C" JNIEXPORT void JNICALL Java_HelloNative_greeting(JNIEnv* env, jclass cl) {    printf("Hello, Native World!\n"); } 


You compile the native C code into a dynamically loaded library. The details depend on your compiler.

For example, with the Gnu C compiler on Linux, use these commands:


gcc -fPIC -I jdk/include -I jdk/include/linux -shared -o libHelloNative.so HelloNative.c

With the Sun compiler under the Solaris Operating System, the command is


cc -G -I jdk/include -I jdk/include/solaris -o libHelloNative.so HelloNative.c

With the Microsoft C++ compiler under Windows, the command is


cl -I jdk\include -I jdk\include\win32 -LD HelloNative.c -FeHelloNative.dll

Here, jdk is the directory that contains the JDK.

TIP

If you use the Microsoft C++ compiler to compile DLLs from a command shell, first run the batch file vcvars32.net or vsvars32.bat. That batch file properly configures the command-line compiler by setting up the path and the environment variables needed by the compiler. You can find it in the directory c:\Program Files\Microsoft Visual Studio .NET 2003\Common7\tools (or a similar monstrosity).


You can also use the freely available Cygwin programming environment, available from http://www.cygwin.com. It contains the Gnu C compiler and libraries for UNIX-style programming on Windows. With Cygwin, use the command


gcc --mno-cygwin -D __int64="long long" -I jdk/include/ -I jdk/include/win32
   -shared -Wl,--add-stdcall-alias -o HelloNative.dll HelloNative.c

Type the entire command on a single line.

NOTE

The Windows version of the header file jni_md.h contains the type declaration

 typedef __int64 jlong; 

which is specific to the Microsoft compiler. If you use the Gnu compiler, you may want to edit that file, for example,

 #ifdef __GNUC__    typedef long long jlong; #else    typedef __int64 jlong; #endif 

Alternatively, compile with -D __int64="long long", as shown in the sample compiler invocation.


Finally, add a call to the System.loadLibrary method in the Java class that defines the native method. This ensures that the virtual machine will load the library before the first use of the class. The easiest way to do this is with a static initialization block, as in Example 11-3.

Example 11-3. HelloNative.java
 1. class HelloNative 2. { 3.    public static native void greeting(); 4. 5.    static 6.    { 7.       System.loadLibrary("HelloNative"); 8.    } 9. } 

Assuming you have followed all the steps given above, you are now ready to run the HelloNativeTest application shown in Example 11-4.

Example 11-4. HelloNativeTest.java
 1. class HelloNativeTest 2. { 3.    public static void main(String[] args) 4.    { 5.       HelloNative.greeting(); 6.    } 7. } 

If you compile and run this program, the message "Hello, Native World!" is displayed in a terminal window.

NOTE

If you run Linux, you must add the current directory to the library path. Either set the LD_LIBRARY_PATH environment variable,

 export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH 

or set the java.library.path system property:

 java -Djava.library.path=. HelloNativeTest 


Of course, this is not particularly impressive by itself. However, if you keep in mind that this message is generated by the C printf command and not by any Java programming language code, you will see that we have taken the first steps toward bridging the gap between the two languages!


 java.lang.System 1.0 

  • void loadLibrary(String libname)

    loads the library with the given name. The library is located in the library search path. The exact method for locating the library is operating-system dependent. Under Windows, this method searches first the current directory, then the directories listed in the PATH environment variable.

  • void load(String filename)

    loads the library with the given file name. If the library is not found, then an UnsatisfiedLinkError is thrown.

NOTE

Some shared libraries for native code must run initialization code. You can place any initialization code into a JNI_OnLoad method. Similarly, when the VM shuts down, it will call the JNI_OnUnload method if you provide it. The prototypes are

 jint JNI_OnLoad(JavaVM* vm, void* reserved); void JNI_OnUnload(JavaVM* vm, void* reserved); 

The JNI_OnLoad method needs to return the minimum version of the VM that it requires, such as JNI_VERSION_1_1.




    Core JavaT 2 Volume II - Advanced Features
    Building an On Demand Computing Environment with IBM: How to Optimize Your Current Infrastructure for Today and Tomorrow (MaxFacts Guidebook series)
    ISBN: 193164411X
    EAN: 2147483647
    Year: 2003
    Pages: 156
    Authors: Jim Hoskins

    flylib.com © 2008-2017.
    If you may any questions please contact us: flylib@qtcs.net