jni调用已有的c语言库

上一篇写了jni如何操作java对象,但是实际应用中一般不会这样干。我们使用jni的主要用途是调用现成的c语言好的动态库或静态库,那本文以linux下的c语言库为例子,介绍一下jni如何调用已经写好的c语言库。

c语言库

下面是一个c语言库的例子,直接贴代码。

mystr.h

1
2
3
void print_str();
char* get_info();
char* hello(char *name);

mystr.c

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#include "mystr.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void print_str()
{
printf("中华人民共和国万岁,世界人民大团结万岁!\n");
}
char* get_info()
{
static char info[100];
strcpy(info, "我是中国人!");
return info;
}
char* hello(char *name)
{
char *s = "你好啊,";
char *buf = (char*)malloc(strlen(name)+strlen(s)+1);
strcpy(buf, s);
strcat(buf, name);
return buf;
}

mynum.h

1
int get_num1();

mynum.c

1
2
3
4
5
#include "mynum.h"
int get_num1()
{
return 1;
}

生成动态库

1
2
$ gcc -fPIC -c mystr.c mynum.c
$ gcc -shared -o libtest3.so mystr.o mynum.o

这个是分解动作,可以合成一个命令,看后面的makefile怎么写吧。

生成静态库

1
2
$ gcc -c mystr.c mynum.c
$ ar cr libtest3.a mystr.o mynum.o

这是两个不同的命令,ar其实就是把.o文件打包(解包命令是这样的:ar -x libtest3.a),但是这样打出来的.a文件是不能被jni调用的(我现在是不会)。
什么样的静态库文件能被jni调用呢?还是看后面的makefile文件。

Jni

直接贴代码。

MyJni.java

1
2
3
4
5
6
public class MyJni {
public native void printStr();
public native int getNum1();
public native String getInfo();
public native String hello(String name);
}

MyJniImpl.c

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
#include "MyJni.h"
#include "mystr.h"
#include "mynum.h"
#include <stdlib.h>
JNIEXPORT void JNICALL Java_MyJni_printStr
(JNIEnv *e, jobject o)
{
print_str();
}
JNIEXPORT jint JNICALL Java_MyJni_getNum1
(JNIEnv *e, jobject o)
{
return get_num1();
}
JNIEXPORT jstring JNICALL Java_MyJni_getInfo
(JNIEnv *env, jobject obj)
{
char *info = get_info();
return (*env)->NewStringUTF(env, info);
}
JNIEXPORT jstring JNICALL Java_MyJni_hello
(JNIEnv *env, jobject obj, jstring name)
{
const jbyte *strName = (*env)->GetStringUTFChars(env, name, 0);
char *helloInfo = hello((char*)strName);
(*env)->ReleaseStringUTFChars(env, name, strName);
jstring info = (*env)->NewStringUTF(env, helloInfo);
free(helloInfo);
return info;
}

说明几点个人看法(不一定对)

  • java只能加载jni动态库
  • jni遵循自己的内存自己释放的原则,但是需要手动调用,所以写jni代码的时候应该GetStringUTFCharsReleaseStringUTFChars要成对出现
  • 上面例子中故意没有遵循谁申请谁释放原则,在jni实现中调了free

测试

直接贴代码。

main.c

1
2
3
4
5
6
7
8
9
10
11
#include "mystr.h"
#include "mynum.h"
#include <stdio.h>
int main()
{
print_str();
printf("%d\n", get_num1());
printf("%s\n", get_info());
printf("%s\n", hello("小明"));
return 0;
}

Main.java

1
2
3
4
5
6
7
8
9
10
11
12
public class Main {
static {
System.loadLibrary("myjni");
}
public static void main(String[] args) {
MyJni mj = new MyJni();
mj.printStr();
System.out.println(mj.getNum1());
System.out.println(mj.getInfo());
System.out.println(mj.hello("小明"));
}
}

正常情况这两个c和java的代码输出应该是一样的。

makefile

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
JDKHOME = $(JAVA_HOME)
JNIINC = -I$(JDKHOME)/include -I$(JDKHOME)/include/linux
main: lib_a jni_lib
gcc main.c -L. -ltest3
javac Main.java
lib_so:
gcc -fPIC -shared -o libtest3.so mystr.c mynum.c
lib_a:
gcc -fPIC -c mystr.c mynum.c
ar cr libtest3.a mystr.o mynum.o
rm mystr.o mynum.o
jni_h:
javac MyJni.java
javah MyJni
jni_lib:
gcc $(JNIINC) -fPIC -shared -o libmyjni.so MyJniImpl.c -L. -ltest3
clean:
rm *.class *.o a.out libmyjni.so libtest3.so libtest3.a

把上面的main依赖的lib_a改成lib_so,可以分别测试jni调用c的静态库和动态库,如果不行就把静态库编译时也加上-fPIC重新编译下再说哦。因为别的我还不会(^o^)