jni基础

来源:互联网 发布:python mat函数 编辑:程序博客网 时间:2024/06/07 06:43
JNI -> Java Native Interface
什么情况下使用:
1、Java api不能满足我们程序的需要的时候
2、算法计算,图像渲染,效率要求比较高时
3、需要访问一些已有的本地库


NDK:工具的集合,帮助开发者快速打包c/c++为动态库。


JNI开发步骤(windows VS中):
1、编写native方法
2、javah 命令生成.h头文件
3、赋值.h头文件到cpp工程中
4、赋值jni.h和jni_md.h
5、实现.h头文件中声明的函数
6、生成.dll动态库
7、在java中加载.dll动态库
8、触发native方法。


静态库和动态库 都是函数库
静态库(.a)是代码编译的时候就打包到程序中。
动态库(.dll/.so)代码编译的时候不会打包到程序中,只有用到时才会加载动态库。




c语言中:
JNIEnv 结构体指针的别名
env 是二级指针


c++中:
JNIEnv 结构体别名
env 是一级指针
因为为了在c和c++中使用相同的函数,所以才这样设计的。


本地方法映射到c语言中:
1、静态方法:
JNIEXPORT 返回类型 JNICALL 包名_包名_类名_方法名(JNIEnv *env, jclass jclz);
2、非静态方法:
JNIEXPORT 返回类型 JNICALL 包名_包名_类名_方法名(JNIEnv *env, jobject jobj);


每个native函数,都至少有2个参数(JNIEnv *, jclass/jobject)
jclass: native 静态方法
jobject: native 非静态方法


JNI基本数据类型:
java ----- JNI ------ C
boolean - jboolean
byte - jbyte
char - jchar
short - jchort
int - jint
long - jlong
float - jfloag
double - jdouble


JNI引用类型:
String jstring
Object jobject


基本数据类型数组:
形式:type[] jTypeArray
byte[] jByteArray


引用类型数组:
Object[](String[]) jObjectArray;


1)、
native访问java非静态属性,并设置新的值
public String name = "Tom";


//得到jclass
jclass jclz = (*env)->GetObjectClass(env, jobj);


//得到fieldId (参数 jclass, 属性名 类型签名)
jfieldID fid = (*env)->GetFieldID(env, jclz, "name", "Ljava/lang/String;");


//得到field的值 GetXXXField XXX是类型
jstring jstr = (*env)->GetObjectField(env, jobj, fid);


//将jstring转换成 char*
char *c_str = (*env)->GetStringUTFChars(env, jstr, NULL);


char text[10] = "hello";
//生成新的字符串
strcat(text, c_str);


//c中字符串转换成jstring
jstring new_str = (*env)->NewStringURF(env, text);


//设置java中属性的值 SetXXXField
(*env)->SetObjectField(env, jobj, fid, new_str);


//释放 char * c_str 因为是申请的内存空间
(*env)->ReleaseStringChars(env, new_str, c_str);


2)、
native访问java静态属性
public static int count = 10;


//得到jclass
jclass jclz = (*env)->GetObjectClass(env, jobj);


//得到fieldId
jfieldID fid = (*env)->GetStaticFieldID(env, jclz, "count", "I");


jint count = (*env)->GetStaticIntField(env, jclz, fid);


count++;
//SetStaticXXXField
(*env)->SetStaticIntField(env, jclz, fid, count);


3)、
native 访问java非静态方法


public int getCount(int num){
    return num + 10;
}


//得到jclass
jclass jclz = (*env)->GetObjectClass(env, jobj);


//得到jmethodID
//class:javap -s -p 类名 得到签名
jmethodID mid = (*env)->GetMethodID(env, jclz, "getCount", "(I)I");


//调用
jint count = (*env)->CallIntMethod(env, jobj, mid, 100);


4)、
native 访问java静态方法


public static String getName()
{
    return "Tom";
}


//得到jclass 通过jobject搜索class,如果找到了,就将class转换成jclass,然后返回
jclass jclz = (*env)->GetObjectClass(env, jobj);//根据对象查找class


//jmethodID
jmethodID jmid = (*env)->GetStaticMethodID(env, jclz, "getName", "()Ljava/lang/String;");


//调用
(*env)->CallStaticObjectMethod(env, jclz, jmid);


5)、
访问java构造方法
//得到jclass
jclass jclz = (*env)->FindClass(env, "java/util/Date");//根据类名(路径)查找class


//得到jmethodID 所有构造方法都是 "<init>"
jmedhodID mid = (*env)->GetMethodID(env, jclz, "<init>", "()V");


//调用NewObject实例化Date对象,返回一个jobject
jobject obj = (*env)->NewObject(env, jclz, mid);


//得到对象的方法
jmethodID jmid = (*env)->GetMethodId(env, jclz, "getTime", "()J");


//调用jmid方法
jlong time = (*env)->CallMethodID(env, jclz, jmid);


十二、
字符串
java UTF-16
jni  UTF-8 unicode
c/c++
GetStringChars wchar_t * UTF-16
GetStringUTFChars char* UTF-8


第一种:
int length = (*env)->GetStringLength(env, in);
const jchar* jcstr = (*env)->GetStringChars(env, in, NULL);//宽字符
if(jcstr != NULL)
{
    char *rtn = (char*)malloc(sizeof(char) * 2 * length + 1);
    //windows宽字符转换
    memset(rtn, 0, sizeof(char) * 2 * length + 1);
    int size = WideCharToMultiBype(CP_ACP, 0, (LPCWSTR)jcstr, length, rtn, sizeof(char) * 2 * length + 1, NULL, NULL);
    if(size > 0)
    {
         printf("%s", rtn);   
    }
}
(*env)->ReleaseStringChars(env, in, jcstr);//JVM使用,通知JVM jcstr所指向的空间可以释放了
第二种:
利用java里面的String方法
char *str = "中国";
jclass jclz = (*env)->FindClass(env, "java/lang/String");
jmethodID jmid = (*env)->GetMethodID(env, jclz, "<init>", "([BLjava/lang/String;)V");


