【混合编程jni 】第九篇之Jni总结
动态库的加载
可以使用标准 System.loadLibrary 从共享库加载原生代码。
关于参数传递 int、char 等这样的基本数据类型,在本地代码和JVM之间进行复制传递,
而对象是引用传递的。每一个引用都包含一个指向JVM中相应的对象的指针,
但本地代码不能直接使用这个指针,必须通过引用来间接使用。
局部引用和全局引用
传递给原生方法的每个参数,以及 JNI 函数返回的几乎每个对象都属于“局部引用”。
这意味着,局部引用在当前线程中的当前原生方法运行期间有效。
在原生方法返回后,即使对象本身继续存在,该引用也无效。
这适用于 jobject 的所有子类,包括 jclass、jstring 和 jarray。(启用扩展的 JNI 检查时,运行时会针对大部分引用误用问题向您发出警告。)
获取非局部引用的唯一方法是通过 NewGlobalRef 和 NewWeakGlobalRef 函数。
如果您希望长时间保留某个引用,则必须使用“全局”引用。
NewGlobalRef 函数将局部引用作为参数,然后返回全局引用。
在调用 DeleteGlobalRef 之前,全局引用保证有效。
一个对象从JVM传递给本地方法时,就把控制权移交了过去,
JVM会为每一个对象的传递创建一条记录,一条记录就是一个本地代码中的引用和JVM中的对象的一个映射。
记录中的对象不会被GC回收。所有传递到本地代码中的对象和从JNI函数返回的对象都被自动地添加到映射表中。
当本地方法返回时,VM会删除这些映射,允许GC回收记录中的数据。
JavaVM 和 JNIEnv
两者本质上都是指向函数表的二级指针。
(在 C++ 版本中,它们是一些类,这些类具有指向函数表的指针,并具有每个通过该函数表间接调用的 JNI 函数的成员函数。)
JavaVM 提供“调用接口”函数,您可以利用此类来函数创建和销毁 JavaVM。理论上,每个进程可以有多个 JavaVM
JNIEnv 提供了大部分 JNI 函数。您的原生函数都会收到 JNIEnv 作为第一个参数。
JNIEnv 将用于线程本地存储。因此无法在线程之间共享 JNIEnv。
如果一段代码无法通过其他方法获取自己的 JNIEnv,您应该共享相应 JavaVM,然后使用 GetEnv 发现线程的 JNIEnv。
jclass、jmethodID 和 jfieldID
如果要通过原生代码访问对象的字段,可以按下面的顺序操作:
使用 FindClass 获取类的类对象引用 使用 GetFieldID 获取字段的字段 ID 使用适当函数获取字段的内容,例如 GetIntField 同样,如需调用方法,
首先要获取类对象引用,
然后获取方法 ID。方法 ID 通常只是指向内部运行时数据结构的指针。
建议查找一次这些值并将结果缓存在原生代码中。由于每个进程只能包含一个 JavaVM,因此将这些数据存储在静态本地结构中是一种合理的做法。
线程
线程通常从受管理代码启动(使用 Thread.start()),但也可以在其他位置创建,然后附加到 JavaVM。
例如,可以使用 AttachCurrentThread() 或 AttachCurrentThreadAsDaemon() 函数附加通过 pthread_create() 或 std::thread 启动的线程。在附加之前,线程不包含任何 JNIEnv,也无法调用 JNI。
通常,最好使用 Thread.start() 创建需要调用 Java 代码的任何线程。这样做可以确保您有足够的堆栈空间、属于正确的 ThreadGroup 且与您的 Java 代码使用相同的 ClassLoader。而且,设置线程名称以在 Java 中进行调试也比通过原生代码更容易
附加原生创建的线程会构建 java.lang.Thread 对象并将其添加到“主”ThreadGroup,从而使调试程序能够看到它。在已附加的线程上调用 AttachCurrentThread() 属于空操作。
通过 JNI 附加的线程在退出之前必须调用 DetachCurrentThread()。
总结:
这是最后一篇jni的了,下面将介绍其他的框架,减少jni的开发难度和复杂的
- 点赞
- 收藏
- 关注作者
评论(0)