JNI调用机制(一)

| 分类 Java  | 标签 JNI 

JNI是Java Native Interface的缩写,从Java1.1开始就成为了Java标准的一部分。JNI的定义了一种管理方式使Java能够与C/C++互相调用;它支持从动态链接库中loading code,且工作效率尚可。使用JNI调用现有的本地库,极大灵活了Java的开发(Java本身编写底层应用实现比较困难)。

JNI中的一些概念

  • native : Java语言中修饰本地方法的修饰符, 被该修饰符修饰的方法没有方法体;

  • Native方法 : 在Java语言中被native关键字修饰的方法是Native方法;

  • JNI层 : Java声明Native方法的部分;

  • JNI函数 : JNIEnv提供的函数, 这些函数在jni.h中进行定义;

  • JNI方法 : Native方法对应的JNI层实现的 C/C++方法, 即在jni目录中实现的那些C语言代码;

C与Java如何交流

  • JNI规范 : C语言与Java语言交流需要一个适配器, 中间件, 即 JNI, JNI提供了一种规范;

  • C语言中调用Java方法 : 可以让我们在C代码中找到Java代码class中的方法, 并且调用该方法;

  • Java语言中调用C语言方法 : 同时也可以在Java代码中, 将一个C语言的方法映射到Java的某个方法上;

  • JNI桥梁作用 : JNI提供了一个桥梁, 打通了C语言和Java语言之间的障碍;

JNI作用 :

  • 扩展: JNI扩展了JVM能力, 驱动开发, 例如开发一个wifi驱动, 可以将手机设置为无限路由;

  • 高效 : 本地代码效率高, 游戏渲染, 音频视频处理等方面使用JNI调用本地代码, C语言可以灵活操作内存;

  • 复用 : 在文件压缩算法 7zip开源代码库, 机器视觉 openCV开放算法库 等方面可以复用C平台上的代码, 不必在开发一套完整的Java体系, 避免重复发明轮子;

  • 特殊 : 产品的核心技术一般也采用JNI开发, 不易破解;

Java语言执行流程 :

  • 编译字节码 : Java编译器编译 .java源文件, 获得.class 字节码文件;

  • 装载类库 : 使用类装载器装载平台上的Java类库, 并进行字节码验证;

  • Java虚拟机 : 将字节码加入到JVM中, Java解释器 和 即时编译器 同时处理字节码文件, 将处理后的结果放入运行时系统;

  • 调用JVM所在平台类库 : JVM处理字节码后, 转换成相应平台的操作, 调用本平台底层类库进行相关处理;

网上JNI的文章不多,我将自己的一些总结写在这里。如有错误,欢迎指出。

Java调用C/C++总结起来有如下三个步骤:

  1. 编写带有native方法的Java类,使用javac工具编译Java类

  2. 使用javah工具来生成与native方法对应的头文件(一般为XX.h文件)

  3. 实现这个头文件,并将其编译为动态链接库(.dll或.so)

下面是一个简单完整的Java调用C/C++的例子,改自IBM的一篇介绍JNI的文章icejoywoo的博客。

编写Java类

我们来编写一个JNITest的Java类。

public class JNITest {
	public native int intFunc(int n); //参数类型为int

	public native boolean booleanFunc(boolean bool);  //参数类型为boolean

	public native String stringFunc(String text);  //参数类型为String

	public native int intArrayFunc(int[] intArray);  //参数类型为int


	public static void main (String[] args) {
		//加载了动态类库,在windows下加载.dll,在Linux下加载.so,决不可加上后缀,要保证JNITest在path路径中

		System.loadLibrary("JNITest"); 
		JNITest jnitest = new JNITest();
		
		int square = jnitest.intFunc(5);
		boolean boolv = jnitest.booleanFunc(true);
		String text = jnitest.stringFunc("Hello JNI");
		int sum = jnitest.intArrayFunc(new int[]{1,2,3,4,5,6,7,8,9,0});

		System.out.println("intFunc: " + square);
		System.out.println("booleanFunc: " + boolv);
		System.out.println("stringFunc: " + text);
		System.out.println("intArrayFunc: " + sum);
	}
}

上面代码中的4个native方法就是我们需要用C来实现的方法。

编译JNITest文件,可以看到JNITest.class文件。

javah工具生成头文件

命令行运行

javah JNITest

可以看到目录下自动生成了一个头文件JNITest.h

/* 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:    intFunc
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_JNITest_intFunc
  (JNIEnv *, jobject, jint);

/*
 * Class:     JNITest
 * Method:    booleanFunc
 * Signature: (Z)Z
 */
