【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(三)

本文涉及的产品
全局流量管理 GTM,标准版 1个月
云解析 DNS,旗舰版 1个月
公共DNS(含HTTPDNS解析),每月1000万次HTTP解析
简介: 【Android NDK 开发】JNI 方法解析 ( C/C++ 调用 Java 方法 | 函数签名 | 调用对象方法 | 调用静态方法 )(三)

3 . 代码示例 : 调用 Student 类的 logInfo 方法 ;


student 是 Student 类对象 ,
“method_logInfo” 是方法 ID
info : jstring 类型参数 , 传入字符串到 Java 层运行 ( 注意 : 参数 必须 都是 Java 类型 ) ;
extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniObjectTest(JNIEnv *env, jobject instance, jobject student) {
    ...
  //1 . 获取 Java 对应的 Class 对象
    jclass student_class = env->GetObjectClass(student);
    //2 . 获取 Student 的 public static void logInfo(String info) 方法
    //  注意这里要使用 GetStaticMethodID 方法反射该静态方法
    jmethodID method_logInfo = env->GetStaticMethodID(student_class, "logInfo" , "(Ljava/lang/String;)V");
  //3 . 准备传入的 jstring 参数
  jstring info = env->NewStringUTF("C/C++ 创建的 Java 字符串");
    //4 . 调用静态方法 : 注意传入的参数必须都是 Java 类型的参数
    env->CallStaticVoidMethod(student_class, method_logInfo, info);
  ...
}



X . 完整代码示例


完整代码示例 :


