基于JNI实现调用C++ SDK

基于JNI实现调用C++ SDK

  • 背景
  • 分析
  • 解决
  • 实践

背景

上篇文章总结了几种Java项目调用C/C++ SDK项目方法,在逐一实践、踩坑后,最终还是敲定采用 JNI 方式进行实现。在文章开始的过程,会先大概讲讲笔者遇到的情况,因为封装方式需要根据实际项目而定,可能不太适合于任何人。

分析

如图,这里列举了一个C++项目的头部文件,我们可以看到第一个方法,它返回的是一个GopHandle对象,而这个对象里有什么属性呢?笔者也不得而知,它的具体定义在一个被打包好的动态库里面,而且这里模板函数方式的格式,也让笔者无从下手。

在这里插入图片描述
这里总结了笔者遇到过的一些问题及解决方式:

  • C++模板函数请求的映射:目前通过各种渠道资源的搜索(chatgpt、google等社区)及外部求助,暂时还未找出好的解决方式。

  • 关于std::string &data_root 类型映射 :这个在用JNA实践过程中,在C++实际操作字符串时(如简单的读取值)会出现 invalid memory access 问题。猜测 &data_root 这种方式的传递,JNA传入的时string的地址,而C++ 对于这个地址的访问属于跨内存访问,去掉 & ,C++可正常访问

  • 对象指针的映射:虽然JNA提供了Pointer 指针类,但在实践过程中,这个 Pointer 对 基础类型(int、short、byte)的指针生效。对于对象类类型的指针,需要我们在Java层面,设计一个包含一摸一样的属性的 class 类去对应 C++ 的 结构体或者class 类。

以上图为例子,我们需要设计一个Java版本的 GopHandle类去对应 C++ 的 GopHandle,但GopHandle 封装的一个结构体或类,其中信息不得而知。

解决

