JNI 详细介绍

news2025/2/24 6:20:33

一 介绍 

java调⽤c++,c++代码可以通过JNIEnv执行java代码。

安卓NDK 已经对JNI环境进行了集成,我们可以通过android studio来快速搭建一个项目。

二 项目搭建

打开android studio 创建工程,创建工程选择模板Native C++

 三 模板格式介绍

生成的模板java类如下

public class MainActivity extends AppCompatActivity {

    // Used to load the 'jnidemo' library on application startup.
    static {
        System.loadLibrary("jnidemo");
    }

    private ActivityMainBinding binding;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        binding = ActivityMainBinding.inflate(getLayoutInflater());
        setContentView(binding.getRoot());

        // Example of a call to a native method
        TextView tv = binding.sampleText;
        tv.setText(stringFromJNI());
    }

    /**
        这个方法的实现在c++测
     */
    public native String stringFromJNI();
}

native 标记的方法没有方法体,表示在c++测实现,

对应c++测的代码如下:

extern "C"   下⾯的⽅法采⽤C的编译⽅式
JNIEXPORT // JNIEXPORT 关键字 标记该⽅法可以被外部调⽤
jstring // 返回值类型 对应 java 测的 String
JNICALL // 关键字 jni call 约束函数⼊栈顺序,和堆栈内存清理规则
Java_com_example_jnidemo_MainActivity_stringFromJNI(
        JNIEnv* env,
        jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

那长串的名字

Java_com_example_jnidemo_MainActivity_stringFromJNI

红色为包名,绿色为类名,蓝色为方法名。

JNIEnv是c++ 和java沟通的桥梁,env就代表了java环境

四 签名和类型

你们肯定很懵,下面的表是什么,先不管它,请先硬着往下看

java类型对应在c++代码处的类型:

 

typedef void* jobject;  /*对应任何Java对象,通常对应⼀个类的实例*
typedef jobject jclass;  /*对应Java类对象,⽐如Student类*/
typedef jobject jstring;  /*java.lang.String*/
typedef jobject jarray;   /*数组*/

typedef jarray jobjectArray; /*任何对象的数组*/
typedef jarray jbooleanArray; /*boolean []*/
typedef jarray jbyteArray;  /*byte []*/
typedef jarray jcharArray; /*char []*/
typedef jarray jshortArray; /*short []*/
typedef jarray jintArray;  /*int []*/
typedef jarray jlongArray; /*long []*/
typedef jarray jfloatArray; /*float []*/
typedef jarray jdoubleArray; /*double []*/
typedef jobject jthrowable; /*java.lang.Throwable*/
typedef jobject jweak;      /**/

五 函数调用

java 直接调用native 的方法,会执行c++处的实现,c++也可以通过env调用java处的代码。

c++侧执⾏java⽅法

hello,word 

便于理解我java和c++ 代码放在了一块展示,请小白对比看

//java 处代码
public void hello(){
    System.out.println("hello,word!");
}
public native void runHello();

// c++ 处代码
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_fuc_TestMain_runHello(JNIEnv *env, jobject thiz)
    // env是和java沟通的桥梁
    // thiz代表 java对象this, 谁调用native,那么thiz就是那个对象的thiz  
    // 比如 A.runHello(),那么thiz就是A对象

    // 获得Class 对象 
    jclass thisClass= env->GetObjectClass(thiz);

    // 根据方法名字、参数、返回类型; 获得 hello ⽅法id,
    // "hello" 为方法名字
    // 后面的()V  () 代表参数为空,V代表返回值为void 
    jmethodID helloMethodID= env->GetMethodID(thisClass,"hello", "()V");

    // 根据方法id来执行方法,执行 thiz的hello方法
    env->CallVoidMethod(thiz,helloMethodID);
}

c++执行java 测的有参函数

// java 处代码
private int add(int a,int b){
    return a+b;
}
public native int runAdd(int a,int b);


// c++ 处代码
extern "C"
JNIEXPORT jint JNICALL
Java_com_example_myapplication_fuc_TestMain_runAdd(JNIEnv *env, jobject thiz, jint a,jint b){

    // 获得Class 对象
    // jclass 表示java Class 类型
    jclass thisClass= env->GetObjectClass(thiz);

    // 获取add ⽅法id
    // (II)I 表示参数类型 第一个I表示第一个参数 类型为int类型,第二个I表示第二个参数为int类型,
    // 括号外面那个I 表示返回类型为I即int
    // 方法签名对应请看 上文第四节
    jmethodID addMethodID= env->GetMethodID(thisClass,"add", "(II)I");

    // 运⾏ add(a,b) 
    // jint 对应java int类型
    jint result= env->CallIntMethod(thiz,addMethodID,a,b);
    return result;
}


