Java通过JNI调用Linux下C库详解

1. 编译环境准备

1.1 linux-C/C++编译环境

需要根据实际需要,安装编译环境,gcc 或者 g++。

1.2 linux-java编译环境

安装java环境,需要java/javah/javac可执行。

2. java调用JNI中间件编写(java<->JNI)

2.1 创建java文件TestJNI.java

如果通过maven创建工程,输入对应的groupId,在对应的路径下创建java文件。

假如路径是:cn.cmguan.saas.device.dev.test

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package cn.cmguan.saas.device.dev.test;

public class TestJNI{

static {
System.loadLibrary("JNITest");
}

public static native int testSum(int i, int j);

public static native String getDyPwdB(String mac);
public static native String getDyPwdK(String key);

public static native String getEnByN(String pId,String ts);
public static native String getEnByM(String mac,String ts);
}

System.loadLibrary(“JNITest”);里面引用的JNITest库是后续所需的C动态库:libJNITest.so

linux下静态库是:libxxx.a

linux下动态库是:libxxx.so

2.2 生成JNI调用中间件的.h头文件

  1. 生成对应的class文件
`javac -encoding utf-8 TestJNI.java`

生成:TestJNI.class
  1. 生成对应包名的.h文件
到工程的主目录java下:

`javah cn.cmguan.saas.device.dev.test.TestJNI` 

生成:cn\_cmguan\_saas\_device\_dev\_test\_TestJNI.h

2.3 外部调用中间件

外部对TestJNI.java的调用,示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public class RunTest {

public static void main(String[] args)
{
System.out.println("start>>>>");
int num = TestJNI.testSum(10,33);
System.out.println("testSum: "+num);
String s1 = TestJNI.getDyPwdB("a910e107004b1200");
System.out.println("getDyPwdB: "+s1);
String s2 = TestJNI.getDyPwdK("zzweiyhdd11213123");
System.out.println("getDyPwdK: "+s2);
String s3 = TestJNI.getEnByN("0000000000802417","1644389492");
System.out.println("getEnByN: "+s3);
String s4 = TestJNI.getEnByM("a910e107004b1200","1644389495");
System.out.println("getEnByM: "+s4);
System.out.println("end>>>>");
}
}

3. JNI调用的C代码实现(JNI<->C)

3.1 C代码头文件

在上述步骤完成后,我们可以看到生成的头文件cn_cmguan_saas_device_dev_test_TestJNI.h代码如下:

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
47
48
49
50
51
52
53
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class cn_cmguan_saas_device_dev_test_TestJNI */

#ifndef _Included_cn_cmguan_saas_device_dev_test_TestJNI
#define _Included_cn_cmguan_saas_device_dev_test_TestJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: cn_cmguan_saas_device_dev_test_TestJNI_testSum
* Method: testSum
* Signature: (II)I
*/
JNIEXPORT jint JNICALL Java_cn_cmguan_saas_device_dev_test_TestJNI_testSum
(JNIEnv *, jclass, jint, jint);