extern "C"
JNIEXPORT void JNICALL
Java_kim_hsl_jni_MainActivity_jniObjectTest(JNIEnv *env, jobject instance, jobject student) {
    /*
        参数解析 :
        JNIEnv *env : JNI 环境 , 结构体指针 , 结构体中封装了 209 个方法
        jobject instance : 是 MainActivity 对象
        jobject student : Java 层创建的 Student 对象 , 传入 Native 层
     */
    //在 C/C++ 中调用 Student 对象的 get 方法
    //1 . 获取 Java 对应的 Class 对象
    jclass student_class = env->GetObjectClass(student);
    //2 . 通过 Class 的反射获取要调用的方法
    /*
        函数原型 :
            jmethodID GetMethodID(jclass clazz, const char* name, const char* sig)
            { return functions->GetMethodID(this, clazz, name, sig); }
        参数说明 :
            jclass clazz : 使用 GetObjectClass 方法获取的返回值
            const char* name : 要调用的方法名称
            const char* sig : 函数签名 , 具体的签名规则查看签名表格
        public int getAge() 函数签名 : ()I
            左侧的 () : 左侧的括号是参数列表类型签名
            右侧的 I : 括号右的 I 是返回值类型 int
        public void setAge(int age) 函数签名 : (I)V
            (I) 表示左侧的参数列表
            右侧的 V 表示其返回值类型是 void 类型
        引用类型签名 : L + 全限定名 + ;
        javap 工具 :
            可以使用 javap 工具获取方法签名
     */
    //获取 Student 的 public int getAge() 方法
    jmethodID method_getAge = env->GetMethodID(student_class, "getAge" , "()I");
    //获取 Student 的 public void setAge(int age) 方法
    jmethodID method_setAge = env->GetMethodID(student_class, "setAge" , "(I)V");
    //获取 Student 的 public static void logInfo(String info) 方法
    //  注意这里要使用 GetStaticMethodID 方法反射该静态方法
    jmethodID method_logInfo = env->GetStaticMethodID(student_class, "logInfo" , "(Ljava/lang/String;)V");
    //3 . 调用 Java 对象的方法
    /*
        调用 Java 引用对象的方法 : 要根据 返回值类型不同 , 调用不同的方法
            如果返回值是 int 类型 , 那么就需要调用 CallIntMethod 方法
            如果返回值是 void 类型 , 那么就需要调用 CallVoidMethod 方法
            如果调用的是静态方法 , 那么需要调用
        ( 注意 : 调用方法时传入的参数都必须是 C/C++ 中的 Java 类型参数 , 如 jint , jstring 等 )
     */
    //调用 Student 对象的 public void setAge(int age) 方法
    env->CallVoidMethod(student, method_setAge, 18);
    //调用 Student 的 public int getAge() 方法
    jint age = env->CallIntMethod(student, method_getAge);
    /*
        调用静态方法 :
            1 . 创建 Java 字符串
            2 . 调用静态方法
            3 . 释放 Java 字符串
     */
    // 创建 Java 字符串
    jstring info = env->NewStringUTF("C/C++ 创建的 Java 字符串");
    // 调用静态方法 : 注意传入的参数
    env->CallStaticVoidMethod(student_class, method_logInfo, info);
    // jstring info 在方法中创建新的字符串 , 需要在方法结束之前释放该引用对象
    env->DeleteLocalRef(info);
    //4 . 设置 Student 对象属性
    /*
        反射获取属性
        函数原型 :
            jfieldID GetFieldID(jclass clazz, const char* name, const char* sig)
            { return functions->GetFieldID(this, clazz, name, sig); }
        参数说明 :
            jclass clazz : Java 类对象
            const char* name : 属性名称
            const char* sig : 属性类型签名
        设置反射属性值 :
        函数原型 :
            void SetIntField(jobject obj, jfieldID fieldID, jint value)
            { functions->SetIntField(this, obj, fieldID, value); }
        参数说明 :
            jobject obj : 设置对象
            jfieldID fieldID : 通过 GetFieldID 方法获取的属性 ID
            jint value : 要设置的值
        注意 : 设置不同类型的值 , 调用不同的设置方法
     */
    jfieldID age_field_id = env->GetFieldID(student_class, "age", "I");
    env->SetIntField(student, age_field_id, 90);
    // 验证是否设置成功
    age = env->CallIntMethod(student, method_getAge);
    //5 . 在 JNI 中创建 java 对象 , 并设置给另一个对象
    /*
        获取 Teacher 类 : 通过调用 FindClass 方法获取 Teacher 类
            目前已知两种获取 jclass 的方法
        获取 Teacher 类的构造方法 public Student(int age, String name)
            构造方法的方法名都是 "<init>"
            构造方法的函数签名为
        此处还要特别注意 : 传入到 Java 方法中的参数 , 必须都是 Java 参数类型
            如 jstring , jint , jintArray 等类型 , 不能将 C/C++ 类型传入参数
            尤其是 char* 字符串 , 需要使用 NewStringUTF 将 C/C++ 字符串转为 jstring 类型字符串
        创建 Teacher 对象
        将 Teacher 对象设置给 Student 对象
     */
    // 5.1 获取 Student 的 public void setTeacher(Teacher teacher) 方法
    //  注意这个 Teacher 的类型签名是 Lkim/hsl/jni/Teacher;
    jmethodID method_setTeacher = env->GetMethodID(student_class, "setTeacher" , "(Lkim/hsl/jni/Teacher;)V");
    LOGE("method_setTeacher");
    // 5.2 获取 Teacher 类 ( 该变量需要释放 )
    jclass class_teacher = env->FindClass("kim/hsl/jni/Teacher");
    // 5.3 查找构造方法
    jmethodID method_init = env->GetMethodID(class_teacher, "<init>", "(ILjava/lang/String;)V");
    // 5.4 准备 Java 类型参数 ( 该变量需要释放 )
    //     此处特别注意 : 传入到 Java 方法中的参数都必须是 Java 参数
    jint teacher_age = 88;
    jstring teacher_name = env->NewStringUTF("Tom Wang");
    // 5.5 创建 Teacher 对象 ( 该变量需要释放 )
    jobject teacher = env->NewObject(class_teacher, method_init, teacher_age, teacher_name);
    // 5.6 调用 Student 对象的 setTeacher 设置方法
    env->CallVoidMethod(student, method_setTeacher, teacher);
    // 5.7 释放上面通过 FindClass NewStringUTF NewObject 创建的引用变量 , 便于节省内存 , 也可以等到 作用域结束 自动释放
    //     使用完这三个引用之后 , 不再使用 ; 这里特别建议手动释放三个引用
    //     如果不手动释放 , 在 该引用 作用域 结束后 , 也会自动释放掉
    env->DeleteLocalRef(teacher_name);
    env->DeleteLocalRef(teacher);
    env->DeleteLocalRef(class_teacher);
}