// test 处的测试方法
// 测试 add
@Test
public void testAdd(){

    // 实际上运行c++处的实现,c++处的实现又去调用java的 add方法
    int result = testMain.runAdd(4, 2); 

    System.out.println("addResult:"+result);
}

这么一看是不是非常像反射调用执行方法,我们对比一下反射学习:

// 反射执⾏
@Test
public void load() throws Exception{
    // 获得class对象
    Class<? extends TestMain> aClass = testMain.getClass();

    // 获得add⽅法
    Method add = aClass.getDeclaredMethod("add", int.class, int.class);
    // 设置可⻅性
    add.setAccessible(true);

    // 运⾏add⽅法
    Object invoke = add.invoke(testMain,2, 4);
    System.out.println("执⾏结果:"+invoke);
}

是的,非常类似,都是先获得class对象,然后获得方法,再执行方法。

修改成员属性值

在c++处修改 java对象的成员变量

// java 处
public  int age;

public int getAge() {
    return age;
}
public native void setAge();


// c++ 处
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_fuc_TestMain_setAge(JNIEnv *env, jobject thiz) {
    // 获得class对象
    jclass thisClass= env->GetObjectClass(thiz);

    // 获得 属性ID
    jfieldID ageID=env->GetFieldID(thisClass,"age", "I");
    // 执⾏修改
    env->SetIntField(thiz,ageID,35);
}

// test
@Test
public void testSetAge(){
    System.out.println("修改前:"+testMain.getAge());
    testMain.setAge();
    System.out.println("修改后:"+testMain.getAge());
}

创建对象

在c++侧创建⼀个List数组,并向它添加"A","B"元素

// java
public native List<String> getStringArray();

// C++
extern "C"
JNIEXPORT jobject JNICALL
Java_com_example_myapplication_fuc_TestMain_getStringArray(JNIEnv *env, jobject){
    // 获得ArrayList Class 对象
    jclass listClass= env->FindClass("java/util/ArrayList");

    // 获得ArrayList 构造⽅法id
    // 构造方法签名统一为 <init>
    jmethodID initMethodID= env->GetMethodID(listClass,"<init>", "()V");

    // 创建 ArrayList 对象,相当于在java处那么 new了一个对象
    jobject list= env->NewObject(listClass,initMethodID);
    // 获得add ⽅法id
    jmethodID addMethodID= env->GetMethodID(listClass,"add", "(ILjava/lang/Object;)V");

    // 创建 A 和 B
    jstring A= env->NewStringUTF("A");
    jstring B= env->NewStringUTF("B");

    // 添加元素
    env->CallVoidMethod(list,addMethodID,0,A);
    env->CallVoidMethod(list,addMethodID,1,B);

    // 释放本地引⽤
    // 只是释放c++ 测的引用,并没有删除 java那么对应的对象
    env->DeleteLocalRef(A);
    env->DeleteLocalRef(B);
    return list;
}

// 在 c++ 的实现相当于执⾏了如下java 代码
ArrayList<Object> list = new ArrayList<>();
list.add(0,"A");
list.add(1,"B");

创建全局引⽤

1. 全局引⽤:全局引⽤在整个Java虚拟机中都有效,只要它们还在被使⽤,它们就不会被垃圾回收。全局引⽤在JNI代码结束时必须显式删除。

jobject globalRef = env->NewGlobalRef(localRef);// ...
env->DeleteGlobalRef(globalRef);

2. 本地引⽤:本地引⽤只在单个JNI调⽤期间有效。当JNI⽅法返回时,所有的本地引⽤都会⾃动被删除。(保险起⻅需要⼿动删除)

jobject localRef = env->NewLocalRef(tem);// ...
env->DeleteLocalRef(localRef);

异常处理

在JNI中,异常处理有点不⼀样,当在c++调⽤java⽅法抛出异常时候,函数不会直接捕获异常,异常会直接挂起,需要⼿动处理
• ExceptionCheck :这个⽅法⽤于检查是否有未处理的异常。它返回⼀个布尔值,如果存在未
处理的异常,则返回 true ,否则返回 false 。
• ExceptionOccurred :这个⽅法⽤于检查是否有异常发⽣。它返回⼀个指向异常对象的引⽤,
如果没有异常发⽣,则返回 nullptr 。
应该在执⾏⽅法时候都做⼀个异常处理,防⽌程序崩溃

 

// java
// 抛出运⾏异常
public int throwError(){
    return 1/0;
}

public native void tryError();


