C语言执行的流程:
预编译:为编译做准备工作,完成代码文本的替换工作。
头文件只是告诉编译器有这种函数,连接器负责找到函数的实现。
#ifdef _cplusplus //标识支持C++语法
也可以通过define防止文件重复引入
举个栗子:头文件A.h和B.h相互引用,
A.h:
#ifndef AH //如果没有定义AH
#define AH
#include
void printA();
#endif
B.h
#ifndef BH
#define BH
#include
void printB();
#endif
其实新的编译器可以使用另一种方法,也能保证头文件执行一次
pragma一般只用在头文件,整个头文件只包含一次
A.h
#pragma once
#include
void printA();
B.h
#pragma once
#include
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 java native interface java本地开发接口。JNI是一个协议,通过这个协议,可以实现java代码调用外部的c/c++代码,外部的c/c++代码也可以调用java代码。
为什么用JNI:
每个native函数都至少有两个参数。
参数一:
JNIEnv* env
JNIEnv在C中其实是结构体指针,代表java运行环境,调用java中的代码
env在C中其实就是二级指针
JNIEnv在C++ 中是一个结构体别名,那么env在C++中是一个结构体指针。
参数二:
当native方法为静态方法时,jclass代表native方法所属类的class对象。
当native方法为非静态方法时,jobject代表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
}
}
进入Java工程src目录下,javah cn.gxh.JniTest
此时会生成头文件
/* DO NOT EDIT THIS FILE - it is machine generated */
#include
/* 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
确保这几个头文件复制到了项目里
如果无法打开这几个头文件
项目->属性->C/C++->常规->附加包含目录->编辑中,把此路径添加上
#include
#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文件所在目录到环境变量 或者放到工程根目录下
重启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");
}
}
//访问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,"","()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类型 | 签名 |
---|---|
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)
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,"","([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 |
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
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);
}
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;
}
局部引用需要手动释放对象的场景:
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, "", "()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)变量,在方法初始化的时候,这个变量也被初始化(不管这个方法调用多少次,这个变量只会初始化一次),方法结束后,这个变量依然会存储在内存中,直到整个程序结束。也就是它的作用域就是这个方法,但是生命周期很长。
如果我们需要一些全局的静态变量,一般可以在一个方法里全部初始化完成。而这个方法在我们加载完动态库后马上调用。
文章同步发布在其它博客