当前位置: 首页 > news >正文

在NDK开发中如何正确创建JNI方法

在Android NDK开发中,Java Native Interface (JNI) 允许Java代码与C/C++代码交互。正确创建JNI方法对于确保Java能够正确调用本地代码至关重要。本文将详细介绍如何在C++文件中创建JNI方法。

JNI方法命名规则

JNI方法的命名遵循严格的约定,格式如下:

Java_{包名}_{类名}_{方法名}

其中:

  • 包名中的点(.)要替换为下划线(_)
  • 类名和方法名保持不变

示例解析

让我们分析你提供的示例代码:

#include <jni.h>
#include <string>
#include <unistd.h>extern "C" {
JNIEXPORT jstring JNICALL
Java_com_example_MainActivity_getString(JNIEnv *env, jobject obj) {std::string cppStr = "Hello JNI";return env->NewStringUTF(cppStr.c_str()); // 必须用 JNIEnv 创建 Java String
}
}

这个例子中:

  1. Java - 固定前缀,表示这是一个JNI方法
  2. com_example - 包名com_example的转换形式
  3. MainActivity - 包含native方法的Java类名
  4. getString - Java中定义的native方法名

创建JNI方法的步骤

1. 确定Java端的native方法声明

首先在Java类中声明native方法:

package com_example;public class MainActivity extends AppCompatActivity {// 声明native方法public native String getString();// 加载包含实现的本地库static {System.loadLibrary("native-lib");}
}

2. 在C++中实现对应的方法

根据Java端的声明,在C++文件中实现对应的方法:

#include <jni.h>
#include <string>extern "C" {
JNIEXPORT jstring JNICALL
Java_com_example_MainActivity_getString(JNIEnv *env, jobject obj) {std::string cppStr = "Hello JNI";return env->NewStringUTF(cppStr.c_str()); // 必须用 JNIEnv 创建 Java String
}
}

3. 关键元素说明

  • extern "C":确保C++编译器按C语言方式处理函数名(避免名称修饰)
  • JNIEXPORT:宏定义,确保方法在动态库中可见
  • JNICALL:与调用约定相关的宏
  • jstring:返回类型,对应Java的String
  • JNIEnv*:指向JNI环境的指针,提供访问JNI功能的方法
  • jobject:调用该native方法的Java对象引用(相当于Java中的this)

常见问题解决

  1. UnsatisfiedLinkError:通常是由于方法名不匹配导致的,检查包名、类名和方法名是否完全一致
  2. 方法找不到:确保方法签名正确,包括参数和返回类型
  3. 特殊字符处理:如果类名包含$(如内部类),需要替换为_00024

自动化工具

为了避免手动编写容易出错的长方法名,可以使用以下方法:

  1. 使用javah工具自动生成头文件
  2. 在Android Studio中,定义好Java native方法后,按Alt+Enter可以让IDE帮你生成对应的C++方法声明

** extern "C" 的作用**

(1)防止 C++ 的名称修饰(Name Mangling)

  • C++ 支持函数重载,编译器会对函数名进行修饰(Name Mangling),在编译后的二进制文件中,函数名会被修改成包含参数和返回类型的唯一标识符。
    • 例如,int foo(int) 可能被编译成 _Z3fooi,而 float foo(float) 变成 _Z3foof
  • 但 JNI 要求函数名必须严格按照 Java_包名_类名_方法名 的格式,不能有任何改动,否则 Java 在加载动态库时会找不到对应的函数,导致 UnsatisfiedLinkError
  • extern "C" 告诉 C++ 编译器:“不要对这个函数进行名称修饰,保持原样”,这样 JNI 才能正确链接到它。

(2)确保 C 语言风格的链接方式

  • C 语言没有函数重载,所以它的函数名在编译后不会改变。
  • JNI 底层是用 C 语言实现的,所以 JNI 调用的函数必须符合 C 语言的命名规则。
  • extern "C" 强制让 C++ 代码以 C 语言的方式进行编译和链接,从而兼容 JNI 的调用机制。

为什么 JNI 需要 extern "C"

  • Java 通过动态链接库(.so.dll)调用 C/C++ 函数时,必须精确匹配函数名
  • 如果没有 extern "C",C++ 的函数名会被编译器修改,导致 Java 在运行时找不到对应的函数,报错:
    java.lang.UnsatisfiedLinkError: No implementation found for ...
    
  • 使用 extern "C" 后,函数名保持不变,例如:
    extern "C" JNIEXPORT jstring JNICALL
    Java_com_example_MainActivity_helloFromJNI(JNIEnv* env, jobject thiz) {return env->NewStringUTF("Hello from C++!");
    }
    
    编译后,函数名仍然是 Java_com_example_MainActivity_helloFromJNI,Java 可以正确调用它。

如何使用 extern "C"

(1)单个函数

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_MainActivity_getMessage(JNIEnv* env, jobject thiz) {return env->NewStringUTF("Hello from C++!");
}

(2)多个函数(用 {} 包裹)

extern "C" {  // 告诉编译器,以下所有函数都按 C 语言方式编译JNIEXPORT jstring JNICALLJava_com_example_MainActivity_getMessage(JNIEnv* env, jobject thiz) {return env->NewStringUTF("Hello from C++!");}JNIEXPORT jint JNICALLJava_com_example_MainActivity_add(JNIEnv* env, jobject thiz, jint a, jint b) {return a + b;}
}

