• No se han encontrado resultados

Arvejas frescas o refrigeradas (partida arancelaria 070810)

Although it is said that JNI allows Java code to interact with other language codes, JNI currently provides facilities to access C/C++ or assembly code. We shall first use C and then C++ as native language. Let us first write a simple JNI application that calls a C function print() that prints a message. The following are basic steps to do this:

• Write a Java class having a native method declaration. • Compile this class using javac. • Generate C header file using javah • Implement the native method in a C program • Compile C program and create a shared library • Run the Java program

8.2.1 Writing Java Program

If a Java program wants to invoke a method which is implemented in a different programming language, the method is said to be native method. Before invoking such a method, it must be declared first. Declaring a native method looks very similar to that of abstract methods except that the keyword native is used instead of abstract keyword. So, the declaration of our print() method looks like this:

public native void print();

The native keyword signals that print() will be implemented in a native language. Hence, the method body is not provided. Java compiler understands it and does not look for its body. However, before calling this method, JVM must load the library containing the method’s native implementation. We use loadLibrary() method of System class to load a library. Suppose the method is implemented in GCC and the name of the library is libtest.so, then the following line is used to load the library:

System.loadLibrary("test");

Note that the JVM uses standard, but platform-specific, rule to translate the native library name to the actual library name. For example, for JVM in Unix-like system, the native library name test is translated in actual library name libtest.so while in windows the same is translated in test.dll.

Alternatively, we can use the load() method that takes the pathname of the library file as follows: System.load("/root/ajp/jni/c/libtest.so");

Note that the load() method always expects absolute pathname. Since, it is mandatory to load this library before calling the method, it is a good and safe idea to load it in the static block, which gets executed before all other statements. The complete source code (JNITest.java) is given below:

class JNITest {

public native void print(); static {

System.loadLibrary("test"); }

public static void main(String[] args) { new JNITest().print();

} }

static {

System.loadLibrary("test"); }

public static void main(String[] args) { new JNITest().print();

} }

In the main method, we have created an instance of JNITest class and invoked the native method print(). Note that the syntax to call a native method is exactly same as a regular method call. We shall consistently use the same class name (JNITest) throughout the chapter to demonstrate various concepts of JNI. Needless to say that class’s content will be different.

8.2.2 Compiling Java Program

Compiling a Java program having native method is not different at all. We use the same syntax used for ordinary Java programs as follows:

javac JNITest.java

As usual, this generates a class JNITest.class.

8.2.3 Create Header File

The Java part of the JNI application is complete. It is time to implement the method print() in C. However, since Java and C are two different programming languages, the prototype of this print() function in C is not supposed to be same as in Java. So, how do you determine the prototype of print() in C? Fortunately, Java comes with a tool javah to solve this problem. The tool javah, given a Java class containing native method(s) declaration, essentially generates C/C++ header file containing function prototype(s). So, we use it as follows:

javah JNITest

The name of the header file is the mangled fully-qualified class name with extension h. The above command generates a file JNITest.h whose content is shown below:

/* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h>

/* Header for class JNITest */ #ifndef _Included_JNITest #define _Included_JNITest #ifdef __cplusplus extern "C" { #endif /* * Class: JNITest * Method: print * Signature: ()V */

JNIEXPORT void JNICALL Java_JNITest_print(JNIEnv *, jobject); #ifdef __cplusplus

} #endif #endif

The generated header file includes a file jni.h (<JAVA_HOME>/include/jni.h). This header file provides all information the native function needs to interact with Java code. It defines all of the necessary data types and contains macros and typedefs that hide the complexity of mapping Java types to native types. It also includes other platform-specific header files. Since javah automatically includes this header file, we usually do not have to explicitly include this file.

JAVA NATIVE INTERFACE 161 As you can see, javah generated the prototype of the following C function corresponding to the Java native method print().

JNIEXPORT void JNICALL Java_JNITest_print(JNIEnv *, jobject); The name of the function is determined from the following components:

• the prefix Java_

• a mangled fully-qualified class name • an underscore (“_”) separator • a mangled method name

• for overloaded native methods, two underscores (“__”) followed by the mangled argument signature

This set of rules makes a method name unique even if different Java classes have native methods with same name.

The generated C/C++ function will always contain two parameters in addition to the regular parameters originally declared in the native Java method. The first parameter is a pointer to a structure that contains the interface to the JVM. (Note that this JNIEnv is not the actual name of the structure. The name of the structure is JNIEnv_ and const struct JNINativeInterface_ for C++ and C respectively. We shall discuss more on this later in this chapter). This structure contains all of the functions necessary to interact with the JVM and to work with Java objects. For example, it includes functions for instantiating objects, throwing exceptions, converting native arrays to/from Java arrays, native strings to/from Java strings etc. With this pointer, although it is not that easy, we can virtually do everything that Java code can do.

