预编译
C语言执行的流程:
- 编译:形成目标代码(.obj)
- 连接:将目标代码与C函数库连接合并,形成最终的可执行文件
- 执行
预编译:为编译做准备工作,完成代码文本的替换工作。
头文件只是告诉编译器有这种函数,连接器负责找到函数的实现。
define指令:
- 定义标识
1 | #ifdef _cplusplus //标识支持C++语法 |
- 定义常数
1 | #define MAX 100 |
这个和全局变量是不一样,这个没有类型,只是个替换。
这么写便于阅读和修改。
- 定义宏函数
1 | #define jni(NAME) dn_com_jni_##NAME(); |
JNI
JNI简介
JNI java native interface java本地开发接口。JNI是一个协议,通过这个协议,可以实现java代码调用外部的c/c++代码,外部的c/c++代码也可以调用java代码。
为什么用JNI:
- 扩展了java虚拟机的能力
- 本地代码效率更高
- 复用c代码(人脸识别,ffmpeg)
- c语言反编译比java难
每个native函数都至少有两个参数。
参数一:
JNIEnv* env
JNIEnv在C中其实是结构体指针,代表java运行环境,调用java中的代码
env在C中其实就是二级指针
JNIEnv在C++ 中是一个结构体别名,那么env在C++中是一个结构体指针。
参数二:
当native方法为静态方法时,jclass代表native方法所属类的class对象。
当native方法为非静态方法时,jobject代表native方法所属的对象。
Java调C
- 编写native方法
1 | //新建Java工程,新建JniTest类 |
- javah命令,生成.h头文件
1 | 进入Java工程src目录下,javah cn.gxh.JniTest |
1 | /* DO NOT EDIT THIS FILE - it is machine generated */ |
- 复制.h头文件到cpp工程
- 复制jdk下面的jni.h和jni_md.h文件到cpp工程中
1 | 确保这几个头文件复制到了项目里 |
- 实现.h头文件中声明的函数
1 | #include <stdio.h> |
- 生成dll文件
1 | 项目->属性->配置管理器->活动解决方案平台 |
配置dll文件所在目录到环境变量 或者放到工程根目录下
重启Eclipse
1 | package cn.gxh; |
C访问Java
- 访问非静态属性
1 | //访问java的非静态属性 public String key="liyifeng"; |
- 访问静态属性
1 | //访问静态属性 public static int count=7; |
- 访问非静态方法
1 | public int genRandomInt(int max){ |
- 访问静态方法
1 | public static String getUUID(){ |
- 访问构造方法
1 | 这里我们访问java中的Date类,实例化它的对象,调用它的getTime()方法。 |
注意事项
- 我们为了在java中触发,每个例子都写了native方法。实际中不一定需要。
- C访问java这几个例子中静态非静态指的是要访问的属性、方法。并不是native方法是不是静态的。
- 这几个例子的native方法都是非静态的。由参数(JNIEnv *env, jobject jobj)可以看出。
- 假如native方法是静态的,参数(JNIEnv *env, jclass cls)
- 属性的签名可以参考下表(也可以用命令)。方法的签名得使用命令了。
Java类型 | 签名 |
---|---|
boolean | Z |
byte | B |
char | C |
short | S |
int | I |
long | J |
float | F |
double | D |
void | V |
Array | |
Object | L开头,然后以/分隔它的完整类名,最后加分号。比如String的签名为Ljava/lang/String; |
1 | javap -s -p 完整类名(比如: java.util.Date) |
- C代码中文返回乱码
1 | char* c_str = "李易峰"; |
数据类型
基本类型
Java类型 | Jni类型 |
---|---|
boolean | jboolean |
byte | jbyte |
char | jchar |
short | jshort |
int | jint |
long | jlong |
float | jfloat |
double | jdouble |
void | void |
引用类型
Java类型 | Jni类型 |
---|---|
String | jstring |
Object | jobject |
基本类型的数组(byte[]) | jByteArray |
…上一条类推 | …上一条类推 |
对象数组(Object[]) | jobjectArray |
数组处理
传数组给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
31
32
33
34public native void handleArray(int[] array);
//java调用:
int[] array={8,34,12,99,87};
jniTest.handleArray(array);
for(int i:array){
System.out.print("\n :"+i);
}
int compare(int* a, int* b)
{
return (*a) - (*b);
}
//数组处理
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_handleArray
(JNIEnv *env, jobject jobj, jintArray array)
{
//jintArray-->jint指针-->c int数组
jint* elements=(*env)->GetIntArrayElements(env,array,NULL);
//数组的长度
jsize size=(*env)->GetArrayLength(env,array);
//#include <stdlib.h>
qsort(elements,size,sizeof(jint), compare);
//同步 java中的数组才会改变
//参数四:0 Java数组更新,并且释放c/c++数组
//1 Java数组不更新,释放c/c++数组
//2 Java数组更新,c/c++数组等方法执行完才释放
(*env)->ReleaseIntArrayElements(env,array,elements,0);
}返回数组给Java
1 | public native int[] getArray(int len); |
JNI引用
- 局部引用
1 | 局部引用需要手动释放对象的场景: |
- 全局引用
1 | 全局引用好处就是多个方法可以共享这个变量。 |
异常
1 | //异常 |
缓存策略
如果JNI方法里有局部静态(static)变量,在方法初始化的时候,这个变量也被初始化(不管这个方法调用多少次,这个变量只会初始化一次),方法结束后,这个变量依然会存储在内存中,直到整个程序结束。也就是它的作用域就是这个方法,但是生命周期很长。
如果我们需要一些全局的静态变量,一般可以在一个方法里全部初始化完成。而这个方法在我们加载完动态库后马上调用。