//jstring -> jbyteArray 生成长度为strlen(str)的数组
jbyteArray bytes = (*env)->NewByteArray(env, strlen(str));
//将char *赋值到bytes中, 此时bytes里面就有str的字符数据了
(*env)->SetByteArrayRegion(env, bytes, 0, strlen(str), str);
//设置编码方式
jstring charsetName = (*env)->NewStringUTF(env, "GB2312");


return (*env)->NewObject(env, jclz, jmid, bytes, charsetName);




十三、
数组


基本类型的数组
引用类型的数组


//比较函数
int compare(jint *a, jint *b)
{
    return *a - *b;
}


1)、基本类型的数组:传入int[] jintArray
//jintArray arr -> jint *
jint *elemts = (*env)->GetIntArrayElements(env, arr, NULL);
if(elemts == NULL)
    return;
//数组长度
int length = (*env)->GetArrayLength(env, arr);
//排序
qsort(elemts, len, sizeof(jint), compare);
//释放可能的内存
//将JNI修改的数据重新写回原来的内存里面
(*env)->ReleaseIntArrayElements(env, arr, elemts, JNI_COMMIT);


2)、引用类型的数组 返回String[]
//创建jobjectArray
jobjectArray result;
jclass jclz = (*env)->FindClass(env, "java/lang/String");
if(jclz == NULL)
{
    return NULL;
}
result = (*env)->NewObjectArray(env, size, jclz, jobj);
if(result == NULL)
{
    return NULL;
}
int i = 0;
for(i = 0; i < size; i++)
{
    //得到c 字符串
    char *c_str = (char *)malloc(256);
    memset(c_str, 0, 256);
    //将int转换成为char
    sprintf(c_str, "helo %d", i); 
    //c -> jstring
    jstring str = (*env)->NewStringUTF(env, c_str);
    if(str == NULL)
    {
        return NULL;
    }
    //将jstring赋值给数组
    (*env)->SetObjectArrayElement(env, result, i, str);
    free(c_str);
    c_str = NULL;
    (*env)->DeleteGlobalRef(env, str);
}
return result;


十四、
引用
局部引用
全局引用
弱全局引用


1)、局部引用(512个)
//定义样式多:FindClass, NewObject, GetObjectClass, NewCharArray...NewLocalRef()
//释放方式:1、方法调用玩JVM自动释放 2、DeleteLocalRef
//不能在多线程里面使用
int i = 0;
for(i = 0; i < 5; i++)
{
    jclass jclz = (*env)->FindClass(env, "java/util/Date");
    jmethodID jmid = (*env)->GetMethodID(env, jclz, "<init>", "()V");
    //创建一个date类型的局部引用
    jobject obj = (*env)->NewObject(env, jclz, jmid);
    //使用引用
    。。。
    //释放引用
    (*env)->DeleteLocalRef(env, obj);
    (*env)->DeleteLocalRef(env, jclz);
}


2)、全局引用
//跨方法、跨线程
//NewGlobalRef 是创建全局引用的唯一方法
jstring global_str;
方法一中:
jobject obj = (*env)->NewStringUTF(env, "JNI is Good");
global_str = (*env)->NewGlobalRef(env, obj);
方法二中:
得到全局引用
retur global_str;
方法三中:
删除全局引用:
(*env)->DeleteGlobalRef(env, global_str);


3)、弱全局引用
//它不会阻止GC 可跨线程,跨方法使用
jclass g_weak_cls;
方法一:
jclass clz = (*env)->FindClass(env, "java/lang/String");
g_weak_cls = (*env)->NewWeakGlobalRef(env, clz);


十四、
JNI异常处理
//检查是否发生异常
jthrowable ex = (*env)->ExcptionOccurred(env);
if(ex != NULL)
{
      jclass newException;
      printf("发生异常");
      //情况Jni产生的异常,java层后面的代码可以继续执行
      (*env)->ExceptionClear(env);
      or
      //创建一个异常
      newException = (*env)->FindClass(env, "java/lang/IllegalArgumentException");
      if(newException == NULL)
      {
   printf("exception");
   return NULL;
      }
      (*env)->ThrowNew(env, newException, "Throw a exception");
}


十五、
缓存
//局部静态变量缓存
jclass cls = (*env)->GetObjectClass(env, jboj);
//存储静态区里面,初始化一次后,后面直接可以用,作用范围就在这个方法里面,局部静态区
static jfieldID fid;
if(fid == NULL)
{
    fid = (*env)->GetFieldID(env, cls, "key", "Ljava/lang/String;");
    printf("jfieldID");
}//结果:java层 循环调用此方法的话,fid只生成一次。如果不是static,每循环一次就好打印一次。




2)、
全局静态变量
//作用范围就在定义变量之后,前面不能用
static jfieldID fid;
native 方法()
{
    fid = (*env)->GetFieldID(env, "key", "Ljava/lang/String;");
    if(fid != NULL)
    {
    
    }
}


3)、
//缓存策略和弱引用联合使用带来的问题


1:定义一个静态局部变量,并赋值(局部引用)
2:在另一个方法里面使用含有局部静态变量的native方法,
3:调用完方法后,局部引用会被释放,但是此时静态局部变量还有值,其值是被释放的局部引用的地址,

4:在调用这个含有局部静态变量的方法时,就会报异常(此时静态变量的值指向的那个引用已经被释放了,就会成为野指针)