jni调用C和C++

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

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

jni调用的java代码view raw
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生成的头文件view raw
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.cppview raw
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.cview raw
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++的输出是一致的。

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

完成整个过程的脚本view raw
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。