刘洪江的流水帐

拾起点点滴滴, 聚沙成石.

一个连咖啡都要趁热一饮而尽的男子

jni调用C和C++

| Tags: C/C++

摘要: java程序可以通过jni调用C或者C++的库。本文实现了一个简单的jni调用例子。

已经在工作中碰到了两个这样的例子了,项目用java,但需要调用C/C++的库。之前一个是用java写hadoop的job,但是调用的算法是用C++实现的,使用的是jni。现在好像jni在android上面也有用到,但是我不了解。我自己也不懂java,但实际工作中有的时候也要看看java代码。所以决定写一个简单的jni例子,了解一下jni怎么完成C/C++调用的。

对于jni的内部实现,我也是不求甚解。编写一个java程序

jni调用的java代码 (Sample1.java) download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Sample1 {
	public native int intMethod(int n);
	public native boolean booleanMethod(boolean bool);
	public native String stringMethod(String text);
	public native int intArrayMethod(int[] intArray);
	public static void main(String[] args) {
		System.loadLibrary("Sample1");
		Sample1 sample = new Sample1();

		int square = sample.intMethod(3);
		System.out.println("intMethod: " + square);

		boolean bool = sample.booleanMethod(true);
		System.out.println("booleanMethod: " + bool);

		String text = sample.stringMethod("java");
		System.out.println("stringMethod: " + text);

		int sum = sample.intArrayMethod(new int[] { 1, 1, 2, 3, 5, 8, 13 });
		System.out.println("intArrayMethod: " + sum);
	}
}

然后编译java文件

1
javac Sample1.java

编译完以后, 使用下面的命令生成jni调用的头文件

1
javah -classpath ./ -jni Sample1

生成的头文件为Sample1.h, 就是下面C/C++实现的函数的声明, 注意:Sample1.h是自动生成的

jni生成的头文件 (Sample1.h) download
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
40
41
42
43
44
45
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class Sample1 */

#ifndef _Included_Sample1
#define _Included_Sample1
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     Sample1
 * Method:    intMethod
 * Signature: (I)I
 */
JNIEXPORT jint JNICALL Java_Sample1_intMethod
  (JNIEnv *, jobject, jint);

/*
 * Class:     Sample1
 * Method:    booleanMethod
 * Signature: (Z)Z
 */
JNIEXPORT jboolean JNICALL Java_Sample1_booleanMethod
  (JNIEnv *, jobject, jboolean);

/*
 * Class:     Sample1
 * Method:    stringMethod
 * Signature: (Ljava/lang/String;)Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_Sample1_stringMethod
  (JNIEnv *, jobject, jstring);

/*
 * Class:     Sample1
 * Method:    intArrayMethod
 * Signature: ([I)I
 */
JNIEXPORT jint JNICALL Java_Sample1_intArrayMethod
  (JNIEnv *, jobject, jintArray);

#ifdef __cplusplus
}
#endif
#endif

有了头文件后,就可以实现这些调用的C/C++代码

C++实现的Sample1.cpp (Sample1.cpp) download
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 "Sample1.h"
#include <string.h>
#include <ctype.h>

JNIEXPORT jint JNICALL
Java_Sample1_intMethod(JNIEnv *env, jobject obj, jint num){
   return num * num;
}

JNIEXPORT jboolean JNICALL
Java_Sample1_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean){
   return !boolean;
}

JNIEXPORT jstring JNICALL
Java_Sample1_stringMethod(JNIEnv *env, jobject obj, jstring jstr){
     const char *str = env->GetStringUTFChars(jstr, 0);
     char cap[128];
     strcpy(cap, str);
     env->ReleaseStringUTFChars(jstr, str);
	 char* ptr = cap;
	 while (*ptr != '\0'){
		 *ptr = toupper(*ptr);
		 ptr++;
	 }
     return env->NewStringUTF(cap);
}

JNIEXPORT jint JNICALL
Java_Sample1_intArrayMethod(JNIEnv *env, jobject obj, jintArray array){
     int i, sum = 0;
     jsize len = env->GetArrayLength(array);
     jint *body = env->GetIntArrayElements(array, 0);
     for (i=0; i<len; i++) {
		sum += body[i];
     }
     env->ReleaseIntArrayElements(array, body, 0);
     return sum;
}

