JNI即Java Native Interface,是Java与本地代码(多指C/C++)交互的机制。Java是平台无关的,移植性强,但有时候由于特殊原因,需要与本地接口交互,也就是说在Java中调用C/C++函数,或者在C/C++中调用Java接口,下面介绍其实现方法。
1、在Java中声明native方法
与普通的Java方法类似,只不过这个方法需要使用native关键字声明,而且只需要native声明即可,不用定义具体的行为。如下面例子中的TestJNI类,testJniAdd是个native方法,在test方法中被调用。
// TestJNI.java
class TestJNI
{
private native int testJniAdd(int v1, int v2); // using native to declaration
private void test()
{
System.out.println("called native from java: testJniAdd(10, 11) is " + testJniAdd(10, 11));
}
public static void main(String args[])
{
new TestJNI().test();
}
static
{
System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
}
}
2、定义native方法
如果熟悉JNI规则的话,我们可以直接在C/C++中定义native方法,不过还可以借助javah工具帮我们生成.h头文件,毕竟这样还是很方便的。
使用javac生成对应的class文件——
$ javac TestJNI.java
使用javah生成对应的.h文件——
$ javach TestJNI
这样TestJNI.h就生成了,我们来看一下其内容:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class TestJNI */
#ifndef _Included_TestJNI
#define _Included_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/* * Class: TestJNI * Method: testJniAdd * Signature: (II)I */
JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *, jobject, jint, jint);
#ifdef __cplusplus
}
#endif
#endif // _Included_TestJNI
jni.h是个重要的文件,里面定义了许多有用的数据类型和接口以供JNI使用。
__cplusplus宏定义的处理C/C++代码的使用规则,以C的方式来编译C++中的函数。
重点在于Java_TestJNI_testJniAdd,在testJniAdd方法名前添加了Java标识和TestJNI类名,参数类型和返回值类型的int变成了JNI的jint,而且还多了两个参数JNIEnv和jobject,以及JNICALL和JNIEXPORT的使用,这都是JNI的规则,这些数据类型都可以在jni.h中找到。
Signature签名是JNI的一个特色,用以标记方法的参数类型和返回值类型,圆括号中的是参数类型,括号后面的是返回值类型,上面代码中的I即jint,jni.h中定义了这些标识,如下:
typedef union jvalue {
jboolean z;
jbyte b;
jchar c;
jshort s;
jint i;
jlong j;
jfloat f;
jdouble d;
jobject l;
} jvalue;
然后新建TestJNI.c,定义native方法——
// TestJNI.c
#include "TestJNI.h"
#include <stdio.h>
JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *env, jobject obj, jint v1, jint v2)
{
printf("%s\n", __func__);
return v1 + v2;
}
这个native方法只是简单的将两个值相加并返回。
3、编译native方法为共享库
我们可以添加一个Android.mk,然后使用native-build工具来编译生成一个共享库。一个更简单的方法是使用gcc编译:
$ gcc -fpic -shared -o libjnitest.so TestJNI.c
使用gcc编译生成libjnitest.so,有时候可能因为找不到jni.h而编译错误,可使用gcc的-I参数:
$ gcc -fpic -shared -o libjnitest.so TestJNI.c -Ixxx
xxx为本地jni.h的绝对路径。
4、在Java中加载共享库并使用native方法
上面TestJNI.java代码中,使用了System.loadLibrary来加载共享库,参数为jnitest,去掉前缀lib和后缀.so,还可以使用System.load来加载共享库,参数为共享库的绝对路径。
5、运行
$ java TestJNI
下面是log输出,可见Java调用native方法成功。
Java_TestJNI_testJniAdd
called native from java: testJniAdd(10, 11) is 21
上面介绍了在Java中调用native的一般方法,那么反过来在native中调用Java呢,操作步骤一样,对代码稍作修改即可。
在TestJNI类中添加negate方法给native调用——
class TestJNI
{
private native int testJniAdd(int v1, int v2); // using native to declaration
private void test()
{
System.out.println("called native from java: testJniAdd(10, 11) is " + testJniAdd(10, 11));
}
public int negate(int v)
{
return -v;
}
public static void main(String args[])
{
new TestJNI().test();
}
static
{
System.loadLibrary("jnitest"); // load native lib with xxx of libxxx.so from the right path
}
}
negate方法用于取一个数值的相反值。
在TestJNI.c中的Java_TestJNI_testJniAdd这个native实现中调用Java方法——
#include "TestJNI.h"
#include <stdio.h>
#include <stdlib.h>
JNIEXPORT jint JNICALL Java_TestJNI_testJniAdd(JNIEnv *env, jobject obj, jint v1, jint v2)
{
printf("%s\n", __func__);
jclass cls = (*env)->FindClass(env, "TestJNI");
jmethodID mid = (*env)->GetMethodID(env, cls, "negate", "(I)I");
jint ret = (*env)->CallIntMethod(env, obj, mid, 12);
printf("called java from native: negate(12) is %d\n", ret);
return v1 + v2;
}
在native中调用Java方法,正是利用了native方法的参数env和obj,下面是JNIEnv和jobject的定义:
#ifdef __cplusplus
class _jobject {};
typedef _jobject* jobject;
#else
typedef void* jobject;
#endif
#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
#else
typedef const struct JNINativeInterface* JNIEnv;
#endif
我们的native方法是在C代码中定义的,所以JNIEnv为const struct JNINativeInterface*,jobject为void*,首先通过FindClass获取jclass,然后通过GetMethodID获取jmethodID,最后根据方法的返回值类型为int而调用CallIntMethod,这样任务就完成了。
运行结果——
Java_TestJNI_testJniAdd
called java from native: negate(12) is -12
called native from java: testJniAdd(10, 11) is 21
可见,native中调用Java方法成功。