C++库封装JNI接口——实现java调用c++
1. JNI原理概述
通常为了更加灵活高效地实现计算逻辑,我们一般使用C/C++实现,编译为动态库,并为其设置C接口和C++接口。用C++实现的一个库其实是一个或多个类的简单编译链接产物。然后暴露其实现类构造方法和纯虚接口类。这样就可以通过多态调用到库内部的实现类及其成员方法。进一步地,为了让不同库之间调用兼容,可以将C++接口进一步封装为一组C接口函数,C接口编译时不会添加复杂的函数签名,也不支持函数重载,可以方便其他C或C++客户程序调用。C接口的封装需要有"extern C{}"标识,以告诉编译器请使用C编译方式编译这些函数。
进一步地,为了方便上层应用调用C/C++库, 如Android应用,可以为C++库封装Java接口。jdk中地jni组件可以方便地实现在java中调用c++库函数。基本调用原理如下:

- Java客户代码实现和native方法声明属于java层,使用java编译器编译;
- JNI接口实现代码和c++库属于c++层,使用G++编译。
这里假定C++类库已经预编译好了,有现成的so库和c接口使用。首先明确一点就是,我们要为C++库封装一个java接口,也即在java层使用C++库暴露的所有函数,那么:
- 第一步就是创建一个java类,并按照c++库的接口函数声明,创建所有的native本地接口函数声明(可以是static的)。
- 第二步,将这些本地接口声明映射为C++ JNI接口声明,这一步是通过java提供的工具按照既定的映射机制自动生成。这也就保证了java层能正确找到c++实现。
- 第三步,实现第二步自动生成的c++ JNI接口函数,在这些接口实现中,按照需要调用c++类库的接口函数,以使用特定的功能并拿到需要的结果。所以,这里要注意的一点是,c++ JNI接口函数实现会编译为一个单独的动态库,并且该动态库动态链接C++类动态库。(这里没有尝试过静态库,按道理应该也是可以的)。此外,在c++ JNI函数实现中按照类型签名规则,我们可以获取到从java层传入的参数,也可以返回特定的数据到Java层。
- 第四步,在java应用层使用
system.loadLibrary("libname.so");加载第三步编译生成的jni so库,即可间接调用到c++库函数。
PS:
jni层类型和java类型的对应关系,基本数据类型只是简单地加了前缀
j,如int<=>jint, double<=>jdouble,下面是一些对象类型(包含数组)的类型映射关系:

签名规则对应表


String 字符串函数操作
// 在jni实现函数中把jstring类型的字符串转换为C风格的字符串,会额外申请内存
const char *str = env->GetStringUTFChars(string,0);
// 做检查判断
if (str == NULL){
return NULL;
}
// do something;
// 使用完之后释放申请的内存
env->ReleaseStringUTFChars(string,str);
JNI 支持将 jstring 转换成 UTF 编码和 Unicode 编码两种。因为 Java 默认使用 Unicode 编码,而 C/C++ 默认使用 UTF 编码。所以使用
GetStringUTFChars(jstring string, jboolean* isCopy)将 jstring 转换成 UTF 编码的字符串。其中,jstring 类型参数就是我们需要转换的字符串,而 isCopy 参数的值在实际开发中,直接填 0或NULL就好了,表示深拷贝。当调用完
GetStringUTFChars方法时别忘了做完全检查。因为 JVM 需要为产生的新字符串分配内存空间,如果分配失败就会返回 NULL,并且会抛出 OutOfMemoryError 异常,所以要对 GetStringUTFChars 结果进行判断。当使用完 UTF 编码的字符串时,还不能忘了释放所申请的内存空间。调用
ReleaseStringUTFChars方法进行释放。除了将 jstring 转换为 C 风格字符串,JNI 还提供了将 C 风格字符串转换为 jstring 类型。
通过
NewStringUTF函数可以将 UTF 编码的 C 风格字符串转换为 jstring 类型,通过NewString函数可以将 Unicode 编码的 C 风格字符串转换为 jstring 类型。这个 jstring 类型会自动转换成 Java 支持的 Unicode 编码格式。除了 jstring 和 C 风格字符串的相互转换之外,JNI 还提供了其他的函数。