The second argument differs depending on whether the native method is non-static or static. It is a reference to the object (jobject) for a non-static native method and is a reference to its Java class (jclass) for static method.

When a native method is called, JVM passes these two arguments to the corresponding C/C++ function from where we can see the JVM back using these two parameters.

The JNIEXPORT and JNICALL are macros used to specify the calling and linkage convention of both Java native methods and their implementations. In Linux like OS, they are blank macros (defined in <JAVA_HOME>/include/linux/jni_md.h) as follows:

#define JNIEXPORT #define JNICALL

So, the function prototype essentially looks like an ordinary one as follows: void Java_JNITest_print(JNIEnv *, jobject);

Since windows use different calling conventions, their definitions (<JAVA_HOME>\include\win32\ jni_md.h) are as follows(<JAVA_HOME>\include\win32\jni_md.h) are as follows

#define JNIEXPORT __declspec(dllexport) #define JNICALL __stdcall

In windows, the function prototype finally looks like this:

__declspec(dllexport) void __stdcall Java_JNITest_print(JNIEnv *, jobject);

8.2.4 Implement Native Method

In the C implementation file, we include the above generated header file JNITest.h. #include "JNITest.h"

We then implement the native method as follows:

JNIEXPORT void JNICALL Java_JNITest_print(JNIEnv *env, jobject obj){ printf("In a C function\n");

}

The function Java_JNITest_print() prints a simple message "In a C function\n" so that we can trace the method calls. Here is the complete source code of C implementation file (JNITestImpl.c):

//JNITestImpl.c #include "JNITest.h" #include <stdio.h>

JNIEXPORT void JNICALL Java_JNITest_print(JNIEnv *env, jobject obj){ printf("In a C function\n");

}

8.2.5 Create Shared Library

The implementation of native method is now ready. We shall use GNU C compiler to compile this file and create a shared library. We use the following command to create a library libtest.so:

cc -shared -I/usr/java/jdk1.7.0_45/include -

I/usr/java/jdk1.7.0_45/include/linux JNITestImpl.c -o libtest.so

Note that to compile, we need the system header file <JAVA_HOME>/include/jni.h, which in turn includes <JAVA_HOME>/include/linux/jni_md.h. So, include these two directories <JAVA_HOME>/ include and <JAVA_HOME>/include/linux during compilation. The symbolic name <JAVA_HOME> represents the directory where Java was installed. For our system, Java home directory was /usr/ java/jdk1.7.0_45/. So, we included two directories /usr/java/jdk1.7.0_45/include and /usr/ java/jdk1.7.0_45/include/linux so that the compiler cc can recognize header files jni.h and jni_md.h.

8.2.6 Running the Program

Now, we are about to call a native function from a Java program. Use the following command specifying the location of the native library:

java -Djava.library.path=. JNITest

If everything goes fine, the following message should appear: In a C function

The location of the library may also be specified using LD_LIBRARY_PATH environment variable as follows:

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:. We can then run the Java program directly as follows: java JNITest

For convenience, we write a shell script as follows: #make.sh

javac JNITest.java javah JNITest

cc -shared -I/usr/java/jdk1.7.0_45/include -

I/usr/java/jdk1.7.0_45/include/linux JNITestImpl.c -o libtest.so java -Djava.library.path=. JNITest

JAVA NATIVE INTERFACE 163 Give the execution permission as follows:

chmod +x make.sh

We can now perform all JNI tasks by executing this shell script: ./make.sh

8.3 USING C++

So far we have implemented a native method in C language. JNI also supports methods to be implemented in C++. Implementing the native method in C++ is not very different. Here is a sample implementation (JNITestImpl.cpp):

//JNITestImpl.cpp #include "JNITest.h" #include <iostream>

JNIEXPORT void JNICALL Java_JNITest_print(JNIEnv *env, jobject obj){ std::cout << "In a C++ function\n";

}

Since, we have not used any C++ specific feature, it looks exactly like C implementation except that we used cout instead of printf and included iostream in place of stdio.h. Use GNU C++ compiler g++ to create the library:

g++ -shared -I/usr/java/jdk1.7.0_45/include -

I/usr/java/jdk1.7.0_45/include/linux JNITestImpl.cpp -o libtest.so Run the Java program as before. The following message should appear.

In a C++ function

The shell script will look like this: #make.sh

javac JNITest.java javah JNITest

g++ -shared -I/usr/java/jdk1.7.0_45/include -

I/usr/java/jdk1.7.0_45/include/linux JNITestImpl.cpp -o libtest.so java -Djava.library.path=. JNITest