预编译和JNI

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/GXH_APOLOGIZE/article/details/89313301

预编译

C语言执行的流程:

  • 编译:形成目标代码(.obj)
  • 连接:将目标代码与C函数库连接合并,形成最终的可执行文件
  • 执行

预编译:为编译做准备工作,完成代码文本的替换工作。

头文件只是告诉编译器有这种函数,连接器负责找到函数的实现。

define指令:

  • 定义标识
#ifdef  _cplusplus  //标识支持C++语法

也可以通过define防止文件重复引入
举个栗子:头文件A.h和B.h相互引用,

A.h:
#ifndef AH //如果没有定义AH
#define AH
#include <B.h>

void printA();

#endif


B.h
#ifndef BH
#define BH
#include <A.h>

void printB();

#endif

其实新的编译器可以使用另一种方法,也能保证头文件执行一次
pragma一般只用在头文件,整个头文件只包含一次

A.h
#pragma once
#include <B.h>
void printA();

B.h
#pragma once
#include <A.h>
void printB();

  • 定义常数
#define MAX 100

这个和全局变量是不一样,这个没有类型,只是个替换。
这么写便于阅读和修改。

  • 定义宏函数
#define jni(NAME) dn_com_jni_##NAME();
//日志输出
//#define LOG(FORMAT,...) printf(FORMAT,__VA_ARGS__);
//#define LOG_I(FORMAT,...) printf("INFO:"); printf(FORMAT,__VA_ARGS__);

//升级版
#define LOG(LEVEL,FORMAT,...) printf(LEVEL);printf(##FORMAT,__VA_ARGS__);
#define LOG_I(FORMAT,...) LOG("INFO:",FORMAT,__VA_ARGS__);
#define LOG_E(FORMAT,...) LOG("ERROR:",FORMAT,__VA_ARGS__);

void dn_com_jni_write() {
	printf("dn_com_jni_write");
}

void main() 
{
	jni(write);
//	LOG("%s\n","haha");
//	LOG("%s,%s,%s\n","hh","haha","dudu");
//	LOG_I("%s\n","en");

	system("pause");
}

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方法
//新建Java工程,新建JniTest类

package cn.gxh;

public class JniTest {
	public native String getStringFromC();

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		// TODO Auto-generated method stub

	}

}

  • javah命令,生成.h头文件
进入Java工程src目录下,javah cn.gxh.JniTest
此时会生成头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class cn_gxh_JniTest */

#ifndef _Included_cn_gxh_JniTest
#define _Included_cn_gxh_JniTest
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     cn_gxh_JniTest
 * Method:    getStringFromC
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_cn_gxh_JniTest_getStringFromC
  (JNIEnv *, jclass);

