NDK学习笔记(二)使用JNI同原生代码通信

一个人想着一个人 提交于 2020-02-29 21:06:53

通常要使用 JNI 技术来实现 Java 应用程序和原生代码的通信。

任何使用JNI的操作都需要两次或者三次函数调用,因此要实现大量的原生方法并让它们同Java类保持同步很容易编程一件非常艰辛的工作。

而利用一些开源的方案则可以帮助我们基于现有的原生代码接口自动生成 JNI 的代码。

  • 学习这项技术,首先需要搞清楚下面这些关键概念:
  • 原生代码如何被Java代码调用到。
  • 原生方法的声明。
  • 从共享库加载原生模块。
  • 使用 C/C++ 来实现原生方法。

原生方法的声明

在Java代码中使用native关键字可以声明原生方法,例如:

public native String stringFromJNI();

注意只是声明而已,“()”这对括弧后面直接就是分号了。

加载共享库

static { System.loadLibrary("hello-jni");//libhello-jni.so }

之所以要在静态代码块中调用 System.loadLibrary ,就是为了Java类首次加载和初始化时就能让原生代码的实现被载入。

在原生代码中实现声明的方法

原生代码是C/C++的,因此要有一个头文件明确要实现那些方法,JDK为我们提供了javah(头文件生成器)来根据已经编译好的class生成头文件,例如:

javah –classpath bin/classes com.example.hellojni.HelloJni

javah 可以配置到 Eclipse 的 External Tools 中,方便使用。

关于原生代码中声明的方法

Java代码中对原生方法的声明可以不带上参数,但对应的原生函数式带有参数的,例如:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv *, jobject);

JNIEnv 是一个指针,指向可供JNI使用的函数列表。JNIEnv所指向的类型根据语言不同(C/C++)而不同,如果是C,则其所指向的类型就是 JNINativeInterface 结构,如果是C++,则是一个拥有成员方法的类实例。

如果要返回一个 String ,C的写法是:

return (*env)->NewStringUTF(env, "Hello from JNI !");//因为C中的JNI函数不清楚当前的JNI环境,所以要传入env。

而C++的写法则是:

return env->NewStringUTF("Hello from JNI !");

jobject 是一个引用了 HelloJni 类的实例的 Java 对象。

原生实例方法的定义示例如下:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jobject thiz);

原生静态方法的定义示例如下:

JNIEXPORT jstring JNICALL Java_com_example_hellojni_HelloJni_stringFromJNI (JNIEnv * env, jclass clazz);

关于数据类型

当然是分为了原生类型和引用类型。

原生类型在Java , JNI 和 C/C++中有各自的对应的表示方法,引用类型也是。

字符串操作

新建字符串:

jstring javaString; javaString = (*env)->NewStringUTF(env, "Hello World!");

