Android架构师之路:JNI与NDK编程-函数注册与C++调用Java详解(c++音视频编码基础)

开发 前端
这篇文章讲解jni中函数的注册和c++调用java的知识点;JNI技术是Java世界与Native世界的通信桥梁,具体到代码,Java层的代码如何同Native层的代码进行调用的呢?

[[412937]]

前言小计

1、jni与ndk的基本知识点前面文章都讲过了,不懂的,可以在公众号首页看;

2、jni中常用的方法比如:类、方法、数组、字符串等前面也讲解过了;

3、这篇文章讲解jni中函数的注册和c++调用java的知识点;

一. JNI函数注册

图片

1、jni函数注解知识点介绍

  • JNI技术是Java世界与Native世界的通信桥梁,具体到代码,Java层的代码如何同Native层的代码进行调用的呢?我们都知道,在调用native方法之前,首先要调用System.loadLibrary接口加载一个实现了native方法的动态库才能正常访问,否则就会抛出java.lang.UnsatisfiedLinkError异常 。那么,在Java中调用某个native方法时,JVM是通过什么方式,能正确的找到动态库中C/C++实现的那个native函数呢?
  • JVM查找native方法有两种方式;
  • 按照JNI规范的命名规则,调用JNI提供的RegisterNatives函数,将本地函数注册到JVM中;
  • 第一种方式,可用使用javah工具按照Java类中定义的native方法,按照JNI规范的命名规则的方式自动生成Jni本地C/C++头文件;
  • 第二种方式则需要在本地库的JNI_OnLoad函数中调用RegisterNatives来动态注册;
  • JNI函数注册是将Java层声明的Native方法同实际的Native函数绑定起来的实现方式,也就是说,只要通过JNI函数注册机制注册了本地方,Java层就可以直接调用定义的这些本地方法了。对应上述JVM查找native方法的两种方式,JNI函数注册方式一般分为静态注册和动态注册两种方式。

2、静态注册

原理:根据函数名来建立 java 方法与 JNI 函数的一一对应关系;

实现流程:

  • 编写 java 代码;
  • 利用 javah 指令生成对应的 .h 文件;
  • 对 .h 中的声明进行实现;

弊端:

编写不方便,JNI 方法名字必须遵循规则且名字很长;

编写过程步骤多,不方便;