// c++
// 抛出异常
extern "C"
JNIEXPORT void JNICALL
Java_com_example_myapplication_fuc_TestMain_tryError(JNIEnv *env, jobject thiz){
    // 执⾏异常代码
    jclass thisClass= env->GetObjectClass(thiz);
    jmethodID throwErrorMethodId= env->GetMethodID(thisClass,"throwError","()I");

    // 异常
    jint result= env->CallIntMethod(thiz,throwErrorMethodId);
    // 是否有异常
    if(env->ExceptionOccurred()!= nullptr){
        // 输出异常信息
        env->ExceptionDescribe();
         // 清除挂起的异常
        env->ExceptionClear();
       
        // 创建 RuntimeException 对象
        jclass runErrorClass = env->FindClass("java/lang/RuntimeException");
        // 抛出异常 消息"ERROR!"
        env->ThrowNew(runErrorClass,"ERROR!");
        return;
    }
}

// 测试
@Test
public void testError(){
    try {
        testMain.tryError();
    }catch (Exception e){
        System.out.println("有异常");
    e.printStackTrace();
    }
}

六 总结



c++ 调⽤java⽅法
        1、获得class对象
        2、获得对象中的⽅法id
        3、执⾏⽅法


c++创建java对象
        1、获得class对象
        2、获得构造⽅法id
        3、执⾏构造⽅法


异常处理流程
        c++层调⽤执⾏java对象⽅法
        异常处理




如果认为对你有帮助,欢迎点赞收藏 

如果认为对你有帮助,欢迎点赞收藏 

如果认为对你有帮助,欢迎点赞收藏 

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2137698.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

char 的整数存储解释

目录 1.原因讲解 1.有符号类型的char 2.无符号类型的char(0-255) 练习题1. 练习题2. 练习题3. 小心&#xff01;VS2022不可直接接触&#xff0c;否则&#xff01;没这个必要&#xff0c;方源面色淡然一把抓住&#xff01;顷刻炼化&#xff01; 1.原因讲解 1.有符号类型的…

AUTOSAR_EXP_ARAComAPI的5章笔记(6)

返回目录 5.3.5.5 Event-Driven vs Polling-Based access ara::com实现完全支持事件驱动和轮询的方式来访问新数据。 对于轮询方式&#xff0c;典型的用例是&#xff0c;一个应用程序被周期性地触发并在特定的截止时间前进行一些处理。这是调节器/控制算法的典型模式 —— 循…

如何清除KylinOS桌面操作系统桌面密钥环?

如何清除KylinOS桌面操作系统桌面密钥环&#xff1f; 1、问题现象2、解决 &#x1f496;The Begin&#x1f496;点点关注&#xff0c;收藏不迷路&#x1f496; 1、问题现象 打开桌面应用会出现【解锁秘钥环提示】&#xff1a; 2、解决 1、安装seahorse sudo apt-get insta…

【JavaEE】IP协议 应用层协议

&#x1f525;个人主页&#xff1a; 中草药 &#x1f525;专栏&#xff1a;【Java】登神长阶 史诗般的Java成神之路 &#x1f576;️一.IP地址 IP协议&#xff08;Internet Protocol&#xff09;是TCP/IP协议族中最核心的协议之一&#xff0c;它定义了数据包在网络中传输的标准…

Word中插入当前日期与时间

Word中插入当前日期与时间 通过构建基块的方法快速插入当前日期与时间 快捷键操作 快捷键具体功能说明 Alt Shift D 插入当前日期date Alt Shift T 插如当前时间time Ctrl Shift F9 使得域文本变为正常文本 Ctrl F11 锁定域更新域菜单工具会变为黑色 C…

音视频入门基础:AAC专题(5)——FFmpeg源码中,判断某文件是否为AAC裸流文件的实现

一、引言 通过FFmpeg命令&#xff1a; ./ffmpeg -i XXX.aac 可以判断出某个文件是否为AAC裸流文件&#xff1a; 所以FFmpeg是怎样判断出某个文件是否为AAC裸流文件呢&#xff1f;它内部其实是通过adts_aac_probe函数来判断的。从《FFmpeg源码&#xff1a;av_probe_input_for…

Qt/C++ 了解NTFS文件系统,获取首张MFT表数据,解析文件记录头内容找到第一个属性偏移地址

系列文章目录 一、Qt/C 了解NTFS文件系统&#xff0c;了解MFT(Master File Table)主文件表&#xff08;一&#xff09; 二、Qt/C 了解NTFS文件系统&#xff0c;解析盘符引导扇区数据获取MFT(Master File Table)主文件表偏移地址 三、Qt/C 了解NTFS文件系统&#xff0c;获取首张…

还在为企微联系人烦恼?一招解决!企业微信2024年效率升级全攻略

现在信息多得让人眼花&#xff0c;微信里头那些企业微信的联系人是不是让你头疼&#xff1f; 看着满屏的绿色头像&#xff0c;心里想&#xff1a;“我就想和朋友聊聊天&#xff0c;怎么就这么难&#xff1f;”别急&#xff0c;今天教你个办法&#xff0c;轻松搞定这些小烦恼&am…

【无标题】达瓦达瓦