如果不加 extern "C" 会怎样?**

  • C++ 编译器会修改函数名,例如:
    // C++ 代码
    JNIEXPORT jstring JNICALL
    Java_com_example_MainActivity_getMessage(JNIEnv* env, jobject thiz);
    
    可能会被编译成类似 _Z30Java_com_example_MainActivity_getMessageP7JNIEnv_P8_jobject 的形式。
  • Java 调用时,仍然会按照 Java_com_example_MainActivity_getMessage 查找,结果找不到,导致 UnsatisfiedLinkError

为什么#include <jni.h> #include <unistd.h>可以放在extern "C"里面

** #includeextern "C" 的关系**

  • #include预处理器处理的,它在编译之前就被展开(即把头文件的内容复制到当前文件)。
  • extern "C"编译器处理的,它影响的是编译后的函数名修饰和链接方式。
  • 因此,#include 可以放在 extern "C" 里面或外面,对最终代码没有影响(只要包含的头文件正确)。

为什么有人会把 #include 放在 extern "C" 里面?

(1)确保头文件里的函数以 C 方式编译

如果某个头文件(例如 jni.h 或第三方 C 库)本身是用 C 语言编写的,但被 C++ 代码包含,那么:

  • 如果头文件没有自带 extern "C" 保护,可能会导致 C++ 编译器错误地尝试对 C 函数进行名称修饰(Name Mangling),导致链接错误。
  • 因此,手动用 extern "C" 包裹 #include,可以强制让该头文件里的所有声明按 C 方式编译
示例
extern "C" {#include <jni.h>   // 确保 jni.h 里的 JNI 函数按 C 方式编译#include <unistd.h> // 确保 unistd.h 里的 C 函数(如 sleep())按 C 方式编译
}

更规范的做法:让头文件自己处理 extern "C"

大多数标准 C 头文件(如 <jni.h><unistd.h>已经自带了 extern "C" 保护,例如:

  • jni.h 内部通常会有:

    #ifdef __cplusplus
    extern "C" {
    #endif// JNI 函数声明...#ifdef __cplusplus
    }
    #endif
    

    这样,无论你是用 C 还是 C++ 包含它,都能正确编译。

  • unistd.h(POSIX 标准头文件)通常也有类似的保护。

所以,即使你不手动加 extern "C",直接 #include <jni.h> 也能正常工作


推荐写法

(1)如果头文件已经保护,直接 #include 在外面
#include <jni.h>      // jni.h 自带 extern "C" 保护
#include <unistd.h>   // unistd.h 也自带保护extern "C" {// 只包裹需要导出的 JNI 函数JNIEXPORT void JNICALL Java_com_example_NativeLib_nativeMethod(JNIEnv* env, jobject obj);
}
(2)如果头文件没有 extern "C" 保护(罕见情况),手动包裹
extern "C" {#include "my_legacy_c_lib.h"  // 假设这个头文件没有 extern "C" 保护
}// 然后正常写 JNI 函数...
http://www.dtcms.com/a/277275.html

相关文章:

  • Perl小骆驼学习笔记 - 9. 用正则表达式处理文本
  • 香港服务器Python自动化巡检脚本开发与邮件告警集成
  • 《雨下小暑》诗赏——小暑时节暴雨之晨的清凉视听(智普清言)
  • iOS UI视图面试相关
  • 从儿童涂鸦到想象力视频:AI如何重塑“亲子创作”市场?
  • [特殊字符]使用 Nginx 将 HTTP 重定向到 HTTPS
  • Anaconda3安装教程(Windows)
  • 低代码引擎核心技术:OneCode常用动作事件速查手册及注解驱动开发详解
  • Web应用性能优化之数据库查询实战指南
  • 楼宇自动化:Modbus 在暖通空调(HVAC)中的节能控制(二)
  • 【Linux系统与网络编程】06:进程间通信
  • Day 19: 标准库巡礼:Python的“百宝箱”
  • c++学习之---红黑树的实现
  • CentOS 7 升级系统内核级库 glibc 2.40 完整教程
  • MSVCP*.dll、vcruntime*.dll缺失或损坏,以及.NET Framework相关问题,解决办法
  • 移动端设备本地部署大语言模型(LLM)
  • 【论文阅读】基于注意力机制的冥想脑电分类识别研究(2025)
  • LabVIEW智能避障小车
  • C/C++数据结构之多维数组
  • vue3 el-select默认选中
  • Java_Springboot技术框架讲解部分(二)
  • 【Linux内核模块】模块加载函数--从启动到运行的幕后推手
  • MySQL 分表功能应用场景实现全方位详解与示例
  • 算法学习笔记:19.牛顿迭代法——从原理到实战,涵盖 LeetCode 与考研 408 例题
  • 先“跨栏”再上车 公交站台装70厘米高护栏 公司回应
  • Mock 数据的生成与使用全景详解
  • 知识蒸馏:模型压缩与知识迁移的核心引擎
  • 通过同态加密实现可编程隐私和链上合规
  • GraphRAG:融合知识图谱与RAG的下一代信息检索框架
  • 【RK3568 平台I2C协议与AGS10驱动开发】