参考:https://blog.csdn.net/TLuffy/article/details/123994246
2. JNI封装示例
实践出真知,分别建立一个c++工程和java工程,源码github地址。
结构目录如下:
├── cpp_project
│ ├── build.sh
│ ├── CMakeLists.txt
│ ├── include
│ │ ├── c_api.h
│ │ ├── com_Student.h
│ │ └── student.h
│ ├── jni_impl
│ │ └── jni_impl.cpp
│ ├── src
│ │ ├── c_api.cpp
│ │ └── student.cpp
│ └── test
│ └── main.cpp
└── java_project
├── com
│ ├── Student.java
│ └── Test.java
├── com_Student.h
└── run.sh
整体构建流程如下:
- 在java工程下创建和C++类库同名(非必须)的java类源文件,并声明和c++工程接口统一的native成员函数;
使用javac -encoding utf8 -h ./ com/Student.java命令生成naive本地接口.h头文件。将其拷贝到c++工程下。 - 在c++工程下实现jni接口头文件中的函数声明,实现中调用c接口间接完成特定能力调用,编译为libjnilib.so,并链接原始c++库的动态库。
- 回到java工程中,在native接口所在的那个类中,添加jni库加载代码:
// 加载jni库
static {
try {
System.loadLibrary("jnilib");
}
catch(UnsatisfiedLinkError e) {
System.err.println(">>> Can not load library: " + e.toString());
}
}
- java 测试代码调用,使用如下脚本:
# 编译java文件
javac -encoding utf8 com/Test.java -d bin
# 运行java文件
java -Djava.library.path=/root/project/lzq/jni_demo/cpp_project/build/bin -cp bin com.Test
PS: 编译脚本分别在cpp工程和java工程目录下
3. 思考
- 目前即使编译debug版本,调试还是无法进入到jni实现层。有博客说可以通过attach进程可以进入,我尝试并没有成功。
- JNI接口传参和返回数据到java层要注意数据类型匹配,签名要一致,否则会直接崩溃掉。
- 类似的为C++库封装Python接口,并生成一个安装包可以直接使用pip安装也是常见的封装方式,有时间也可以尝试一下。
- 为C++库实现JNI接口可以用Android studio,IDEA,更加方便。也可以直接在Linux上进行,只要有jdk和gcc就可以,但正常人一般不会在linux上写JAVA代码。
C++库封装JNI接口——实现java调用c++的更多相关文章
- Android使用JNI(从java调用本地函数)
当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本 ...
- Java 调用Restful API接口的几种方式--HTTPS
摘要:最近有一个需求,为客户提供一些Restful API 接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试Restful ...
- windows和linux环境下java调用C++代码-JNI技术
最近部门做安卓移动开发的需要调C++的代码,困难重重,最后任务交给了我,查找相关资料,没有一个教程能把不同环境(windows,linux)下怎么调用说明白的,自己在实现的过程中踩了几个坑,在这里总结 ...
- JNI学习笔记_Java调用C —— 非Android中使用的方法
一.学习笔记 1.java源码中的JNI函数本机方法声明必须使用native修饰. 2.相对反编译 Java 的 class 字节码文件来说,反汇编.so动态库来分析程序的逻辑要复杂得多,为了应用的安 ...
- java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可!
java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可! 通过 云寻觅 javacallcsharp 生成器 自动生成java jni类库, ...
- Delphi使用android的NDK是通过JNI接口,封装好了,不用自己写本地代码,直接调用
一.Android平台编程方式: 1.基于Android SDK进行开发的第三方应用都必须使用Java语言(Android的SDK基于Java实现) 2.自从ndk r5发布以后, ...
- java调用jni oci接口宕机原因排查
调用最简单的JNI没有出错,但是涉及到OCI时就会异常退出,分析后基本确定是OCI 11g中的signal所致,参考ora-24550 signo=6 signo=11解决. 但是这个相同的so库直接 ...
- Java调用C/C++实现的DLL动态库——JNI
由于项目的需要,最近研究了java 调用DLL的方法,将如何调用的写于此,便于日后查阅: 采用的方法是JNI:Java Native Interface,简称JNI,是Java平台的一部分,可用于让J ...
- JNI之JAVA调用C++接口
1.JNI定义(来自百度百科) JNI是Java Native Interface的缩写,中文为JAVA本地调用.从Java1.1开始,Java Native Interface(JNI)标准成为ja ...
- Java调用动态库方法说明-最详细
Java不能直接调用由c或者c++写得dll(TF_ID.dll),所以只能采用jni得方法,一步一步生成符合规范得dll文件(假设叫FANGJIAN.dll),在FANGJIAN.dll这个文件里来 ...
随机推荐
- 【Appium_python】利用Template生成对象模板_appium_元素定位/操作
UI自动化中用PageObject设计模式就会发现page元素定位代码基本重复,复制黏贴,修改,所以就想到运用模板方式,批量生成page,同理也能批量生成handle. 有模板,利用配置文件ini获取 ...
- openSUSE 15.4 安装 Deepin Wine QQ
1. 准备: deepin-wine5 deepin-wine-qq deepin-wine-helper 这三个包我是在openSUSE网站上搜索到的,https://software.opensu ...
- jdkman(jdk版本管理工具)安装和使用(mac)
1.安装jdkman 1.1.下载命令 curl -s "https://get.sdkman.io" | bash 执行后,sdkman安装到目录$HOME/.sdkman/,比 ...
- redis存储类型-数字和带双引号的数字
这个是不一样的值,出现了转换异常
- vivado程序示例
//full_add.v 全加器 module full_add( input a, input b, input carry, output sum, output count ); assign ...
- html-list
<!DOCTYPE html><html lang="en"><head> <meta charset="UTF-8" ...
- 文献阅读笔记——Boosting the Performance of CDCL-Based SAT Solvers by Exploiting Backbones and Backdoors
Boosting the Performance of CDCL-Based SAT Solvers by Exploiting Backbones and Backdoors 布尔结构措施 本研究考 ...
- C代码调用C++动态库
最近在工作中遇到了修改C++代码嵌入到C代码中去,C肯定不能直接用C++代码,就需要自己去修改成C代码,所以我就决定在C中调用C++动态库(谁让我懒呢),话不多说,直接上步骤 第一步:编写C++代码 ...
- Post 大小超出允许的限制
原因大体找到了: 除了项目最外层有web.config, 在 Pages页面下还有一个web.config配置文件, 遍历顺序为 :aspx 页面同级目录-->逐级父级目录-->根目录-- ...
- Cpolar使用
官网:https://www.cpolar.com/ 下载好,安装,双击打开,注册,登录