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

通常要使用 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);

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏java架构师

C# 温故而知新:Stream篇(—)

C# 温故而知新:Stream篇(—) 什么是Stream? MSDN 中的解释太简洁了: 提供字节序列的一般视图 (我可不想这么理解,这必定让我抓狂,我理解的...

37480
来自专栏Java面试通关手册

Java多线程学习(二)synchronized关键字(2)

Java面试通关手册(Java学习指南,欢迎Star,会一直完善下去,欢迎建议和指导):https://github.com/Snailclimb/Java_G...

23360
来自专栏Java Web

Java 面试知识点解析(四)——版本特性篇

15950
来自专栏DOTNET

.Net多线程编程—System.Threading.Tasks.Parallel

System.Threading.Tasks.Parallel类提供了Parallel.Invoke,Parallel.For,Parallel.ForEach...

419130
来自专栏大内老A

WCF技术剖析之十二:数据契约(Data Contract)和数据契约序列化器(DataContractSerializer)

大部分的系统都是以数据为中心的(Data Central),功能的实现表现在对相关数据的正确处理。而数据本身,是有效信息的载体,在不同的环境具有不同的表示。一个...

36280
来自专栏博客园

WebAPI返回JSON

web api写api接口时默认返回的是把你的对象序列化后以XML形式返回,那么怎样才能让其返回为json呢,下面就介绍两种方法:  方法一:(改配置法)  ...

90920
来自专栏锦小年的博客

python学习笔记6.5-类中描述符的使用

描述符(Descriptor)就是以特殊方法get(), set(), delete()的形式实现了三个核心的属性访问操作(set,get,delete)的类。...

20690
来自专栏贾老师の博客

Boost 中的智能指针

10420
来自专栏我就是马云飞

Retrofit源码模拟

如果要进行网络请求,你可能会这样写一个简单的OKHttp请求 public class CallExector { public static fin...

239100
来自专栏微信公众号:Java团长

Java编程要点之 I/O 流详解

字节流处理原始的二进制数据 I/O。输入输出的是8位字节,相关的类为 InputStream 和 OutputStream.

10310

扫码关注云+社区

领取腾讯云代金券