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++库暴露的所有函数,那么:

  1. 第一步就是创建一个java类,并按照c++库的接口函数声明,创建所有的native本地接口函数声明(可以是static的)。
  2. 第二步,将这些本地接口声明映射为C++ JNI接口声明,这一步是通过java提供的工具按照既定的映射机制自动生成。这也就保证了java层能正确找到c++实现。
  3. 第三步,实现第二步自动生成的c++ JNI接口函数,在这些接口实现中,按照需要调用c++类库的接口函数,以使用特定的功能并拿到需要的结果。所以,这里要注意的一点是,c++ JNI接口函数实现会编译为一个单独的动态库,并且该动态库动态链接C++类动态库。(这里没有尝试过静态库,按道理应该也是可以的)。此外,在c++ JNI函数实现中按照类型签名规则,我们可以获取到从java层传入的参数,也可以返回特定的数据到Java层。
  4. 第四步,在java应用层使用system.loadLibrary("libname.so");加载第三步编译生成的jni so库,即可间接调用到c++库函数。

PS:

  1. jni层类型和java类型的对应关系,基本数据类型只是简单地加了前缀j,如int<=>jint, double<=>jdouble,下面是一些对象类型(包含数组)的类型映射关系:

  2. 签名规则对应表



  3. 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

整体构建流程如下:

  1. 在java工程下创建和C++类库同名(非必须)的java类源文件,并声明和c++工程接口统一的native成员函数;

    使用javac -encoding utf8 -h ./ com/Student.java命令生成naive本地接口.h头文件。将其拷贝到c++工程下。
  2. 在c++工程下实现jni接口头文件中的函数声明,实现中调用c接口间接完成特定能力调用,编译为libjnilib.so,并链接原始c++库的动态库。
  3. 回到java工程中,在native接口所在的那个类中,添加jni库加载代码:
    // 加载jni库
static {
try {
System.loadLibrary("jnilib");
}
catch(UnsatisfiedLinkError e) {
System.err.println(">>> Can not load library: " + e.toString());
}
}
  1. 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. 思考

  1. 目前即使编译debug版本,调试还是无法进入到jni实现层。有博客说可以通过attach进程可以进入,我尝试并没有成功。
  2. JNI接口传参和返回数据到java层要注意数据类型匹配,签名要一致,否则会直接崩溃掉。
  3. 类似的为C++库封装Python接口,并生成一个安装包可以直接使用pip安装也是常见的封装方式,有时间也可以尝试一下。
  4. 为C++库实现JNI接口可以用Android studio,IDEA,更加方便。也可以直接在Linux上进行,只要有jdk和gcc就可以,但正常人一般不会在linux上写JAVA代码。

C++库封装JNI接口——实现java调用c++的更多相关文章

  1. Android使用JNI(从java调用本地函数)

    当编写一个混合有本地C代码和Java的应用程序时,需要使用Java本地接口(JNI)作为连接桥梁.JNI作为一个软件层和API,允许使用本地代码调用Java对象的方法,同时也允许在Java方法中调用本 ...

  2. Java 调用Restful API接口的几种方式--HTTPS

    摘要:最近有一个需求,为客户提供一些Restful API 接口,QA使用postman进行测试,但是postman的测试接口与java调用的相似但并不相同,于是想自己写一个程序去测试Restful ...

  3. windows和linux环境下java调用C++代码-JNI技术

    最近部门做安卓移动开发的需要调C++的代码,困难重重,最后任务交给了我,查找相关资料,没有一个教程能把不同环境(windows,linux)下怎么调用说明白的,自己在实现的过程中踩了几个坑,在这里总结 ...

  4. JNI学习笔记_Java调用C —— 非Android中使用的方法

    一.学习笔记 1.java源码中的JNI函数本机方法声明必须使用native修饰. 2.相对反编译 Java 的 class 字节码文件来说,反汇编.so动态库来分析程序的逻辑要复杂得多,为了应用的安 ...

  5. java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可!

    java 调用 C# 类库 实战视频, 非常简单, 通过 云寻觅 javacallcsharp 生成器 一步即可! 通过 云寻觅 javacallcsharp 生成器 自动生成java jni类库,  ...

  6. Delphi使用android的NDK是通过JNI接口,封装好了,不用自己写本地代码,直接调用

    一.Android平台编程方式:      1.基于Android SDK进行开发的第三方应用都必须使用Java语言(Android的SDK基于Java实现)      2.自从ndk r5发布以后, ...

  7. java调用jni oci接口宕机原因排查

    调用最简单的JNI没有出错,但是涉及到OCI时就会异常退出,分析后基本确定是OCI 11g中的signal所致,参考ora-24550 signo=6 signo=11解决. 但是这个相同的so库直接 ...

  8. Java调用C/C++实现的DLL动态库——JNI

    由于项目的需要,最近研究了java 调用DLL的方法,将如何调用的写于此,便于日后查阅: 采用的方法是JNI:Java Native Interface,简称JNI,是Java平台的一部分,可用于让J ...

  9. JNI之JAVA调用C++接口

    1.JNI定义(来自百度百科) JNI是Java Native Interface的缩写,中文为JAVA本地调用.从Java1.1开始,Java Native Interface(JNI)标准成为ja ...

  10. Java调用动态库方法说明-最详细

    Java不能直接调用由c或者c++写得dll(TF_ID.dll),所以只能采用jni得方法,一步一步生成符合规范得dll文件(假设叫FANGJIAN.dll),在FANGJIAN.dll这个文件里来 ...

随机推荐

  1. 西瓜书 5.5 编写过程(标准BP与累计BP)

    话不多说先用numpy表示出数据集 Y=['色泽','根蒂','敲声','纹理','脐部','触感','密度','含糖率','好瓜与否']D=np.array([[2,1,2,3,3,1,0.697, ...

  2. Docker-compose安装Wordpress

    Docker-compose实战Wordpress 96 iXiAo9 2019.01.10 18:54 字数 82 阅读 105评论 0喜欢 0 Docker-compose实战Wordpress ...

  3. 通过VS Code轻松连接树莓派

    如果您正在使用树莓派作为开发平台,那么通过远程连接VS Code到树莓派是非常方便的一种方法.这样,您可以在Windows或macOS等计算机上开发和测试代码,而不必在树莓派上进行. 以下是通过VS ...

  4. v-show无法隐藏自定义组件

    因为v-show的本质是设置标签display:none,但组件并非是一个标签,所以无法使用v-show控制组件 错误用法: 正确用法:

  5. mysql索引的面试常问问题

  6. centos7 安装 elasticsearch 7.15

    下载 elasticsearch wget https://artifacts.elastic.co/downloads/elasticsearch/elasticsearch-7.15.2-linu ...

  7. new一个实例的原理及过程

    前提,要明白new出来的实例是什么,包含了哪些内容? 请看一下举例代码↓↓↓↓ function Person(name,age){ this.name = name; this.age = age; ...

  8. Q:oracle19c 如何重建vm_concat函数

    没有wm_concat是因为oracle12c数据库中废弃了wm_concat函数 --–登录dba用户sys权限,为wmsys用户创建可用的wm_concat函数,依次执行以下语句 第一步 定义对象 ...

  9.  NuGet国内镜像

    NuGet国内镜像 https://nuget.cdn.azure.cn/v3/index.json

  10. 面试题:int[] arr 和 int... arr在参数列表中是一回事儿吗?

    public class Exer { public static void main(String[] args) { Base1 b1 = new Sub1(); b1.add(1,2,3); } ...