使用流程
简单说一下 jni 的使用流程就像一个圆,从 java 出发,声明你需要的 native 方法,生成一个对应的.h 文件,根据这个 h 文件构造 c/c++工程生成 dll/so 动态库,最后返回来给 java 调用。
1 : java class
src/jni/LibTest.java:
1
2
3
4
5
6
7
| public class LibTest {
static {
System.loadLibrary("jni_lib_LibTest");
}
public native static void HelloWorld(String i_instr);
}
|
src/JniTest.java:
1
2
3
4
5
| public class JniTest {
public static void main(String[] args) {
LibTest.HelloWorld(new String("I'm Pancake"));
}
}
|
到这里 java 的代码可以说是全部写完了,当 main 方法执行时,将调用 LibTest 的 HelloWorld 方法,该方法的实现来自于 jni_lib_LibTest.dll(libjni_lib_LibTest.so)中,那么这个工程现在缺少的就是这个 c++动态库了,下面来生成这个动态库。
2 : dll/so
无论是 windows 还是 linux,你需要确保命令行可以运行 javac
- 在定义 native 的.java 文件目录下用 javac 编译该文件获得.class 文件。
1
2
| cd src/jni/
javac LibTest.java
|
- 然后前往该项目的源码目录,用 javah 处理刚才生成的.class 文件,这里 javah 后面跟的不是路径,而是 java 中的包名+类名(注意一下命令与文件路径(src/jni/LibTest.java)的关系)
1
2
| cd src/
javah jni.LibTest
|
- 生成了一个.h 文件,该文件官方说法是建议不要修改的,然后根据这个.h 文件构建一个 c/c++工程生成对应的动态库。
jni_lib_LibTest.h:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
| /* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class jni_lib_LibTest */
#ifndef _Included_jni_lib_LibTest
#define _Included_jni_lib_LibTest
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: jni_lib_LibTest
* Method: HelloWorld
* Signature: (Ljava/lang/String;)V
*/
JNIEXPORT void JNICALL Java_jni_1lib_LibTest_HelloWorld
(JNIEnv *, jclass, jstring);
#ifdef __cplusplus
}
#endif
#endif
|
- 编写对应 c/cpp 文件,jni_lib_LibTest.cpp:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
| #include "jni_lib_LibTest.h"
#include <iostream>
using namespace std;
JNIEXPORT void JNICALL Java_jni_1lib_LibTest_HelloWorld
(JNIEnv *env, jclass, jstring j_i_inStr) {
char* i_inStr = (char*)env->GetStringUTFChars(j_i_inStr, false);
if (i_inStr == NULL) {
cout << "i_inStr == NULl!" << endl;
return ;
}
cout << "OutPut in jni_lib_LibTest.cpp!" << endl;
cout << "Helloworld! i_inStr is : " << i_inStr << endl;
env->ReleaseStringUTFChars(j_i_inStr, i_inStr);
}
|
- 编译成动态库
某些系统会出现找不到 jni.h 和 jni_md.h 你需要把路径加入编译选项
视情况加入:-I $(JAVA_HOME)/include/
视情况加入:-I $(JAVA_HOME)/include/linux(jni_md.h 一般再 include 子目录下找一下就能找到了)
g++ -shared -o libjni_lib_LibTest.so jni_lib_LibTest.cpp
3 : java 加载动态库
jni_lib_LibTest.dll / libjni_lib_LibTest.so
- 把这个.dll(.so)动态库放到合适的地方,可以是系统默认库目录,也可以通过设置 LD_LIBRARY_PATH 的值实现,更多关于找不到库的问题需要视乎不同系统,不同平台,不同 ide 区分解决办法,这里不详细描述
System.loadLibrary(“jni_lib_LibTest”); //注意文件名与库名的对应规则 - 同样你也可以使用绝对路径来加载:如
System.load(“/home/Pancake/libjni_lib_LibTest.so”); - 运行 java 程序,输出如下:
1
2
| OutPut in jni_lib_LibTest.cpp!
Helloworld! i_inStr is : I'm Pancake
|
介绍两个常用的类型转换
关于 jni 的内存管理,在这里不做详细描述,提供几个关键词进行搜索:
Heap Memory/Native Memory/LocalReference/GlobalReference/Weak Global Reference
目前个人简单理解是:除了使用 JNIEnv 方法创建的,然后 return 给 java 的对象以外,其他的都需要进行内存的释放,最基础的使用是 java 层入参,如:GetStringUTFChars 把 jstring 类型转换成 char*后,务必使用 ReleaseStringUTFChars 进行释放
- string 获取:
获取传入参数 String:jstring java_string
1
2
3
4
5
6
| char* str_cpp =(char*)(env->GetStringUTFChars(java_string, false));
if(str_cpp == NULL) {
return NULL;
}
//do something
env->ReleaseStringUTFChars(java_string, str_cpp);
|
1
2
3
| char* str = "Pancake";
//do something
return env->NewStringUTF(str);
|
- byte[]获取:
获取 jbyteArray java_byte
1
2
3
4
| int byte_len= env->GetArrayLength(java_byte );
char* byte_value=(char*)(env->GetByteArrayElements(java_byte ,0));
//do something
env->ReleaseByteArrayElements(java_byte, (jbyte*)byte_value, 0);
|
1
2
3
4
5
| int byte_len = 10;
BYTE* byte_value=new BYTE[10]();
jbyteArray jarrRV = env->NewByteArray(byte_len);
env->SetByteArrayRegion(jarrRV, 0, byte_len , (jbyte*)byte_value);
return jarrRV;
|
关于 java 中 native 方法是否定义为 static 的问题
- 定义为 static 的方法不需要创建该类的实例就能调用,对应生成的.h 文件第二个参数是 jclass 类型,由于不是实例,所以估计(java 基础不足只能说估计)如果要在 c++上使用这个参数也只能调用到同是 static 的其他成员变量或者方法
- 非 static 方法需要先创建该类的实例才可以通过该实例调用,对应.h 文件是第二个参数是 jobject 类型,是调用该方法的实例对象,可以用该变量在 c++获取 java 该对象的其他成员
- 所以,如果修改了 java 工程中相关定义,记得要根据新生成的.h 文件来修改.c/.cpp 文件,这个类型一定要对上