然后调用下面的命令生成动态链接库(系统:ubuntu 12.04,gcc编译)

1
g++ -I /usr/lib/jvm/java-1.6.0-openjdk/include/ -I /usr/lib/jvm/java-1.6.0-openjdk/include/linux/ Sample1.cpp -fPIC -shared -o libSample1.so

具体的jdk的路径应该是和本地的相适应的。

然后运行java程序

1
java Sample1

运行结果如下

图中加入了一个环境变量的声明,export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH, 当java加载动态链接库时,会在本地路径下找libSample1.so。

上面是调用C++的代码,C实现的代码如下

C语言实现的Sample1.c (Sample1.c) download
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
40
41
42
43
44
45
46
/*
 * the difference between C++ and c like this:
 *		const char *str = (*env)->GetStringUTFChars(env, jstr, 0);
 *		const char *str = env->GetStringUTFChars(jstr, 0);
 */


#include "Sample1.h"
#include <string.h>
#include <ctype.h>

JNIEXPORT jint JNICALL
Java_Sample1_intMethod(JNIEnv *env, jobject obj, jint num){
   return num * num;
}

JNIEXPORT jboolean JNICALL
Java_Sample1_booleanMethod(JNIEnv *env, jobject obj, jboolean boolean){
   return !boolean;
}

JNIEXPORT jstring JNICALL
Java_Sample1_stringMethod(JNIEnv *env, jobject obj, jstring jstr){
     const char *str = (*env)->GetStringUTFChars(env, jstr, 0);
     char cap[128];
     strcpy(cap, str);
     (*env)->ReleaseStringUTFChars(env, jstr, str);
	 char* ptr = cap;
	 while (*ptr != '\0'){
		 *ptr = toupper(*ptr);
		 ptr++;
	 }
     return (*env)->NewStringUTF(env, cap);
}

JNIEXPORT jint JNICALL
Java_Sample1_intArrayMethod(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];
     }
     (*env)->ReleaseIntArrayElements(env, array, body, 0);
     return sum;
}

C与C++的实现基本一样,唯一的差异在于用来访问 JNI 函数的方法。在 C 中,JNI 函数调用由“(*env)->”作前缀,目的是为了取出函数指针所引用的值。在 C++ 中,JNIEnv 类拥有处理函数指针查找的内联成员函数。下面将说明这个细微的差异,其中,这两行代码访问同一函数,但每种语言都有各自的语法。

1
2
C 语法:jsize len = (*env)->GetArrayLength(env,array);
C++ 语法:jsize len = env->GetArrayLength(array);

编译命令

1
gcc -I /usr/lib/jvm/java-1.6.0-openjdk/include/ -I /usr/lib/jvm/java-1.6.0-openjdk/include/linux/ Sample1.c -fPIC -shared -o libSample1.so

运行结果与上面C++的输出是一致的。

实际上我是用了一个脚本完成编译等工作的

完成整个过程的脚本 (run.sh) download
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
#!/bin/bash

echo "### compile Sample1.java"
javac Sample1.java

echo "### generate jni headerfile Sample1.h"
javah -classpath ./ -jni Sample1

echo "### compile cpp Sample1.so"
g++ -I /usr/lib/jvm/java-1.6.0-openjdk/include/ -I /usr/lib/jvm/java-1.6.0-openjdk/include/linux/ Sample1.cpp -fPIC -shared -o libSample1.so

export LD_LIBRARY_PATH=.:$LD_LIBRARY_PATH

echo "### run cpp"
echo "---------------"
java Sample1
echo "--------------- end"

rm libSample1.so


echo "### compile c Sample1.so"
gcc -I /usr/lib/jvm/java-1.6.0-openjdk/include/ -I /usr/lib/jvm/java-1.6.0-openjdk/include/linux/ Sample1.c -fPIC -shared -o libSample1.so

echo "### run c"
echo "---------------"
java Sample1

#rm libSample1.so Sample1.class Sample1.h

脚本的运行结果如下图

github上本文的例子代码 jni调用的例子

本文参考了以下两个blog的内容, 代码来自第二个blog。

Comments