/*
* Class: cn_cmguan_saas_device_dev_test_TestJNI_getDyPwdB
* Method: getDyPwdB
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_cmguan_saas_device_dev_test_TestJNI_getDyPwdB
(JNIEnv *, jclass, jstring);

/*
* Class: cn_cmguan_saas_device_dev_test_TestJNI_getDyPwdK
* Method: getDyPwdK
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_cmguan_saas_device_dev_test_TestJNI_getDyPwdK
(JNIEnv *, jclass, jstring);

/*
* Class: cn_cmguan_saas_device_dev_test_TestJNI_getEnByN
* Method: getEnByN
* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_cmguan_saas_device_dev_test_TestJNI_getEnByN
(JNIEnv *, jclass, jstring, jstring);

/*
* Class: cn_cmguan_saas_device_dev_test_TestJNI_getEnByM
* Method: getEnByM
* Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT jstring JNICALL Java_cn_cmguan_saas_device_dev_test_TestJNI_getEnByM
(JNIEnv *, jclass, jstring, jstring);

#ifdef __cplusplus
}
#endif
#endif

3.2 C代码实现

接下来创建对应的C文件:cn_cmguan_saas_device_dev_test_TestJNI.c

假如我们在这个c函数里面需要实现其他复杂的功能,需要多文件情况下,就创建多个c文件。

例如:cJniTest.h, cJniTest.c

1
2
3
4
5
6
7
8
9
10
11
12
13
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include "cJniTest.h"

char* operation(char* str1,char* str2)
{
char tmpBuff[128] = {0};
sprintf(tmpBuff,"%s-%s",str1,str2);
return tmpBuff;
}

然后就是对头文件中函数的实现。

我们以两个函数为例:

3.2.1 Java_cn_cmguan_saas_device_dev_test_TestJNI_testSum

1
2
3
4
5
6
7
JNIEXPORT jint JNICALL  Java_cn_cmguan_saas_device_dev_test_TestJNI_testSum
(JNIEnv * jEnv, jclass c, jint i, jint j)
{
printf("Java_cn_cmguan_saas_device_dev_test_TestJNI_testSum start >>>>>>>>>>>>>>>>\n");
return (i+j);
}

3.2.2 Java_cn_cmguan_saas_device_dev_test_TestJNI_getEnByN

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
JNIEXPORT jstring JNICALL  Java_cn_cmguan_saas_device_dev_test_TestJNI_getEnByN
(JNIEnv *jEnv, jclass c, jstring pId, jstring times)
{
printf("Java_cn_cmguan_saas_device_dev_test_TestJNI_getEnByN start >>>>>>>>>>>>>>>>\n");
// 取出参数pId
uint8_t *perID = (uint8_t *)(*jEnv)->GetStringUTFChars(jEnv, pId, 0);
if( NULL == perID ) {
return NULL;
}
// 取出参数times
uint8_t *timestamp = (uint8_t *)(*jEnv)->GetStringUTFChars(jEnv, times, 0);
if( NULL == timestamp ) {
return NULL;
}

// 复制一份
uint8_t cPersonID[16];
memset(cPersonID,0,sizeof(cPersonID));
strncpy(cPersonID,perID,sizeof(cPersonID));

// 复制一份
uint8_t ctt[10];
memset(ctt,0,sizeof(ctt));
strncpy(ctt,timestamp,sizeof(ctt));

//释放指针,减少引用计数
(*jEnv)->ReleaseStringUTFChars(jEnv, pId, personID);
(*jEnv)->ReleaseStringUTFChars(jEnv, times, timestamp);

// 运算
//char tmpBuff[128] = {0};
//sprintf(tmpBuff,"%s-%s",cPersonID,ctt);
char* tmpBuff = operation(cPersonID,ctt);

// 返回
return (*jEnv)->NewStringUTF(jEnv,tmpBuff);
}

4. C动态库编译

java通过jni调用可实现对c动态库的调用。

通过gcc 编译c动态库:(-rdynamic -shared -fPIC)

1
2
3
4
CFLAGS_SO=-Wall -Os -rdynamic -shared -fPIC
INCLUDE=-I./ -I/usr/java/jdk1.8.0_311-amd64/include -I/usr/java/jdk1.8.0_311-amd64/include/linux

so: gcc $(CFLAGS_SO) $(INCLUDE) cn_cmguan_saas_device_dev_test_TestJNI.c cJniTest.c -o libJNITest.so

make so

5. java执行测试

将java程序生成对应的jar文件后,把生成的C动态库放到java执行的当前目录。

执行jar程序:

java -Djava.library.path='.' -classpath TestJNI-1.0-SNAPSHOT.jar "cn.cmguan.saas.device.dev.test.RunTest"

即可实现java对C库的调用。