程序运行效率低,因为初次调用native函数时需要根据根据函数名在JNI层中搜索对应的本地函数,然后建立对应关系,这个过程比较耗时;

  1. public class Test { 
  2.     static { 
  3.         System.loadLibrary("native-lib"); 
  4.     } 
  5.     public native String textFromJni(); 
  6. 使用javah生成对应的本地方法头文件。 
  7. #include <jni.h> 
  8. #ifndef _Included_test 
  9. #define _Included_test 
  10. #ifdef __cplusplus 
  11. extern "C" { 
  12. #endif 
  13. JNIEXPORT jstring JNICALL Java_test_Test_textFromJni 
  14.   (JNIEnv *, jobject); 
  15. #ifdef __cplusplus 
  16. #endif 

3、动态注册

对应与上面的静态注册方法,还有一种动态注册JNI函数的方式,即动态注册。动态注册是当Java层调用System.loadLibrary方法加载so库后,本地库的JNI_OnLoad函数会被调用,在JNI_OnLoad函数中通过调用RegisterNatives函数来完成本地方法的注册。

原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;

实现流程:

  • 利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;
  • 实现 JNI_OnLoad 方法,在加载动态库后,执行动态注册;
  • 调用 FindClass 方法,获取 java 对象;
  • 调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;

优点:

流程更加清晰可控;

效率更高;

其中JNINativeMethod结构体用来描述本地方法结构,其定义如下:

  1. typedef struct { 
  2.     const charname;      // Java方法名 
  3.     const char* signature; // Java方法签名 
  4.     void* fnPtr;           // jni本地方法对应的函数指针 
  5. } JNINativeMethod; 

结构体的第一个参数 name 是java 方法名;

第二个参数 signature 用于描述方法的参数与返回值;

第三个参数 fnPtr 是函数指针,指向 jni 函数;

其中,第二个参数 signature 使用字符串记录方法的参数与返回值,具体格式形如“()V”、“(II)V”,其中分为两部分,括号内表示的是参数,括号右侧表示的是返回值;

①、数据类型映射

基本数据类型

②. 数组引用类型

如果是一维数组则遵循下表,如果是二维数组或更高维数组则对应的 native 类型为 jobjectArray,域描述符中使用 ‘[’ 的个数表示维数

③. 对象引用类型

对于其它引用类型,即 java 中的对象,其映射规则为

④. 对象数组引用类型

如果是一维数组则遵循下表,如果是二维数组或更高维数组则对应的 native 类型为 jobjectArray,域描述符中使用 ‘[’ 的个数表示维数

在Java文件中定义本地方法,加载本地so库

  1. package test.jnitest; 
  2. public class Test { 
  3.     static { 
  4.         System.loadLibrary("native-lib"); 
  5.     } 
  6.     public native String textFromJni(); 

在JNI_OnLoad函数中注册本地方法

  1. jstring textFromJni(JNIEnv* env, jobject thiz) { 
  2.     return env->NewStringUTF("text from jni"); 
  3. static JNINativeMethod gMethods[] = { 
  4.         {"textFromJni""()Ljava/lang/String;", (void*)textFromJni} 
  5. }; 
  6. int registerMethod(JNIEnv *env) { 
  7.     jclass test = env->FindClass("cc/ccbu/jnitest/Test"); 
  8.     return env->RegisterNatives(test, gMethods, sizeof(gMethods)/ sizeof(gMethods[0])); 
  9. JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { 
  10.     JNIEnv* env = NULL
  11.     if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) { 
  12.         return JNI_ERR; 
  13.     } 
  14.     if (registerMethod(env) != JNI_OK) { 
  15.         return JNI_ERR; 
  16.     } 
  17.     return  JNI_VERSION_1_6; 

注意:

在JNI_OnLoad函数的结尾处,我们一定要有返回值,而且必须是JNI_VERSION_1_4 或 JNI_VERSION_1_6,也就是JNI的版本号,我们一定要返回正确的版本号,否则系统也是无法加载的;

4、c++调用java详解

(1) 找到java对应的Class

(2) 找到要调用的方法的methodID

(3) 在C语言中调用相应方法

①.通过JAVA层的本地方法创建同类对象

步骤:

I.通过对象获取类

II.通过类获取类的构造方法的ID

III.基于方法ID和类,创建新对象

  1. JNIEXPORT void JNICALL JAVA_nativeMethod 
  2.         (JNIEnv *env, jobject thiz,jint i){ 
  3.     ... 
  4.     jclass clazz = (*env).GetObjectClass(thiz); 
  5.     jmethodID mid = (*env).GetMethodID(clazz,"<init>","()V"); 
  6.     jobject obj = (*env).NewObject(clazz,mid); 
  7.     ... 
  8.     return

②.通过C/C++创建不同类对象

步骤:

I.通过FindClass方法获取需要的类

II.通过类获取类的构造方法的ID

III.基于方法ID和类,创建新对象

  1. JNIEXPORT void JNICALL JAVA_nativeMethod 
  2.         (JNIEnv *env, jobject thiz,jint i){ 
  3.     ... 
  4.     jclass clazz = (*env).FindClass("com/x/test/Test");//参数为类路径 
  5.     jmethodID mid = (*env).GetMethodID(clazz,"<init>","()V"); 
  6.     jobject obj = (*env).NewObject(clazz,mid); 
  7.     ... 
  8.     return

③获取上下文环境JNIEnv

如果找不到上下文JNIEnv就要获取

  1. bool AttachCurrentThread(JavaVM* vm, JNIEnv** p_env) 
  2.     bool bAttached = false
  3.  
  4.     switch(vm->GetEnv((void**)p_env, JNI_VERSION_1_4)) 
  5.     { 
  6.         case JNI_OK: 
  7.             break; 
  8.         case JNI_EDETACHED: 
  9.  
  10.             if (vm->AttachCurrentThread(p_env, 0) < 0) 
  11.             { 
  12.                 LOGD("%s :test failed!",__func__); 
  13.                 return false
  14.             } 
  15.             else 
  16.             { 
  17.                 bAttached = true
  18.             } 
  19.             break; 
  20.         case JNI_EVERSION: 
  21.             LOGE("Invalid java version"); 
  22.             break; 
  23.     } 
  24.   return bAttached; 
  25.  

总结

以上总结了JNI中函数注册的两种方法,在实际应用中都很常见都用得到的,要理解到位才可以;

下次文章会继续讲解关于JNI的知识点和高级应用

本文转载自微信公众号「Android开发编程」,可以通过以下二维码关注。转载本文请联系Android开发编程公众号。

 

责任编辑:姜华 来源: Android开发编程
相关推荐

2021-07-20 05:34:45

JNINDKC++

2012-04-28 15:28:21

JNI混合编程Java

2011-07-01 14:55:28

Qt QML C++

2009-07-20 09:53:43

Java混合编程

2012-03-20 11:37:24

JavaJNI

2010-01-28 13:35:41

调用C++函数

2010-01-19 15:36:02

C++语言

2011-08-04 13:38:01

Objective-C C++

2011-08-22 17:13:00

LuaC++函数

2010-01-21 11:23:58

C++函数调用

2010-01-25 09:57:39

C++函数参数

2010-01-11 10:28:51

C++编程

2010-02-01 16:13:15

C++继承

2010-01-12 17:55:03

C++程序

2010-02-02 15:59:32

C++赋值函数

2009-08-13 17:30:30

C#构造函数

2011-08-22 17:25:31

LuaC++函数

2023-11-09 23:31:02

C++函数调用

2010-01-20 14:25:56

函数调用

2011-07-14 17:45:06

CC++
点赞
收藏

51CTO技术栈公众号