&#x1f4e2;博客主页&#xff1a;https://blog.csdn.net/2301_779549673 &#x1f4e2;欢迎点赞 &#x1f44d; 收藏 ⭐留言 &#x1f4dd; 如有错误敬请指正&#xff01; &#x1f4e2;本文由 JohnKi 原创&#xff0c;首发于 CSDN&#x1f649; &#x1f4e2;未来很长&#…

胤娲科技:解锁AI奥秘——产品经理的智能进化之旅

当AI不再是遥不可及的科幻 想象一下&#xff0c;你走进一家未来感十足的咖啡厅&#xff0c;无需言语&#xff0c;智能咖啡机就能根据你的偏好调制出一杯完美的拿铁&#xff1b; 打开手机&#xff0c;AI助手不仅提醒你今天有雨&#xff0c;还贴心推荐了最适合雨中漫步的音乐列表…

C语言中的GCC的优化和数组的存放方式、Cache机制、访问局部性

“我们仍需共生命的慷慨与繁华相爱&#xff0c;即使岁月以刻薄和荒芜相欺” 文章目录 前言文章有误敬请斧正 不胜感恩&#xff01;第一题&#xff1a;***什么是gcc&#xff1a;***C 语言中&#xff0c;“gcc -O2”是使用 GCC 编译器时的一个编译选项。第一部分&#xff1a;为什…

利用yolov8模型实现的西红柿成熟程度检测系统包含源码+配置说明(基于YOLOv8的西红柿成熟程度检测系统)

西红柿成熟程度的检测对于农业生产尤为重要&#xff0c;可以提高收获效率和产品质量。本项目利用YOLOv8&#xff08;You Only Look Once v8&#xff09;模型实现了一个高效的西红柿成熟程度检测系统。该系统可以自动识别西红柿的颜色&#xff0c;从而判断其成熟程度。 关键特性…

pycharm从VCS获取项目报错unable to access:Recv failure:Connection was reset

&#xff08;已老实求放过&#xff09; 版本&#xff1a;PyCharm Community Edition 2024.2.1 【解决办法】取消Git的网络代理&#xff0c;在目标路径所在文件夹处右键选择Git Bash Here&#xff0c;输入以下命令&#xff1a; git config --global --unset http.proxy git …

2024最新版零基础学习Modbus通信协议(保姆级教程)

合集 - 上位机开发(2) 1.零基础学习Modbus通信协议09-13 2.RS485与ModbusRTU09-10 收起 大家好&#xff01;我是付工。 2012年开始接触Modbus协议&#xff0c;至今已经有10多年了&#xff0c;从开始的懵懂&#xff0c;到后来的顿悟&#xff0c;再到现在的开悟&#xff0c;…

STM32的寄存器深度解析

目录 一、STM32 寄存器概述 二、寄存器的定义与作用 三、寄存器分类 1.内核寄存器 2.外设寄存器 四、重要寄存器详解 1.GPIO 相关寄存器 2.定时器相关寄存器 3.中断相关寄存器 4.RCC 相关寄存器 五、寄存器操作方法 1.直接操作寄存器 2.使用库函数操作寄存器 六…

4个方法教你图片转PDF怎么弄。

我们有时候会接触了一些重要的图片文件或者资料&#xff0c;想要装换成可编辑的PDF格式&#xff0c;更方便自己管理。这时候就会需要转换的工具&#xff0c;我这里就有&#xff14;款可以完成这种转换的高效工具可以分享给大家。 1、365PDF转换软件 直通车&#xff1a;www.pdf…

软件设计之JavaWeb(3)

软件设计之JavaWeb(3) 此篇应在MySQL之后进行学习: 路线图推荐&#xff1a; 【Java学习路线-极速版】【Java架构师技术图谱】 尚硅谷全新JavaWeb教程&#xff0c;企业主流javaweb技术栈 资料可以去尚硅谷官网免费领取 此章节最好学完JDBC观看 学习内容&#xff1a; 请求转发…

jenkins流水线+k8s部署springcloud微服务架构项目

文章目录 1.k8s安装2.jenkins安装3.k8s重要知识1.简介2.核心概念3.重要命令1.查看集群消息2.命名空间3.资源创建/更新4.资源查看5.描述某个资源的详细信息6.资源编辑7.资源删除8.资源重启9.查看资源日志10.资源标签 4.k8s控制台1.登录2.界面基本操作1.选择命名空间2.查看命名空…

损耗金属件检测系统源码分享

损耗金属件检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

全球和局部精细化:提升大模型推理能力的新方法

人工智能咨询培训老师叶梓 转载标明出处 尽管大模型在数学、科学或编程任务上表现出优异的推理精细化能力&#xff0c;但它们在没有外部反馈的情况下&#xff0c;很难识别何时何地需要精细化。为了解决这一问题&#xff0c;来自Meta的FAIR团队和佐治亚理工学院的研究者们提出了…