/*
 * Class:     cn_gxh_JniTest
 * Method:    getStringFromC2
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_cn_gxh_JniTest_getStringFromC2
  (JNIEnv *, jobject);

#ifdef __cplusplus
}
#endif
#endif
  • 复制.h头文件到cpp工程
  • 复制jdk下面的jni.h和jni_md.h文件到cpp工程中
确保这几个头文件复制到了项目里
如果无法打开这几个头文件

项目->属性->C/C++->常规->附加包含目录->编辑中,把此路径添加上
  • 实现.h头文件中声明的函数
#include <stdio.h>
#include "cn_gxh_JniTest.h"

//函数实现
JNIEXPORT jstring JNICALL Java_cn_gxh_JniTest_getStringFromC(JNIEnv *env, jclass jcls)
{
	return (*env)->NewStringUTF(env,"C String");
}


JNIEXPORT jstring JNICALL Java_cn_gxh_JniTest_getStringFromC2
(JNIEnv *env, jobject jobj) 
{
	return (*env)->NewStringUTF(env, "C String 2");
}
  • 生成dll文件
项目->属性->配置管理器->活动解决方案平台
常规->配置类型->动态库

//生成dll文件
生成->生成解决方案

  • 配置dll文件所在目录到环境变量 或者放到工程根目录下

  • 重启Eclipse

package cn.gxh;

public class JniTest {
	public native static String getStringFromC();
	public native String getStringFromC2();

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		System.out.print(getStringFromC());
		JniTest jniTest=new JniTest();
		System.out.print(jniTest.getStringFromC2());

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

}

C访问Java

  • 访问非静态属性
//访问java的非静态属性    public String key="liyifeng";
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_accessField
(JNIEnv *env, jobject jobj)
{
	//得到jclass
	jclass cls=(*env)->GetObjectClass(env,jobj);
	//参数三:属性名 参数四:签名
	jfieldID fid=(*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
	//获取属性key的值
	jstring jstr=(*env)->GetObjectField(env,jobj,fid);
	
	//jstring转c的字符串
	char* c_str=(*env)->GetStringUTFChars(env,jstr,JNI_FALSE);
	char text[20] = "who is ";
	strcat(text,c_str);

	//c字符串转jstring
	jstring new_str=(*env)->NewStringUTF(env,text);

	//修改key
	(*env)->SetObjectField(env,jobj,fid, new_str);
}
  • 访问静态属性
//访问静态属性  public static int count=7;

JNIEXPORT void JNICALL Java_cn_gxh_JniTest_accessStaticField
(JNIEnv *env, jobject jobj)
{
	//得到jclass
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetStaticFieldID(env, cls, "count", "I");
	jint j_count = (*env)->GetStaticIntField(env, cls, fid);
	j_count++;
	(*env)->SetStaticIntField(env,cls,fid,j_count);
}
  • 访问非静态方法
public int genRandomInt(int max){
		return new Random().nextInt(max);
}
	
public native void accessMethod();


//访问非静态方法
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_accessMethod
(JNIEnv *env, jobject jobj)
{
	//得到jclass
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jmethodID mid=(*env)->GetMethodID(env,cls,"genRandomInt","(I)I");
	//调用
	jint j_random=(*env)->CallIntMethod(env,jobj,mid,200);
	printf("random:%ld",j_random);

}

  • 访问静态方法
public static String getUUID(){
	return UUID.randomUUID().toString();
}

public native void accessStaticMethod();


//访问静态方法
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_accessStaticMethod
(JNIEnv *env, jobject jobj)
{
	//得到jclass
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jmethodID mid = (*env)->GetStaticMethodID(env, cls, "getUUID", "()Ljava/lang/String;");
	//调用
	jstring j_str = (*env)->CallStaticObjectMethod(env, cls, mid);
	char* c_str = (*env)->GetStringUTFChars(env, j_str, JNI_FALSE);
	printf("uuid:%s", c_str);
}
  • 访问构造方法
这里我们访问java中的Date类,实例化它的对象,调用它的getTime()方法。

//访问构造方法
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_accessConstructor
(JNIEnv *env, jobject jobj)
{
	//得到jclass
	jclass cls = (*env)->FindClass(env, "java/util/Date");
	jmethodID constructor_mid=(*env)->GetMethodID(env,cls,"<init>","()V");
	//实例化对象
	jobject date_obj=(*env)->NewObject(env,cls,constructor_mid);
	//调用getTime方法
	jmethodID mid=(*env)->GetMethodID(env,cls,"getTime","()J");
	//调用 这个方法返回值xxx 就CallxxxMethod
	jlong time=(*env)->CallLongMethod(env,date_obj,mid);
	printf("\ntime:%ld",time);
}
注意事项
  • 我们为了在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;
javap -s -p 完整类名(比如: java.util.Date)
  • C代码中文返回乱码
char* c_str = "李易峰";
//c-->jstring
return (*env)->NewStringUTF(env,c_str);

上面这种写法会乱码。正确写法如下:

jclass cls=(*env)->FindClass(env,"java/lang/String");
jmethodID constructor_mid=(*env)->GetMethodID(env,cls,"<init>","([BLjava/lang/String;)V");

char* c_str = "李易峰";
//char c_str[] = "李易峰";
jbyteArray byteArray=(*env)->NewByteArray(env,strlen(c_str));
//byte数组赋值
(*env)->SetByteArrayRegion(env,byteArray,0,strlen(c_str),c_str);

jstring charsetName=(*env)->NewStringUTF(env,"GB2312");
//实例化对象
return (*env)->NewObject(env, cls, constructor_mid,byteArray,charsetName);

数据类型

基本类型
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
public 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
public native int[] getArray(int len);


JNIEXPORT jintArray JNICALL Java_cn_gxh_JniTest_getArray
(JNIEnv *env, jobject jobj,jint len)
{	//创建一个指定大小的数组
	jintArray jint_arr=(*env)->NewIntArray(env,len);
	jint* elements=(*env)->GetIntArrayElements(env,jint_arr,NULL);
	int i = 0;
	for (; i < len;i++) {
		elements[i] = i;
	}

	(*env)->ReleaseIntArrayElements(env, jint_arr, elements, 0);
	return jint_arr;
}

JNI引用

  • 局部引用
局部引用需要手动释放对象的场景:
1.访问一个很大的java对象,使用完后,还要进行复杂的耗时操作
2.创建了大量的局部引用,占用了太多的内存,而且后面不再使用它了

要通过DeleteLocalRef手动释放对象

JNIEXPORT void JNICALL Java_cn_gxh_JniTest_localRef
(JNIEnv *env, jobject jobj)
{
	int i = 0;
	for (;i<5; i++) {
		jclass cls= (*env)->FindClass(env, "java/util/Date");
		jmethodID constructor_mid = (*env)->GetMethodID(env, cls, "<init>", "()V");
		//实例化对象
		jobject date_obj = (*env)->NewObject(env, cls, constructor_mid);
		//代码省略了...

		//通知垃圾回收器回收这些对象
		(*env)->DeleteLocalRef(env,date_obj);

		//下面的代码不再使用date_obj
	}
}
  • 全局引用
全局引用好处就是多个方法可以共享这个变量。

//全局引用
jstring global_str;

JNIEXPORT void JNICALL Java_cn_gxh_JniTest_deleteGlobalRef
(JNIEnv *env, jobject jobj)
{
	(*env)->DeleteGlobalRef(env,global_str);
}

JNIEXPORT void JNICALL Java_cn_gxh_JniTest_createGlobalRef
(JNIEnv *env, jobject jobj)
{
	jstring obj=(*env)->NewStringUTF(env,"life is but a span ");
	global_str=(*env)->NewGlobalRef(env,obj);
}

异常

//异常
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_exception
(JNIEnv *env, jobject jobj)
{
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");

	printf("执行了...");
}

没有key2这个属性,导致了运行后Java层异常,
Exception in thread "main" java.lang.NoSuchFieldError: key2
但是在Java中,try catch Exception是捕捉不到的。JNI抛出的是Throwable异常,只能捕捉Throwable异常。

想在JNI处理了异常怎么办呢?
检测异常-->清空异常
//异常
JNIEXPORT void JNICALL Java_cn_gxh_JniTest_exception
(JNIEnv *env, jobject jobj)
{
	jclass cls = (*env)->GetObjectClass(env, jobj);
	jfieldID fid = (*env)->GetFieldID(env, cls, "key2", "Ljava/lang/String;");

	printf("执行了...");

	//检测是否发生Java异常
	jthrowable exception=(*env)->ExceptionOccurred(env);
	if (exception!=NULL) {
		//清空异常信息
		(*env)->ExceptionClear(env);
		//发生异常后的其他业务逻辑代码
	}
}
这样Java层就没有异常了。

JNI层也可以人为抛异常给Java层,这样Java可以try catch Exception。

//人为抛异常给Java层处理
jclass e_cls=(*env)->FindClass(env,"java/lang/IllegalArgumentException");
(*env)->ThrowNew(env,e_cls,"key2 not exist");

缓存策略

如果JNI方法里有局部静态(static)变量,在方法初始化的时候,这个变量也被初始化(不管这个方法调用多少次,这个变量只会初始化一次),方法结束后,这个变量依然会存储在内存中,直到整个程序结束。也就是它的作用域就是这个方法,但是生命周期很长。

如果我们需要一些全局的静态变量,一般可以在一个方法里全部初始化完成。而这个方法在我们加载完动态库后马上调用。

文章同步发布在其它博客

猜你喜欢

转载自blog.csdn.net/GXH_APOLOGIZE/article/details/89313301