• JNI入门


    JNI数据类型及与Java数据类型的映射关系

    Java数据类型JNI数据类型
    bytejbyte
    charjchar
    shortjshort
    intjint
    longjlong
    floatjfloat
    doublejdouble

    java数据类型与签名类型的对应关系

    Java类型类型签名
    booleanZ
    byteB
    intI
    charC
    shortS
    longL
    floatF
    doubleD
    voidV
    数组[类型签名,比如String[] 是[Ljava/lang/String;
    L全限定名;,比如String, 其签名为Ljava/lang/String;(注意后面有个分号)

    静态注册函数

    根据函数名来建立 java 方法与 JNI 函数的一一对应关系;
    命名规则如下:Java+方法的全路径(路径中一定要用“_”代替Java全路径里面的"“.”)
    固定模式1:每个函数头部必须包含extern “C” JNIEXPORT <函数的返回类型> JNICALL
    固定模式2:每个函数的入参必须包含JNIEnv env, jclass clazz

      jobject与jclass通常作为JNI函数的第二个参数,当所声明Native方法是静态方法时,对应参数jclass,因为静态方法不依赖对象实例,而依赖于类,所以参数中传递的是一个jclass类型。相反,如果声明的Native方法时非静态方法时,那么对应参数是jobject

    在MainActivity里面定义了一个native方法:

     public native String stringFromJNI();
    
    • 1

    按照命名对应规则,JNI层的函数注册为:

    extern "C" JNIEXPORT jstring JNICALL
    Java_com_anniljing_jnidemo_MainActivity_stringFromJNI(
            JNIEnv *env,
            jobject /* this */) {
        std::string hello = "Hello from C++";
        LOGD("Get native message success");
        return env->NewStringUTF(hello.c_str());
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    问题1:正常情况下,光标放在native方法上,然后使用快捷键alt+enter会有Create JNI function for setMode提示,选择Create JNI function for setMode,会快速帮我们创建对应的cpp文件,
    在这里插入图片描述
    如果没有的话,需要我们手动创建,手动输入是不存在的,还是把光标放在对应的native方法之上,复制提示的内容,然后加上头部固定模式就可以了。
    在这里插入图片描述

    动态注册函数

    原理:利用 RegisterNatives 方法来注册 java 方法与 JNI 函数的一一对应关系;
    实现流程:
    1、实现 JNI_OnLoad 方法,该方法在jni.h的头文件中定义,其中还定义了JNI_OnUnload方法,在加载动态库后,执行动态注册;

    JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved);
    JNIEXPORT void JNI_OnUnload(JavaVM* vm, void* reserved);
    
    • 1
    • 2
    JNIEXPORT int JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) {
    }
    
    • 1
    • 2

    2、调用 FindClass 方法,获取 java 对象;

     jclass jcls;
        jcls = env->FindClass(name);
        if (jcls == nullptr) {
            return JNI_FALSE;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5

    3、利用结构体 JNINativeMethod 数组记录 java 方法与 JNI 函数的对应关系;

    typedef struct {
        const char* name;
        const char* signature;
        void*       fnPtr;
    } JNINativeMethod;
    
    • 1
    • 2
    • 3
    • 4
    • 5

    name为java层的方法名,signature为java层的参数和返回值的类型签名,fnPtr为jni层方法名

    static JNINativeMethod gMethods[] = {
            {"sum",             "(II)I",                (void *) sum},
            {"getNativeString", "()Ljava/lang/String;", (void *) getMessage}
    };
    
    • 1
    • 2
    • 3
    • 4

    4、调用 RegisterNatives 方法,传入 java 对象,以及 JNINativeMethod 数组,以及注册数目完成注册;

     if (env->RegisterNatives(jcls, methods, nMethods) < 0) {
            return JNI_FALSE;
        }
    
    • 1
    • 2
    • 3

    JNI层访问Java层类属性

    JNI获取属性方法含义
    GetFieldID(jclass clazz, const char* name, const char* sig)获取属性Id,形参:clazz-java类,name-java类属性名,sig-java类属性类型签名
    GetObjectField(jobject obj, jfieldID fieldID)获取属性为对象的
    GetBooleanField(jobject obj, jfieldID fieldID)获取类型为布尔值的属性
    GetByteField(jobject obj, jfieldID fieldID)获取类型为byte的属性
    GetCharField(jobject obj, jfieldID fieldID)获取类型为Char的属性
    GetShortField(jobject obj, jfieldID fieldID)获取类型为Short的属性
    GetIntField(jobject obj, jfieldID fieldID)获取类型为Int的属性
    GetLongField(jobject obj, jfieldID fieldID)获取类型为long的属性
    GetFloatField(jobject obj, jfieldID fieldID)获取类型为float的属性
    GetDoubleField(jobject obj, jfieldID fieldID)获取类型为double的属性

    JNI层设置Java层类属性值

    JNI设置属性方法含义
    SetObjectField(jobject obj, jfieldID fieldID, jobject value)设置类型为Object的属性值
    SetBooleanField(jobject obj, jfieldID fieldID, jobject value)设置类型为Boolean的属性值
    SetByteField(jobject obj, jfieldID fieldID, jobject value)设置类型为Byte的属性值
    SetCharField(jobject obj, jfieldID fieldID, jobject value)设置类型为Char的属性值
    SetShortField(jobject obj, jfieldID fieldID, jobject value)设置类型为Short的属性值
    SetIntField(jobject obj, jfieldID fieldID, jobject value)设置类型为Int的属性值
    SetLongField(jobject obj, jfieldID fieldID, jobject value)设置类型为Long的属性值
    SetFloatField(jobject obj, jfieldID fieldID, jobject value)设置类型为Float的属性值
    SetDoubleField(jobject obj, jfieldID fieldID, jobject value)设置类型为Double的属性值

    第一步、获取到Java类

    jclass jc = env->GetObjectClass(people);
    
    • 1

    第二步、GetFieldID方法获取到Java层类属性

    jfieldID fidAge = env->GetFieldID(jc, "age", "I");
    
    • 1

    第三步、调用SetXXXField()方法,为java层的类属性设置值

    env->SetIntField(people, fidAge, 18);
    
    • 1

    JNI层访问Java层类成员方法

    第一步、获取Java类

    jclass jc = env->GetObjectClass(people);
    
    • 1

    第二步、调用GetMethodID()方法,获取jMethodID

    jmethodID jMethod = env->GetMethodID(jc, "doIntroduce", "()V");
    
    • 1

    第三步、调用CallVoidMethod()方法,调用Java类成员方法

     env->CallVoidMethod(people, jMethod);
    
    • 1

    问题1:新增加的AndroidLog.h的头文件一直无法预编译头文件

    解决方法:

    先创建空的AndroidLog.h文件,然后在其他的cpp源文件加入其预编译的头文件,重新编译一下,AndroidLog头文件再重新加入相关的头文件,编译即可通过

    问题2:新增加jni_dynamic_load.cpp,实现动态注册jni,CMakeList.txt的编写如下:

    image
    新增了如下代码:

    add_library(
            dynamicLoad-lib
            SHARED
            jni_dynamic_load.cpp
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5

    但是编译的时候,抛出之前的AndroidLog.h文件未定义的异常

    555d51a695b861ed316cd118068eefb

    解决方法:

    1、把jni_dynamic_load.cpp放到 native-lib里面,CMakeList.txt如下:

    cmake_minimum_required(VERSION 3.10.2)
    
    project("jnidemo")
    
    
    add_library(
            native-lib
            SHARED
            native-lib.cpp
            jni_dynamic_load.cpp
    )
    find_library(
            log-lib
            log
    )
    include_directories(${CMAKE_HOME_DIRECTORY}/base)
    target_link_libraries(
            native-lib
            ${log-lib})
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这样的话,就放在了一个库里面。

    2、但是这不是我当初想要的效果,我是想生成两个库的。原因肯定是在连接库的地方出现了问题的,于是CMakeList.txt改为如下内容:

    cmake_minimum_required(VERSION 3.10.2)
    
    project("jnidemo")
    
    
    add_library(
            native-lib
            SHARED
            native-lib.cpp
    )
    add_library(
            dynamicLoad-lib
            SHARED
            jni_dynamic_load.cpp
    )
    find_library(
            log-lib
            log
    )
    include_directories(${CMAKE_HOME_DIRECTORY}/base)
    target_link_libraries(
            native-lib
            ${log-lib})
    target_link_libraries(
            dynamicLoad-lib
            ${log-lib}
    )
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    核心代码在于最下面第二个target_link_libraries,就是做了dynamicLoad-lib库与Log库的连接。

    target_link_libraries(
            dynamicLoad-lib
            ${log-lib}
    
    • 1
    • 2
    • 3

    问题3:illegal class name

    image
    image

    cpp文件是无法识别“.”的路径,要把“.”换成“/”即可

    #define JAVA_CLASS "com/anniljing/jnidemo/JniDynamicLoad"
    
    • 1

    GitHub源码

  • 相关阅读:
    C语言找出一个二维数组中的鞍点,即该位置上的元素在该行上最大,在该列上最小,也可能没有鞍点
    热噪声 Thermal noise
    Hadoop总结
    【附源码】Python计算机毕业设计图书销售网站
    猿创征文 |【C++】面向对象之微观部分——类的组成(中)
    密码学【第一节:密码学概述】
    华为OD机试 - 滑动窗口最大和
    第四章:视图【mysql数据库-进阶】
    【Web前端基础进阶学习】HTML详解(下篇)
    分布式应用之监控平台zabbix的认识与搭建
  • 原文地址:https://blog.csdn.net/u011557841/article/details/126253106