摘要:
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文件
编译完以后, 使用下面的命令生成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程序
运行结果如下
图中加入了一个环境变量的声明,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。