JNIEXPORT jboolean JNICALL Java_JNITest_booleanFunc
  (JNIEnv *, jobject, jboolean);

/*
 * Class:     JNITest
 * Method:    stringFunc
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_JNITest_stringFunc
  (JNIEnv *, jobject, jstring);

/*
 * Class:     JNITest
 * Method:    intArrayFunc
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_JNITest_intArrayFunc
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

可以看到有4个函数声明,命名方式为 Java_完整类名_方法名,完整类名包括包名,假设我的demo.JNITest是完整类名,对应的就是demo_JNITest(本文不是)。 注释中有个方法签名(Signature),通过一个表格来介绍Signature。

Java类型 Signature 备注
boolean Z  
byte B  
char C  
short S  
int I  
long L  
float F  
double D  
void V  
object L用/分割的完整类名 例如:Ljava/lang/String表示String类型
Array [签名 例如:[I 表示int数组,[Ljava/lang/String表示String数组
Method (参数签名) 返回类型签名 例如:([I)I 表示参数类型为int数组,返回int类型的方法

上面头文件的第一个函数声明

JNIEXPORT jint JNICALL Java_Sample1_intMethod (JNIEnv *, jobject, jint);

注释中的签名是 Signature: (I)I

实现头文件中的函数

可以使用C/C++语言实现,下面是使用C语言实现的版本(其实C/C++)实现起来差不多。

#include "JNITest.h"
#include <string.h>
/* JNIEnv*:是每个函数都有的参数,代表的是Java环境, 通过这个环境可以调用Java里面的方法;
 *jobject参数: 调用C语言方法的对象, this对象表示当前的对象, jobject 指向在此 Java 代码中实例化的 Java 对象 LocalFunction 的一个句柄,相当于 this 指针即调用JNI方法所在的类;
 *
*/
JNIEXPORT jint JNICALL Java_JNITEST_intFunc(JNIEnv *env, jobject obj, jint num) {

	return num * num;
}

JNIEXPORT jboolean JNICALL Java_JNITEST_booleanFunc(JNIEnv *env, jobject obj, jboolean boolean) {

	return !boolean;
}

JNIEXPORT jstring JNICALL Java_JNITEST_stringFunc(JNIEnv *env, jobject obj, jstring string) {
	//GetStringUTFChars()这个方法,是Java和C/C++之间转换字符串,Java本身应的是双字节字符,而C使用的是单字节字符,故需要转换
	const char* str = (*env)->GetStringUTFChars(env, string, 0);
	char cap[128];
	strcpy(cap, str);
	//ReleaseStringUTFChars()是用来释放对象的, 在Java中有虚拟机进行垃圾回收, 但是在C语言中, 这些对象必须手动回收. 否则可能造成内存泄漏.
	(*env)->ReleaseStringUTFChars(env, string, 0);
	return (*env)->NewStringUTF(env, strupr(cap));
}

JNIEXPORT jint JNICALL Java_JNITEST_intArrayFunc(JNIEnv *env, jobject obj, jintArray array) {
	int i, sum = 0;
	jsize len = (*env)->GetArrayLength(env, array);
	jint *body = (*env)->GetIntArrayElements(env, array, 0);

	for (i = 0; i < len; i++) {
		sum += body[i];
	}
	return sum;
}

C++的代码和C的很像,只有一个不同点

C代码: (*env)->GetStringUTFChars(env, string, 0);

C++代码: env->GetStringUTFChars(string, 0);

C语言中使用的是结构体的函数指针, 而在C++中使用的还是struct, 我们知道struct在C++中和class的功能是几乎一样的, struct也可以用来定义类, 所以env在C++中是个类对象的指针.

编译和运行

MS的编译器,编译C语言版本的.dll

用VS新建dll工程,编译即可。

Linux的gcc编译器,编译C语言版本的.so

gcc命令编译

gcc -I /usr/lib/jvm/java-6-sun/include/linux/ -I /usr/lib/jvm/java-6-sun/include/ -I /home/mike_ming/workspace/JNI/ -fPIC -shared -o libJNITest.so JNIC.c

注意:

  1. -I /home/mike_ming/workspace/JNI/ 这个是指当前有JNITest.h文件的目录
  2. Linux规定库文件(.so)必须以lib开头,但是我们使用loadlibrary方法加载的是JNITest

运行

java -Djava.library.path=’.’ JNITest

-Djava.library.path 代表的就是当前的.so路径了

** 引用 **

Android 开发 之 JNI入门 - NDK从入门到精通

Dufresne

使用JNI进行Java与C/C++语言混合编程(1)–在Java中调用C/C++本地库


上一篇     下一篇