使用 Java 编写应用程序时,有时仅 Java 无法满足应用程序的需求。您可能想要使用标准 Java 类库中不存在的功能,或者您可能只想使用以其他语言编写的现有库。这就是 JNI 的用武之地。
我发现大多数关于 JNI 的在线文档看起来都非常分散和过时。因此,这篇文章的范围是向您展示如何通过简单的示例来实现 JNI:
同样也可以用 C++ 来实现。请注意下面第 4 步中提到的修改。
1.编写Java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | //HelloWorld.java public class HelloWorld { native void cfunction(); //Declaring the native function static { System.loadLibrary( "forhelloworld" ); //Linking the native library } //which we will be creating. public static void main(String args[]) { HelloWorld obj = new HelloWorld(); obj.cfunction(); //Calling the native function } } |
2.编译Java代码,生成class文件
1 | javac HelloWorld.java |
3.从class文件生成一个Header文件
1 | javah HelloWorld |
这将生成一个文件 HelloWorld.h,其中包含:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //HelloWorld.h /* DO NOT EDIT THIS FILE - it is machine generated */ #include <jni.h> /* Header for class HelloWorld */ #ifndef _Included_HelloWorld #define _Included_HelloWorld #ifdef __cplusplus extern "C" { #endif /* * Class: HelloWorld * Method: cfunction * Signature: ()V */ JNIEXPORT void JNICALL Java_HelloWorld_cfunction (JNIEnv *, jobject); #ifdef __cplusplus } #endif #endif |
3.从头文件中获取JNI函数签名
1 2 | JNIEXPORT void JNICALL Java_HelloWorld_cfunction (JNIEnv *, jobject); |
即使我们声明没有参数的本机函数,JNI 函数签名仍然包含 2 个参数,它们是:
4.使用函数签名编写本机代码
1 2 3 4 5 6 7 8 9 10 | //HelloWorld.c #include <jni.h> #include <stdio.h> JNIEXPORT void JNICALL Java_HelloWorld_cfunction (JNIEnv *env, jobject jobj) { printf ( "\n > C says HelloWorld !\n" ); } |
5. Generate the library file from the native code
1 | gcc -o libforhelloworld.so -shared -fPIC -I (PATH TO jni.h header) HelloWorld.c -lc |
If you don’t specify the path correctly you will encounter this error :
1 2 3 4 | HelloWorld.c:1:17: fatal error: jni.h: No such file or directory #include <jni.h> ^ compilation terminated. |
It is usually present inside
1 | /usr/lib/jvm/default-java/include |
or
1 | /usr/lib/jvm/java-1.7.0-openjdk-amd64/include |
depending upon the version of Java you have installed in your system.
6. Place the library file in the standard /usr/lib folder
If the previous command executed successfully, it would have generated a file libforhelloworld.so . Conventionally this library file need not be placed anywhere else, it needs to reside in the current working directory.
But for that to work you need to have set the JAVA_PATH variables correctly, in most cases they won’t be set correctly. An easy hack to this would be to just place it inside /usr/lib
1 | sudo cp libforhelloworld.so /usr/lib |
If you don’t place the library file, you would encounter this error :
1 2 3 4 5 | Exception in thread "main" java.lang.UnsatisfiedLinkError: no forhelloworld in java.library.path at java.lang.ClassLoader.loadLibrary(ClassLoader.java:1886) at java.lang.Runtime.loadLibrary0(Runtime.java:849) at java.lang.System.loadLibrary(System.java:1088) at HelloWorld.<clinit>(HelloWorld.java:9) |
7. Execute the Java application
1 | java HelloWorld |
If you’ve followed all the steps correctly, C would greet the world 😀
1 | > C says HelloWorld ! |
Example : Write a Java program to find the factorial of a number. Pass the number as an argument from Java to a native function in C which returns the factorial as an integer.
Java code
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //factorial.java import java.util.Scanner; public class factorial { native int fact( int num); static { System.loadLibrary( "forfact" ); } public static void main(String args[]) { Scanner inp = new Scanner(System.in); System.out.println( " > Enter number :: " ); int num = inp.nextInt(); factorial obj = new factorial(); System.out.println( " > The factorial of " +num+ " is " +obj.fact(num)); } } |
C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | //factorial.c #include <jni.h> #include <stdio.h> JNIEXPORT jint JNICALL Java_factorial_fact (JNIEnv *env, jobject jobj, jint num) { jint result=1; while (num) { result*=num; num--; } return result; } |
示例:编写一个Java参数来给定给Java的字符串。将给定的字符串作为传递给C中的函数,函数返回给本机的字符串。
Java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | //reverse.java import java.util.Scanner; public class reverse { native String reversefunc(String word); static { System.loadLibrary( "forreverse" ); } public static void main(String args[]) { Scanner inp = new Scanner(System.in); System.out.println( " > Enter a string :: " ); String word = inp.nextLine(); reverse obj = new reverse(); System.out.println( " > The reversed string is :: " +obj.reversefunc(word)); } } |
C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | //reverse.c #include <jni.h> #include <stdio.h> JNIEXPORT jstring JNICALL Java_reverse_reversefunc (JNIEnv *env,jobject jobj,jstring original) { const char *org; char *rev; org = (*env)->GetStringUTFChars(env,original,NULL); int i; int size = (*env)->GetStringUTFLength(env,original); for (i=0;i<size;i++) rev[i]=org[size-i-1]; rev[size]= '\0' ; return (*env)->NewStringUTF(env,rev); } |
示例:编写一个程序,生成参数n个波那契数前。“将n个”作为Java中的斐数作为本函数将波那契数作为本机返回。
Java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | //fibonacci.java import java.util.Scanner; public class fibonacci { native int [] returnfibo( int n); static { System.loadLibrary( "fibonacci" ); } public static void main(String args[]) { Scanner inp = new Scanner(System.in); System.out.println( " > Enter n :: " ); int n = inp.nextInt(); fibonacci obj = new fibonacci(); int [] Fibo = obj.returnfibo(n); System.out.println( " > The first " +n+ " fibonacci numbers are :: " ); for ( int i= 0 ;i<n;i++) System.out.print(Fibo[i]+ "," ); } } |
C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 | //fibonacci.c #include <jni.h> #include <stdio.h> JNIEXPORT jintArray JNICALL Java_fibonacci_returnfibo (JNIEnv *env,jobject jobj,jint n) { jintArray fiboarray = (*env)->NewIntArray(env,n); int first=0; int second=1; int next; int i; int fibo[n]; for (i=0;i<n;i++) { if (i<=1) next = i; else { next = first + second; first = second; second = next; } fibo[i] = next; } (*env)->SetIntArrayRegion(env,fiboarray,0,n,fibo); return fiboarray; } |
示例:写了一个显示几个 Java 程序,这个星期是从 C 中的本机传递过来的。
Java代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | //daysofweek.java public class daysofweek { native String[] returndays(); static { System.loadLibrary( "daysofweek" ); } static public void main(String args[]) { daysofweek obj = new daysofweek(); String[] days = obj.returndays(); System.out.println( " > The days of the week are :: " ); for (String name: days) System.out.println(name); } } |
C代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | //daysofweek.c #include <jni.h> #include <stdio.h> JNIEXPORT jobjectArray JNICALL Java_daysofweek_returndays(JNIEnv *env, jobject jobj) { char *days[]={ "Sunday" , "Monday" , "Tuesday" , "Wednesday" , "Thursday" , "Friday" , "Saturday" }; jstring str; jobjectArray day = 0; jsize len = 7; int i; day = (*env)->NewObjectArray(env,len,(*env)->FindClass(env, "java/lang/String" ),0); for (i=0;i<7;i++) { str = (*env)->NewStringUTF(env,days[i]); (*env)->SetObjectArrayElement(env,day,i,str); } return day; } |
如您所见,C 字符隐含(C 字符串)一个数组,而返回 Java 字符串类型代码。需要您不必担心 Java,最好的旧转换来救援🙂
联系客服