这里GitHub找了很Java调用C++ SDK项目作参考,最终还是敲定 JNI 去实现,对于上面遇到的一些问题,也想出了解决思路:

  • 如果C++ 方法返回的指针,可以将其放到中间层,统一转为整形表示。(指针通常被表示为一个64位整数,可以用来存储内存地址。通过将指针转换为整数类型,可以在需要时保存或传递指针的值,而不需要直接操作指针。这点来自GPT,理性看待
  • 传参时,对于指针传参,统一放到中间层,用C去实现,Java还是采用对应的数据类型传输。
  • 关于模板函数的传参,同样统一放到中间层,用C去实现调用。

思路理清了,接下来就是撸代码了,以下是一些可能写得不太好C代码(现学现卖),但是能用。(手动狗头)

实践

  • Java项目里,根据头部文件定义本地方法:SentScore.java
    在这里插入图片描述
  • 生成头部文件
javac -h . SentScore.java
  • 用C 实现头部文件接口,在这里才去真正调用C++接口
  • 注意!!! 在这里对于内存的操作,不属于JVM的管理范畴,需要调用ReleaseStringUTFChars 手动释放内存,才不会有内存泄漏的风险。
  • 如下,是几个方法的例子:
 
#include "SentScore.h"
#include "SentScore.h"
 
 
#include "SentScoreSDK.h"
#include <vector>
 
 
#undef __request
#define __request EnglishSentPronsRequest
 
JNIEXPORT jlong JNICALL Java_SentScore_HandleCreate(JNIEnv *env, jclass obj, jstring data, jint num_threads){
    GopJniHandle *jni = new GopJniHandle(num_threads);
    if (jni){
        jboolean is_copy;
        // 实现Java里String ->  char *
        const char *data_root = env->GetStringUTFChars(data, &is_copy);
        // 调用SDK
        jni->handle = GopHandleCreate<__request>(data_root, num_threads);
        if (is_copy){
            // 释放内存
            env->ReleaseStringUTFChars(data, data_root);
        }
    }
    // 指针转整形
    return reinterpret_cast<jlong>(jni);
}
 
JNIEXPORT jint JNICALL Java_SentScore_HandleRelease(JNIEnv *env, jclass obj, jlong jni_gop_handle){
   // 整形转指针
    auto *jni = reinterpret_cast<GopJniHandle*>(jni_gop_handle);
    jint ans = 0;
    if (jni){
        if (jni->handle){
           ans = GopHandleRelease<__request>(jni->handle);
           jni->handle = 0;
        }
        delete jni;
    }
    return ans;
}
 
 
 
JNIEXPORT jint JNICALL Java_SentScore_ThreadHandleStarts(JNIEnv *env, jclass obj, jlong jni_gop_thread_handle, jstring jni_ref, jstring jni_utt, jfloat gop_adjust){
    auto *thread = reinterpret_cast<GopJniThreadHandle*>(jni_gop_thread_handle);
    __request request;
    request.result.gop_adjust = gop_adjust;
    if (thread && thread->thread){
    // 把传入的字符串jni_ref 复制到到text_buff
        CopyString(env, thread->text_buff, jni_ref);
        request.ref_text = thread->text_buff.data();
        CopyString(env, thread->text_buff, jni_utt);
        request.audio_id = thread->text_buff.data();
        return GopThreadHandleStarts<__request>(thread->thread, request);
    }
    else{
        jboolean a1, b1;
        const char *a = env->GetStringUTFChars(jni_ref, &a1);
        const char *b = env->GetStringUTFChars(jni_utt, &b1);
        request.audio_id = b;
        request.ref_text = a;
 
        if (a1) env->ReleaseStringUTFChars(jni_ref, a);
        if (b1) env->ReleaseStringUTFChars(jni_utt, b);
        return GopThreadHandleStarts<__request>(NULL, request);
    }
}
  • 最后一步,将上面的C代码跟着SDK 一起打包成动态库(.so),然后.so文件放到库路径下,在项目里引用调用。这里采用的框架是SpringBoot,直接在启动类里加加载库路径。
  • 关于库路径,可以打印如下代码进行获取,或者在程序里指定库路径:
System.getProperty("java.library.path")
  • 如果是Linux上,一般默认会库路径会有 /usr/bin,那么我们也可以通过创建软链接方式,把库文件放入库路径里。
    在这里插入图片描述

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Debian12配置ssh服务器

Debian12配置ssh服务器 安装ssh-server sudo apt install openssh-server启动ssh sudo systemctl start ssh启用ssh sudo systemctl enable ssh查看ssh状态 sudo systemctl status ssh可以看到有enabled和running字样 说明ssh启用成功 连接到服务器 # username是你的用…

React立即更新DOM

正常情况下&#xff0c;react会等待set完毕后再进行页面渲染&#xff0c;所以在set时无法拿到更新后的dom import { useRef, useState } from "react"export default () > {const div useRef(null)const [count, setCount] useState(0)const btnClick () >…

备战春招——12.3 算法

哈希表 哈希表主要是使用 map、unordered_map、set、unorerdered_set、multi_&#xff0c;完成映射操作&#xff0c;主要是相应的函数。map和set是有序的&#xff0c;使用的是树的形式&#xff0c;unordered_map和unordered_set使用的是散列比表的&#xff0c;无序。 相应函数…

【PUSDN】java中easyexcel导入导出带有图片的Excel(main方法方式)

简述 java中easyexcel导入导出带有图片的Excel&#xff08;main方法方式&#xff09;&#xff0c;web方式详见另一篇 由于电脑音频问题&#xff0c;视频暂时没有解说声音&#xff0c; 回头重新补上 前情提示 如果有任何疑问、需求、技术支持&#xff0c;欢迎点赞&#xff0…

微机原理——定时器8253(8254)学习2应用与设计

目录 简要说明 用户扩展的定时计数器应用举例 1 8254作测量脉冲宽度 2 8254作定时 3 8254作分频 4 8254同时用作计数与定时 硬件设计 ​编辑软件设计 微机系统中定时计数器应用举例 5 计时器设计 硬件设计 软件设计 6 发生器设计 硬件设计 软件设计 简要说明 定…

springboot数据源配置

springboot数据源配置 数据层解决方案——持久化技术 内置持久化解决方案——jdbcTemplate 内置数据库 H2一般用于测试环境&#xff0c;配置profiels&#xff0c;只在开发阶段使用&#xff0c;让他在上线的时候不走这里就可以了 要使用内嵌的数据库H2,要先导入jar包

C++ day51 买卖股票最佳时期

题目1&#xff1a;309 买卖股票的最佳时机含冷冻期 题目链接&#xff1a;买卖股票的最佳时机含冷冻期 对题目的理解 prices[i]表示第i天股票的价格&#xff0c;尽可能多地完成更多的交易&#xff0c;不能同时进行多笔交易&#xff0c;卖出股票后&#xff0c;第二天无法买入股…

Hisat-Trinity-PASA等组学分析流程

一边学习&#xff0c;一边总结&#xff0c;一边分享&#xff01; 详细教程请访问&#xff1a; 组学分析流程 本期分析流程 Hisat2-SamtoolsTrinity_GG_denovoPASA … 本期教程文章 题目&#xff1a;Genomic insights into local adaptation and future climate-induced vu…

C++ 基础篇

目录 C开发概述 C特点 C跨平台的原因 C编译器 C库 操作系统API C基本概念 注释 变量 常量 两种定义常量方式的区别 表示符命名规则 常见的关键字 数据类型 整型 浮点数 字符型 转义字符 字符串型 布尔类型 运算符 算术运算符 赋值运算符 比较运算符 逻…

三极管在数字电路中的应用

一、认识三极管 三极管拥有3个引脚&#xff0c;分别对应3个级&#xff1a;基极(Base)、发射极&#xff08;Emitter&#xff09;、集电极(Collector)&#xff0c;如下图所示&#xff1b;下图横向左侧的是基极&#xff0c;带箭头的那个引脚就是发射极&#xff0c;另一个就是集电…

从零开始搭建博客网站-----登陆页面

登录按钮以及背景图设置 安装element-plus和css插件 npm install element-plus --save npm install sass --save npm install sass-loader --save在main.js里引用 寻找背景图存入assets文件下&#xff0c;并且在Login.vue里设置背景图和登录按钮 设置的背景图的大小没有起…

基于ResNet18网络完成图像分类任务

目录 1 数据处理 1.1 数据集介绍 1.2 数据读取 1.3 构造Dataset类 2 模型构建 3 模型训练 4 模型评价 5 模型预测 6 什么是预训练模型和迁移学习 7 比较“使用预训练模型”和“不使用预训练模型”的效果。 总结 在本实践中&#xff0c;我们实践一个更通用的图像分类任务…

物联网开发(一)新版Onenet 基础配置

onenet新创建的账号&#xff0c;没有了多协议接入&#xff0c;只有新的物联网开放平台 第一讲&#xff0c;先给大家讲一下&#xff1a;新版Onenet 基础配置 创建产品 产品开发-->创建产品 产品的品类选择个&#xff1a;大致符合你项目的即可&#xff0c;没有影响 选择智…

构建第一个ArkTS应用(纯HarmonyOS应用)

1. 安装开发工具 在华为开发者官方上下载HarmonyOS应用专用的开发工具&#xff0c;链接地址&#xff1a;HUAWEI DevEco Studio和SDK下载和升级 | HarmonyOS开发者 要想使用开发工具让项目跑起来&#xff0c;需要10G的磁盘空间。开发工具需要的磁盘空间为2.36G&#xff1b;SDK需…

Python笔记1.2(with open() as file和open()、logging、os、shutil、glob、decode和encode)

Python笔记1.1&#xff08;字符串日期转换、argparse、sys、overwrite、eval、json.dumpsloads、os.system(cmd)、zfill、endswith、深浅拷贝&#xff09; Python笔记2&#xff08;函数参数、面向对象、装饰器、高级函数、捕获异常、dir&#xff09; Python笔记1.2 13、with …

Linux(12):磁盘配额(Quota)与进阶文件系统管理

磁盘配额&#xff08;Quota&#xff09;的应用与实作 Quota 的一般用途&#xff1a; 针对 www server &#xff0c;例如:每个人的网页空间的容量限制&#xff1b; 针对 mail server&#xff0c;例如:每个人的邮件空间限制。 针对 file server&#xff0c;例如:每个人最大的可用…

Python 爬虫 之scrapy 框架

文章目录 常用的命令开始爬虫请求与响应让控制台只输出想要的信息创建一个py 文件来帮忙运行爬虫 工作原理图实战 常用的命令 Scrapy是一个用于爬取网站数据的Python框架&#xff0c;以下是一些常用的Scrapy命令&#xff1a; 开始的时候 用 cd 进入你想创建scrapy 的文件夹 &a…

Java中各种数据类型之间的转换

低类型向高类型自动进行转换&#xff0c;高类型向低类型的准换会丢失数据&#xff0c;整数到字符类型的转换将获取对应编码的字符。 进行高精度向低精度的强制类型准换时&#xff0c;需要将想要转换成的数据类型加一个括号()。 如何完成自动转换呢&#xff1f; 转换前的数据类…

Linux 下命令行启动与关闭WebLogic的相关服务

WebLogic 的服务器类型 WebLogic提供了三种类型的服务器&#xff1a; 管理服务器节点服务器托管服务器 示例和关系如下图&#xff1a; 对应三类服务器&#xff0c; 就有三种启动和关闭的方式。本篇介绍使用命令行脚本的方式启动和关闭这三种类型的服务器。 关于WebLogic 的…

系统地自学 Python

文章目录 如何系统地自学 Python1. 选择合适的 Python 版本2. 安装 Python 和必要的工具3. 学习 Python 的基础知识4. 学习 Python 的高级特性5. Python 的应用领域6. 保持良好的学习习惯 如何系统地自学 Python Python 是一种广泛使用的编程语言&#xff0c;它具有简洁、易读、…