Java 字符串转成 C 字符串: const jbyte* str; jboolean isCopy; str = (*env)->GetStringUTFChars(env, javaString, &isCopy); if (0 != str) { printf("Java string: %s", str); if (JNI_TRUE == isCopy) { printf("C string is a copy of the Java string."); } else { printf("C string points to actual string."); }

数组操作

新建数组:

jintArray javaArray; javaArray = (env)->NewIntArray(env, 10); if (0 != javaArray) { / You can now use the array. */ }

通过操作副本来访问数组元素:

jint nativeArray[10]; (*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray);

将在 C 数组上的修改提交到 Java Array 中:

(*env)->SetIntArrayRegion(env, javaArray, 0, 10, nativeArray);

直接在指针上对数组进行操作:

jint* nativeDirectArray; jboolean isCopy; nativeDirectArray = (*env)->GetIntArrayElements(env, javaArray, &isCopy);

内存空间操作

新建:

unsigned char* buffer = (unsigned char*) malloc(1024); ... jobject directBuffer; directBuffer = (*env)->NewDirectByteBuffer(env, buffer, 1024);

获取:

unsigned char* buffer; buffer = (unsigned char*) (*env)->GetDirectBufferAddress(env, directBuffer);

访问 Java 类中的域

public class JavaClass { // Instance field private String instanceField = "Instance Field"; // Static field private static String staticField = "Static Field"; ... }

获取域ID:

jclass clazz; clazz = (*env)->GetObjectClass(env, instance);

jfieldID instanceFieldId; instanceFieldId = (*env)->GetFieldID(env, clazz, "instanceField", "Ljava/lang/String;");

获取静态域ID:

jstring staticField; staticField = (*env)->GetStaticObjectField(env, clazz, staticFieldId);

调用 Java 类中的方法

public class JavaClass { //Instance method. private String instanceMethod() { return "Instance Method"; } //Static method. private static String staticMethod() { return "Static Method"; } ... }

获取方法ID:

jmethodID instanceMethodId; instanceMethodId = (*env)->GetMethodID(env, clazz, "instanceMethod", "()Ljava/lang/String;");

获取静态方法ID:

jmethodID staticMethodId; staticMethodId = (*env)->GetStaticMethodID(env, clazz, "staticMethod", "()Ljava/lang/String;");

调用方法:

jstring instanceMethodResult; instanceMethodResult = (*env)->CallStringMethod(env, instance, instanceMethodId);

调用静态方法:

jstring staticMethodResult; staticMethodResult = (*env)->CallStaticStringMethod(env, clazz, staticMethodId);

关于方法和域的描述符

Java Type Signature Boolean Z Byte B Char C Short S Int I Long J Float F Double D fully-qualified-class Lfully-qualified-class; type[] [type method type (arg-type)ret-type

使用 javap 可以从 class 文件中提取出域和方法的描述符:

javap –classpath bin/classes –p –s com.example.hellojni.HelloJni

捕获异常

public class JavaClass { //Throwing method. private void throwingMethod() throws NullPointerException { throw new NullPointerException("Null pointer"); } //Access methods native method. private native void accessMethods(); }

原生代码是这么写的:

jthrowable ex; ... (*env)->CallVoidMethod(env, instance, throwingMethodId); ex = (*env)->ExceptionOccurred(env); if (0 != ex) { (*env)->ExceptionClear(env); //Exception handler. }

抛出异常

jclass clazz; ... clazz = (*env)->FindClass(env, "java/lang/NullPointerException"); if (0 ! = clazz) { (*env)->ThrowNew(env, clazz, "Exception message."); }

本地和全局引用

jclass clazz; clazz = (*env)->FindClass(env, "java/lang/String");

新增全局引用:

jclass localClazz; jclass globalClazz; ... localClazz = (*env)->FindClass(env, "java/lang/String"); globalClazz = (*env)->NewGlobalRef(env, localClazz); ... (*env)->DeleteLocalRef(env, localClazz);

删除全局引用:

(*env)->DeleteGlobalRef(env, globalClazz);

全局弱引用

新增全局弱引用:

jclass weakGlobalClazz; weakGlobalClazz = (*env)->NewWeakGlobalRef(env, localClazz);

验证全局弱引用是否可用:

if (JNI_FALSE == (*env)->IsSameObject(env, weakGlobalClazz, NULL)) { //Object is still live and can be used. } else { //Object is garbage collected and cannot be used. }

删除全局弱引用:

(*env)->DeleteWeakGlobalRef(env, weakGlobalClazz);

多线程

Java代码是这样写的:

synchronized(obj) { //Synchronized thread-safe code block. }

原生代码应该这样写:

if (JNI_OK == (*env)->MonitorEnter(env, obj)) { //Error handling. } //Synchronized thread-safe code block. if (JNI_OK == (*env)->MonitorExit(env, obj)) { // Error handling. }

原生多线程

将当前线程同虚拟机绑定和解绑: JavaVM* cachedJvm; ... JNIEnv* env; ... //Attach the current thread to virtual machine. (*cachedJvm)->AttachCurrentThread(cachedJvm, &env, NULL); //Thread can communicate with the Java application using the JNIEnv interface. //Detach the current thread from virtual machine. (*cachedJvm)->DetachCurrentThread(cachedJvm);

标签
易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!