JNI(Java Native Interface)java本地化接口,这是java平台一个非常强大的特征。java编写的应用程序可以通过JNI来调用本地化语言编写的程序,例如C/C++,调用过程就像调用java本身编写的代码一样简单。本文通过几个简单的例子来说明一下java语言调用C语言的过程和基本的常见的调用形式。
一、JNI使用的基本步骤
a、加载C库
b、建立java中方法与C语言当中的函数的映射关系
c、在java程序里面调用本地化方法
二、基本的开发环境
操作系统:Ubuntu12.04
GCC版本 : 4.6.3 (Ubuntu/Linaro 4.6.3-1ubuntu5)
JDK版本 : java version "1.7.0_101"
三、显式映射和隐式映射
将java中方法和C语言中的函数进行映射有两种方式,一种是显式映射,另一种是隐式映射。下面就通过一个例子来说明一下它们的区别,在java中实现一个本地化方法,分别通过显式调用和隐式调用两种方式来调用这个本地方法,观察程序的运行结果是否一致。java语言的代码如下所示:
public class JNIDemo {
static {
System.loadLibrary("c_demo"); // load c library, libc_demo.so
}
/* Program entry function */
public static void main(String args[]) {
printHello();
}
/* define java native method */
public native static void printHello();
}
这个java程序的功能能简单,通过调用本地化方法printHello来打印“Hello,world!”。下面来分别实现隐式映射和显式映射的本地化程序,关于具体的细节本文不进行讲解,如有兴趣课参考JNI相关文档和书籍。
3.1 隐式映射
隐式映射从操作上来讲是相对简单的。对上面的java代码执行以下命令:
javac -d . JNIDemo.java
javah -jni JNIDemo
会得到一个JNIDemo.h的文件,这个文件的内容如下:
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class JNIDemo */
#ifndef _Included_JNIDemo
#define _Included_JNIDemo
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: JNIDemo
* Method: printHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JNIDemo_printHello
(JNIEnv *, jclass);
#ifdef __cplusplus
}
#endif
#endif
在这个函数中声明了一个方法:Java_JNIDemo_printHello(),我们只需要在C程序中实现这个方法即可,C程序如下:#include <stdio.h>
#include <jni.h> /* 位于/usr/lib/jvm/java-1.7.0-openjdk-amd64/include */
/*
* Class: JNIDemo
* Method: printHello
* Signature: ()V
*/
JNIEXPORT void JNICALL Java_JNIDemo_printHello
(JNIEnv * env, jclass cls)
{
printf("Hello,world!\n");
}
执行如下命令来编译运行程序:gcc -shared -o libc_demo.so c_demo.c -I /usr/lib/jvm/java-1.7.0-openjdk-amd64/include -fPIC
export LD_LIBRARY_PATH=.
java JNIDemo
结果如下所示:3.2 显示映射
显示映射要比隐式映射复杂一些但是本地化函数的名称不必想隐式映射那样需要完全匹配才能够调用成功,这样说来显示映射的方法名是可以随便定义的,不受java方法名的限制。
在显示调用中需要使用一个数组来描述这个映射关系,这个数组是JNINativeMethod,它的定义在jvm/java-1.7.0-openjdk-amd64/include/jni.h这个文件中,它的定义如下:
* used in RegisterNatives to describe native method name, signature,
* and function pointer.
*/
typedef struct {
char *name;
char *signature;
void *fnPtr;
} JNINativeMethod;
在本例中,实现一个这种结构体变量来映射本地函数,具体如下:
/* 定义一个java语言和C语言的映射数组 */
static const JNINativeMethod methods[] = {
{"printHello", "()V", (void *)print_hello},
};
接下来需要对这个结构体数组进行映射,这样才能实现java的方法和C语言函数之间的映射关系,具体如下:/* 将java语言和C/C++语言进行映射 */
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0)
{
return JNI_ERR;
}
上面这个函数是java语言咋加载C库是调用的,调用的是JNI_OnLoad()方法,所以要把上面这个函数的调用放入这个当中,具体如下:/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
/* 获得JNI运行环境 */
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
/* 获取java定义的JNIHello类 */
cls = (*env)->FindClass(env, "JNIDemo");
if (cls == NULL) {
return JNI_ERR;
}
/* 将java语言和C/C++语言进行映射 */
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0)
{
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
显示映射实现的C语言程序完整代码如下所示:
#include <stdio.h>
#include <jni.h> /* 位于/usr/lib/jvm/java-1.7.0-openjdk-amd64/include */
JNIEXPORT void JNICALL print_hello(JNIEnv *env, jclass cls)
{
printf("Hello,world!\n");
}
/* 定义一个java语言和C语言的映射数组 */
static const JNINativeMethod methods[] = {
{"printHello", "()V", (void *)print_hello},
};
/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
/* 获得JNI运行环境 */
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
/* 获取java定义的JNIHello类 */
cls = (*env)->FindClass(env, "JNIDemo");
if (cls == NULL) {
return JNI_ERR;
}
/* 将java语言和C/C++语言进行映射 */
if ((*env)->RegisterNatives(env, cls, methods, 1) < 0)
{
return JNI_ERR;
}
return JNI_VERSION_1_4;
}
对这个代码进行编译,执行结果和隐式调用相同。
但是在真正的项目开发当中一般都使用显示调用的方式来进行,因为这种方式跟灵活而且也更容易对其进行维护。
四、几种简单调用形式
上面的例子java语言对c语言的调用方法中既没有参数也没有返回值,不能很好的表达java语言调用c语言的一般方法,所以下面来举几个例子说一说java语言调用C语言数据传递的几种常见情况。这几个例子都是通过显式映射来实现的。
4.1 基本数据类型的传递
基本数据类型的传递直接使用、直接返回即可。
在java代码中加入一个本地化函数如下所示:
public native static int printHello2(int num);
这个本地化方法带有一个整形的参数和一个整形的返回值。
在main函数中加入以下代码:
System.out.println("The return value is :" + printHello2(200));
在C代码中实现这个本地化方法,如下:/* 和JNIDemo中的printHello2对应 */
JNIEXPORT jint JNICALL print_hello2(JNIEnv *env, jclass cls, jint num)
{
printf("Hello,world! : %d\n", num);
return num + 1;
}
在JNI映射数组中加入一项:{"printHello2", "(I)I", (void *)print_hello2},
编译运行,结果如下:从上面结果可以看出,java程序把200传递给了C,C返回了201给java。
4.2 字符串的传递
字符串的传递不能像基本类型数据那样传递,因为C语言当中的字符串和java语言是不一样的。
在java程序中添加如下代码:
public native static String printHello3(String str);
/* Program entry function */
public static void main(String args[]) {
System.out.println(printHello3("JAVA to C : Hello,world!"));
}
在C程序中实现这个本地化方法,如下:/* 和JNIDemo中的printHello3对应 */
JNIEXPORT jstring JNICALL print_hello3(JNIEnv *env, jclass cls, jstring str)
{
const jbyte *cstr;
/* 获得java传过来的字符串的编码 */
cstr = (*env)->GetStringUTFChars(env, str, NULL);
if(NULL == cstr)
{
return NULL;
}
printf("%s\n", cstr); // 打印java传递过来的字符串
(*env)->ReleaseStringUTFChars(env, str, cstr); // 释放字符串
return (*env)->NewStringUTF(env, "C to JAVA : Hello,world!"); //返回给java
}
在JNI映射数组加入一项:{"printHello3", "(Ljava/lang/String;)Ljava/lang/String;", (void *)print_hello3},
编译并运行,结果如下:从以上可以看出,java和c之间成功的进行了字符串的传递。
4.3 数组的传递
以下关于数组传递实现两个小例子:一个实现java程序传递一个数组给C程序,C程序把计算结果返回给java程序;另一个实现java程序传递一个数组给C程序,C程序对数组中每一项加10在返回给java程序。
4.3.1 第一个例子
在java程序中添加如下代码:
public native static int intArraySum(int[] array);
/* Program entry function */
public static void main(String args[]) {
int[] array = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
System.out.println("The sum is : " + intArraySum(array));
}
在C程序中实现这个本地化方法,如下:/* 和JNIDemo中的intArraySum对应 */
JNIEXPORT jint JNICALL int_array_sum(JNIEnv *env, jclass cls, jintArray array)
{
jint *carr;
jint arrayLength = 0;
jint i, sum = 0;
carr = (*env)->GetIntArrayElements(env, array, NULL); // 获得这个整形数组的元素
if(NULL == carr)
return -1; // 没有获取到数组元素
arrayLength = (*env)->GetArrayLength(env, array); // 获得这个整形数组的长度
/* 遍历数组对元素进行求和 */
for(i = 0; i < arrayLength; i++)
{
printf("%d ", carr[i]);
sum += carr[i];
}
printf("\n");
(*env)->ReleaseIntArrayElements(env, array, carr, 0); // 释放这个数组
return sum; // 返回求和结果
}
在JNI映射数组中加入一项:{"intArraySum", "([I)I", (void *)int_array_sum},
编译运行结果如下:从上面可以看出,java传递一个含有10个元素的整形数组给C,C对数组元素进行求和返回给java。
4.3.2 第二个例子
在java程序中添加如下代码:
public native static int[] intArrayToArray(int[] array);
/* Program entry function */
public static void main(String args[]) {
int[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] array2 = null;
array2 = intArrayToArray(array1);
System.out.print("C to JAVA : ");
for(int i = 0; i < array2.length; i++) {
System.out.print(array2[i] + " ");
}
System.out.println();
}
在C程序中实现这个本地化方法,如下:/* 和JNIDemo中的intArrayToArray对应 */
JNIEXPORT jintArray JNICALL int_array_to_array(JNIEnv *env, jclass cls, jintArray array)
{
jint *carr;
jint *oarr;
jint arrayLength = 0;
jint i;
jintArray return_array;
carr = (*env)->GetIntArrayElements(env, array, NULL); // 获得这个整形数组的元素
if(NULL == carr)
return NULL; // 没有获取到数组元素
arrayLength = (*env)->GetArrayLength(env, array); // 获得这个整形数组的长度
/* 遍历数组将元素打印出来 */
printf("JAVA to C : ");
for(i = 0; i < arrayLength; i++)
{
printf("%d ", carr[i]);
}
printf("\n");
oarr = malloc(sizeof(jint) * arrayLength); /* 分配一个区域用于存放数组元素 */
if(NULL == oarr)
return NULL; // 申请空间失败
/* 将java传入的数组每一项加10 */
for(i = 0; i < arrayLength; i++)
oarr[i] = carr[i] + 10;
return_array = (*env)->NewIntArray(env, arrayLength); // 给定义的数组分配长度
(*env)->SetIntArrayRegion(env, return_array, 0, arrayLength, oarr); // 给这个数组每一项赋值
(*env)->ReleaseIntArrayElements(env, array, carr, 0); //释放java传入的数组
free(oarr); // 释放前面申请的空间
return return_array;
}
在JNI映射数组中添加一项:{"intArrayToArray", "([I)[I", (void *)int_array_to_array},
编译运行结果如下:从结果可以看出,javac传递一个含有10个元素的整形数组给C,C对数组中每一项元素加10有返回给java,java把这个结果打印出来。
附录:本文实现的完整的程序代码如下所示。
JNIDemo.java
public class JNIDemo {
static {
System.loadLibrary("c_demo"); // load c library, libc_demo.so
}
/* Program entry function */
public static void main(String args[]) {
int[] array1 = {1, 2, 3, 4, 5, 6, 7, 8, 9, 10};
int[] array2 = null;
array2 = intArrayToArray(array1);
System.out.print("C to JAVA : ");
for(int i = 0; i < array2.length; i++) {
System.out.print(array2[i] + " ");
}
System.out.println();
}
/* define java native method */
public native static void printHello();
public native static int printHello2(int num);
public native static String printHello3(String str);
public native static int intArraySum(int[] array);
public native static int[] intArrayToArray(int[] array);
}
c_demo.c
#include <stdio.h>
#include <stdlib.h>
#include <jni.h> /* 位于/usr/lib/jvm/java-1.7.0-openjdk-amd64/include */
/* 和JNIDemo中的printHello对应 */
JNIEXPORT void JNICALL print_hello(JNIEnv *env, jclass cls)
{
printf("Hello,world!\n");
}
/* 和JNIDemo中的printHello2对应 */
JNIEXPORT jint JNICALL print_hello2(JNIEnv *env, jclass cls, jint num)
{
printf("Hello,world! : %d\n", num);
return num + 1;
}
/* 和JNIDemo中的printHello3对应 */
JNIEXPORT jstring JNICALL print_hello3(JNIEnv *env, jclass cls, jstring str)
{
const jbyte *cstr;
/* 获得java传过来的字符串的编码 */
cstr = (*env)->GetStringUTFChars(env, str, NULL);
if(NULL == cstr)
{
return NULL;
}
printf("%s\n", cstr); // 打印java传递过来的字符串
(*env)->ReleaseStringUTFChars(env, str, cstr); // 释放字符串
return (*env)->NewStringUTF(env, "C to JAVA : Hello,world!"); //返回给java
}
/* 和JNIDemo中的intArraySum对应 */
JNIEXPORT jint JNICALL int_array_sum(JNIEnv *env, jclass cls, jintArray array)
{
jint *carr;
jint arrayLength = 0;
jint i, sum = 0;
carr = (*env)->GetIntArrayElements(env, array, NULL); // 获得这个整形数组的元素
if(NULL == carr)
return -1; // 没有获取到数组元素
arrayLength = (*env)->GetArrayLength(env, array); // 获得这个整形数组的长度
/* 遍历数组对元素进行求和 */
for(i = 0; i < arrayLength; i++)
{
printf("%d ", carr[i]);
sum += carr[i];
}
printf("\n");
(*env)->ReleaseIntArrayElements(env, array, carr, 0); // 释放这个数组
return sum; // 返回求和结果
}
/* 和JNIDemo中的intArrayToArray对应 */
JNIEXPORT jintArray JNICALL int_array_to_array(JNIEnv *env, jclass cls, jintArray array)
{
jint *carr;
jint *oarr;
jint arrayLength = 0;
jint i;
jintArray return_array;
carr = (*env)->GetIntArrayElements(env, array, NULL); // 获得这个整形数组的元素
if(NULL == carr)
return NULL; // 没有获取到数组元素
arrayLength = (*env)->GetArrayLength(env, array); // 获得这个整形数组的长度
/* 遍历数组将元素打印出来 */
printf("JAVA to C : ");
for(i = 0; i < arrayLength; i++)
{
printf("%d ", carr[i]);
}
printf("\n");
oarr = malloc(sizeof(jint) * arrayLength); /* 分配一个区域用于存放数组元素 */
if(NULL == oarr)
return NULL; // 申请空间失败
/* 将java传入的数组每一项加10 */
for(i = 0; i < arrayLength; i++)
oarr[i] = carr[i] + 10;
return_array = (*env)->NewIntArray(env, arrayLength); // 给定义的数组分配长度
(*env)->SetIntArrayRegion(env, return_array, 0, arrayLength, oarr); // 给这个数组每一项赋值
(*env)->ReleaseIntArrayElements(env, array, carr, 0); //释放java传入的数组
free(oarr); // 释放前面申请的空间
return return_array;
}
/* 定义一个java语言和C语言的映射数组 */
static const JNINativeMethod methods[] = {
{"printHello", "()V", (void *)print_hello},
{"printHello2", "(I)I", (void *)print_hello2},
{"printHello3", "(Ljava/lang/String;)Ljava/lang/String;", (void *)print_hello3},
{"intArraySum", "([I)I", (void *)int_array_sum},
{"intArrayToArray", "([I)[I", (void *)int_array_to_array},
};
/* System.loadLibrary */
JNIEXPORT jint JNICALL
JNI_OnLoad(JavaVM *jvm, void *reserved)
{
JNIEnv *env;
jclass cls;
/* 获得JNI运行环境 */
if ((*jvm)->GetEnv(jvm, (void **)&env, JNI_VERSION_1_4)) {
return JNI_ERR; /* JNI version not supported */
}
/* 获取java定义的JNIHello类 */
cls = (*env)->FindClass(env, "JNIDemo");
if (cls == NULL) {
return JNI_ERR;
}
/* 将java语言和C/C++语言进行映射 */
if ((*env)->RegisterNatives(env, cls, methods, sizeof(methods)/sizeof(methods[0])) < 0)
{
return JNI_ERR;
}
return JNI_VERSION_1_4; /* 返回JNI版本 */
}