目录
相关文章
|
1月前
|
自然语言处理 编译器 Linux
|
1月前
|
设计模式 安全 数据库连接
【C++11】包装器:深入解析与实现技巧
本文深入探讨了C++中包装器的定义、实现方式及其应用。包装器通过封装底层细节,提供更简洁、易用的接口,常用于资源管理、接口封装和类型安全。文章详细介绍了使用RAII、智能指针、模板等技术实现包装器的方法,并通过多个案例分析展示了其在实际开发中的应用。最后,讨论了性能优化策略,帮助开发者编写高效、可靠的C++代码。
36 2
|
9天前
|
安全 编译器 C++
C++ `noexcept` 关键字的深入解析
`noexcept` 关键字在 C++ 中用于指示函数不会抛出异常,有助于编译器优化和提高程序的可靠性。它可以减少代码大小、提高执行效率,并增强程序的稳定性和可预测性。`noexcept` 还可以影响函数重载和模板特化的决策。使用时需谨慎,确保函数确实不会抛出异常,否则可能导致程序崩溃。通过合理使用 `noexcept`,开发者可以编写出更高效、更可靠的 C++ 代码。
15 0
|
9天前
|
存储 程序员 C++
深入解析C++中的函数指针与`typedef`的妙用
本文深入解析了C++中的函数指针及其与`typedef`的结合使用。通过图示和代码示例,详细介绍了函数指针的基本概念、声明和使用方法,并展示了如何利用`typedef`简化复杂的函数指针声明,提升代码的可读性和可维护性。
36 0
|
1月前
|
存储 编译器 C++
【c++】类和对象(下)(取地址运算符重载、深究构造函数、类型转换、static修饰成员、友元、内部类、匿名对象)
本文介绍了C++中类和对象的高级特性,包括取地址运算符重载、构造函数的初始化列表、类型转换、static修饰成员、友元、内部类及匿名对象等内容。文章详细解释了每个概念的使用方法和注意事项,帮助读者深入了解C++面向对象编程的核心机制。
103 5
|
1月前
|
存储 编译器 C++
【c++】类和对象(中)(构造函数、析构函数、拷贝构造、赋值重载)
本文深入探讨了C++类的默认成员函数,包括构造函数、析构函数、拷贝构造函数和赋值重载。构造函数用于对象的初始化,析构函数用于对象销毁时的资源清理,拷贝构造函数用于对象的拷贝,赋值重载用于已存在对象的赋值。文章详细介绍了每个函数的特点、使用方法及注意事项,并提供了代码示例。这些默认成员函数确保了资源的正确管理和对象状态的维护。
90 4
|
1月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
108 4
WK
|
1月前
|
安全 Java 编译器
C++和Java哪个更好用
C++和Java各具优势,选择取决于项目需求、开发者偏好及目标平台特性。C++性能出色,适合游戏、实时系统等;Java平台独立性强,适合跨平台、安全敏感应用。C++提供硬件访问和灵活编程范式,Java有自动内存管理和丰富库支持。两者各有千秋,需根据具体需求选择。
WK
40 1
|
2月前
|
IDE Java 程序员
C++ 程序员的 Java 指南
一个 C++ 程序员自己总结的 Java 学习中应该注意的点。
26 5
|
1月前
|
自然语言处理 编译器 Linux
告别头文件,编译效率提升 42%!C++ Modules 实战解析 | 干货推荐
本文中,阿里云智能集团开发工程师李泽政以 Alinux 为操作环境,讲解模块相比传统头文件有哪些优势,并通过若干个例子,学习如何组织一个 C++ 模块工程并使用模块封装第三方库或是改造现有的项目。