Java Native Interface(Java 本地接口) 简称 JNI,是一种编程框架,使得 JVM 中的 Java 程序 可以调用本地应用/或库,也可以被其他程序调用。其过程可以不负责任的理解成 Java 的反射,因为代码逻辑和反射调用很像。
源码 本文所有涉及的所有源码:https://github.com/gavinliu/Study-JNI
HelloStringFromC 在 java 中编写 native 方法 HelloStringFromC.java 1 2 3 4 5 6 7 8 public class HelloStringFromC { public static native String helloStringFromC () ; public static void main (String[] args) { } }
生成 jni 头文件 1 javac -jni HelloStringFromC
将会生成 HelloStringFromC.h
HelloStringFromC.h 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 #include <jni.h> #ifndef _Included_HelloStringFromC #define _Included_HelloStringFromC #ifdef __cplusplus extern "C" {#endif JNIEXPORT jstring JNICALL Java_HelloStringFromC_helloStringFromC (JNIEnv *, jclass) ; #ifdef __cplusplus } #endif #endif
编写 native 代码 HelloStringFromC.c 1 2 3 4 5 6 7 8 9 #include <stdio.h> #include <stdlib.h> #include "HelloStringFromC.h" JNIEXPORT jstring JNICALL Java_HelloStringFromC_helloStringFromC (JNIEnv* env, jclass cls) { return (*env)->NewStringUTF(env, "Hello" ); }
编译 so 库 1 gcc HelloStringFromC.c -fPIC -shared -o libhelloString.so
java 调用 1 2 3 4 5 6 7 8 9 10 11 12 public class HelloStringFromC { public static native String helloStringFromC () ; public static void main (String[] args) { System.out.println(helloStringFromC()); } static { System.load("/Users/gavin/Workspace/Study-JNI/01.HelloStringFromC/libhelloString.so" ); } }
数据类型 基本数据类型
Java 数据类型
JNI 数据类型
C 数据类型
blean
jboolean
unsigned char
byte
jbyte
signed char
char
jchar
unsigned short
short
jshort
short
int
jint
int
long
jlong
long long
float
jfloat
float
double
jdouble
double
引用类型
Java 数据类型
JNI 数据类型
Object
jobject
Class
jclass
String
jstring
array[]
jarray
数组类型
Java 数据类型
JNI 数据类型
boolean[]
jbooleanArray
byte[]
jbyteArray
char[]
jcharArray
short[]
jshortArray
int[]
jintArray
long[]
jlongArray
float[]
jfloatArray
double
jdoubleArray
objcet
jobjectArray
JNIEnv 1 2 struct JNINativeInterface_ ;typedef const struct JNINativeInterface_ *JNIEnv ;
JNIEnv
其实是 JNINativeInterface_
的指针别名,这个结构是 C 和 Java 交互的关键类,提供了 C 数据类型和 JNI 数据类型的互相转换和 C 调用 Java 的相关方法等。
Java 调 C Java 调 C 的套路:
在 Java 源码中编写 native 方法
使用 javah
生成 jni 头文件
实现 jni
方法
编译生成 so 动态链接库
在 Java 中使用动态链接库
静态调用 1 2 3 4 public static native String getFullName (String firstName) ; JNIEXPORT jstring JNICALL Java_CallC_getFullName (JNIEnv *, jclass, jstring) ;
静态调用生成的 JNI 方法,第二个参数为 jclass,代表 Java 中 CallC.class 这个对象。
对象调用 1 2 3 4 public native String getFirstName (String[] fullName) ; JNIEXPORT jstring JNICALL Java_CallC_getFirstName (JNIEnv *, jobject, jobjectArray) ;
对象调用生成的 JNI 方法,第二个参数为 jobject,代表 Java 中 CallC 这个对象。
示例
Java 调 C 传入一个字符串,返回拼接后的新字符串
Java 调 C 传入一个字符串数组,返回数组第一个元素
CallC.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 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "CallC.h" JNIEXPORT jstring JNICALL Java_CallC_getFullName (JNIEnv* env, jclass jcl, jstring firstName) { const char * fn = (*env)->GetStringUTFChars(env, firstName, JNI_FALSE); const char * ln = " Liu" ; char src[50 ], dest[50 ]; strcpy (src, ln); strcpy (dest, fn); strcat (dest, src); return (*env)->NewStringUTF(env, dest); } JNIEXPORT jstring JNICALL Java_CallC_getFirstName (JNIEnv* env, jobject jobj, jobjectArray fullName) { jstring elm = (jstring) (*env)->GetObjectArrayElement(env, fullName, 0 ); return elm; }
CallC.java 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public class CallC { public static native String getFullName (String firstName) ; public native String getFirstName (String[] fullName) ; public static void main (String[] args) { System.out.println(getFullName("Gavin" )); String[] fullName = {"Gavin" , "Liu" }; System.out.println(new CallC ().getFirstName(fullName)); } static { System.load("/Users/gavin/Workspace/Study-JNI/02.Java2C/libcallC.so" ); } }
C 调 Java C 调 Java 的套路:
获取 FieldID 或者 MethedID
通过 ID 可以 GET|SET Field,和调用 Methed
其中第一步需要用到 Field 和 Methed 签名,可以用 javap
命令获取:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 javap -s -p CallJava Compiled from "CallJava.java" public class CallJava { public java.lang.String firstName; descriptor: Ljava/lang/String; public CallJava(); descriptor: ()V public native java.lang.String changeName(); descriptor: ()Ljava/lang/String; public native java.lang.String sayHiFromC(); descriptor: ()Ljava/lang/String; public static void main(java.lang.String[]); descriptor: ([Ljava/lang/String;)V public void sayHiFromJava(); descriptor: ()V static {}; descriptor: ()V }
示例
C 改变 Java 中的成员变量
C 调用 Java 方法
CallJava.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 35 36 37 38 39 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "CallJava.h" JNIEXPORT jstring JNICALL Java_CallJava_changeName (JNIEnv * env, jobject obj) { jclass cls = (*env)->GetObjectClass(env, obj); jfieldID firstNameID = (*env)->GetFieldID(env, cls, "firstName" , "Ljava/lang/String;" ); jstring firstName = (*env)->GetObjectField(env, obj, firstNameID); const char * str = (*env)->GetStringUTFChars(env, firstName, JNI_FALSE); char src[50 ] = "super " ; strcat (src, str); jstring newName = (*env)->NewStringUTF(env, src); (*env)->SetObjectField(env, obj, firstNameID, newName); return newName; } JNIEXPORT void JNICALL Java_CallJava_sayHiFromC (JNIEnv * env, jobject obj) { jclass cls = (*env)->GetObjectClass(env, obj); jmethodID methodID = (*env)->GetMethodID(env, cls, "sayHiFromJava" , "()V" ); (*env)->CallVoidMethod(env, obj, methodID); printf ("%s\n" , "sayHiFromC" ); }
CallJava.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 public class CallJava { static { System.load("/Users/gavin/Workspace/Study-JNI/03.C2Java/libcallJava.so" ); } public native String changeName () ; public native void sayHiFromC () ; public String firstName = "Gavin" ; public static void main (String[] args) { CallJava c = new CallJava (); System.out.println("c.firstName \t" + c.firstName); System.out.println("c.changeName() \t" + c.changeName()); System.out.println("c.firstName \t" + c.firstName); c.sayHiFromC(); } public void sayHiFromJava () { System.out.println("sayHiFromJava" ); } }
1 2 3 4 5 c.firstName Gavin c.changeName() super Gavin c.firstName super Gavin sayHiFromJava sayHiFromC
其他 JNIEnv
中还有很多方法,就不作一一介绍,掌握了其套路,代码就很容易写了,比如说调用方法是CallVoidMethod
,调用静态方法就是CallStaticVoidMethod
。
C & C++ 的一些不同 JNIEnv 对象不一样 在 C 中:JNIEnv*
是一个二级指针,所以是使用 (*env)->
在 C++中:JNIEnv*
是一个一级指针,则是使用 env->
引用类型不一样 在 C 中:是使用结构体实现 在 C++中:是使用对象实现
官方文档 http://docs.oracle.com/javase/7/docs/technotes/guides/jni/spec/jniTOC.html