JNIEnv介绍

参考:
http://wiki.jikexueyuan.com/project/deep-android-v1/jni.html
JNIEnv是一个和线程相关的,代表JNI环境的结构体,图2-3展示了JNIEnv的内部结构:

从上图可知,JNIEnv实际上就是提供了一些JNI系统函数。通过这些函数可以做到:
- 调用Java的函数。
- 操作jobject对象等很多事情。

通过JNIEnv 操作jobject

除了Java中基本数据类型的数组、Class、String和Throwable外,其余所有Java对象的数据类型在JNI中都用jobject表示。
如果对象类型都用jobject表示,就好比是Native层的void*类型一样,对码农来说,是完全透明的。既然是透明的,那该如何使用和操作它们呢?
前面提到过一个问题,即Java的引用类型除了少数几个外,最终在JNI层都用jobject来表示对象的数据类型,那么该如何操作这个jobject呢?
从另外一个角度来解释这个问题。一个Java对象是由什么组成的?当然是它的成员变量和成员函数了。那么,操作jobject的本质就应当是操作这些对象的成员变量和成员函数。所以应先来看与成员变量及成员函数有关的内容。

(1)jfieldID 和jmethodID的介绍

我们知道,成员变量和成员函数是由类定义的,它是类的属性,所以在JNI规则中,用jfieldID 和jmethodID 来表示Java类的成员变量和成员函数,它们通过JNIEnv的下面两个函数可以得到:

jfieldID GetFieldID(jclass clazz,const char*name, const char *sig);

jmethodID GetMethodID(jclass clazz, const char*name,const char *sig);

(2)使用jfieldID和jmethodID

jstring介绍

Java中的String也是引用类型,不过由于它的使用非常频繁,所以在JNI规范中单独创建了一个jstring类型来表示Java中的String类型。虽然jstring是一种独立的数据类型,但是它并没有提供成员函数供操作。相比而言,C++中的string类就有自己的成员函数了。那么该怎么操作jstring呢?还是得依靠JNIEnv提供的帮助。这里看几个有关jstring的函数:
- 调用JNIEnv的NewString(JNIEnv *env, const jchar*unicodeChars,jsize len),可以从Native的字符串得到一个jstring对象。其实,可以把一个jstring对象看成是Java中String对象在JNI层的代表,也就是说,jstring就是一个Java String。但由于Java String存储的是Unicode字符串,所以NewString函数的参数也必须是Unicode字符串。
- 调用JNIEnv的NewStringUTF()将根据Native的一个UTF-8字符串得到一个jstring对象。在实际工作中,这个函数用得最多。
- 上面两个函数将本地字符串转换成了Java的String对象,JNIEnv还提供了GetStringChars()GetStringUTFChars()函数,它们可以将Java String对象转换成本地字符串。其中GetStringChars()得到一个Unicode字符串,而GetStringUTFChars()得到一个UTF-8字符串。
- 另外,如果在代码中调用了上面几个函数,在做完相关工作后,就都需要调用ReleaseStringChars()ReleaseStringUTFChars()函数对应地释放资源,否则会导致JVM内存泄露。这一点和jstring的内部实现有关系,读者写代码时务必注意这个问题。

附录:

注册JNI函数

静态注册

就是用javah生成.h头文件,然后里面有这样的函数

#include <jni.h>  //必须包含这个头文件,否则编译通不过
//processFile的JNI函数
// JNICALL 在 Windows 中的值为__stdcall,用于约束函数入栈顺序和堆栈清理的规则。
JNIEXPORT void JNICALL Java_android_media_MediaScanner_processFile (JNIEnv *, jobject, jstring,jstring, jobject);

其中
Windows 下jni_md.h头文件内容:

#ifndef _JAVASOFT_JNI_MD_H_  
#define _JAVASOFT_JNI_MD_H_  

#define JNIEXPORT __declspec(dllexport)  
#define JNIIMPORT __declspec(dllimport)  
#define JNICALL __stdcall  

typedef long jint;  
typedef __int64 jlong;  
typedef signed char jbyte;  

#endif   

可见JNICALL在 Windows 中的值为__stdcall,用于约束函数入栈顺序和堆栈清理的规则。
Linux 下jni_md.h头文件内容:

#ifndef _JAVASOFT_JNI_MD_H_  
#define _JAVASOFT_JNI_MD_H_  

#define JNIEXPORT  
#define JNIIMPORT  
#define JNICALL  

typedef int jint;  
#ifdef _LP64 /* 64-bit Solaris */  
typedef long jlong;  
#else  
typedef long long jlong;  
#endif  

typedef signed char jbyte;  

#endif  

可见

从 Linux 下的jni_md.h头文件可以看出来,JNIEXPORT 和 JNICALL 是一个空定义,所以在 Linux 下 JNI 函数声明可以省略这两个宏。

参考:http://wiki.jikexueyuan.com/project/jni-ndk-developer-guide/serchnative.html

动态注册

既然Java native函数数和JNI函数是一一对应的,那么是不是会有一个结构来保存这种关联关系呢?答案是肯定的。在JNI技术中,用来记录这种一一对应关系的,是一个叫JNINativeMethod的结构,其定义如下:

typedef struct {
   //Java中native函数的名字,不用携带包的路径。例如“native_init“。
    const char* name;    
    //Java函数的签名信息,用字符串表示,是参数类型和返回值类型的组合。
    const char* signature;
    void*       fnPtr;  //JNI层对应函数的函数指针,注意它是void*类型。

} JNINativeMethod;

当Java层通过System.loadLibrary()加载完JNI动态库后,紧接着会查找该库中一个叫JNI_OnLoad()的函数,如果有,就调用它,而动态注册的工作就是在这里完成的。
所以,如果想使用动态注册方法,就必须要实现JNI_OnLoad()函数,只有在这个函数中,才有机会完成动态注册的工作。静态注册则没有这个要求,可我建议读者也实现这个JNI_OnLoad()函数,因为有一些初始化工作是可以在这里做的。

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值