首页 > 编程语言 >C++库封装JNI接口——实现java调用c++

C++库封装JNI接口——实现java调用c++

时间:2023-04-05 16:14:56浏览次数:56  
标签:java c++ 接口 C++ jni JNI

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代码。

标签:java,c++,接口,C++,jni,JNI
From: https://www.cnblogs.com/lee-zq/p/17274876.html

相关文章

  • Java Scanner的next和nextLine的区别
    一.next要读取到有效字符才能结束输入,否则会一直处于读取状态读取到有效字符前的空格,会自动清除只有读取到有效字符后,才会把之后的空格清除next不能读取带有空格的字符串空格不能输出只有读取到有效字符后才输出只能输出空格之前的字符二.nextLine1.以Enter为结束......
  • 关于我用ai做了一点c++开发这件事
    简介C++一直不是我的主力编程语言,何况众所周知C++学习曲线之陡峭,尽管如此我还是动了挑战一下用ai写一下c++程序的念头,事实证明ai非常高效的帮助我完成了我的目标,在大概7个小时的工作时间里帮助我写完了一个简单的汇编器,我目前主要使用newbing作为ai助手,我们的完整QA如下:QA过程......
  • java——maven——分模块开发与设计
                注意:   参数说明:-DgroupId:项目组ID,通常为组织名或公司网址的反写。-DartifactId:项目名。-DarchetypeArtifactId:指定ArchetypeId,maven-archetype-quickstart用于快速创建一个简单的Maven项目。-DinteractiveMode:是......
  • Java基础
    注释1.单行注释//用于注释少量的代码或者对附近的代码进行说明2.多行注释/*/多用于注释多行代码3.文档注释/**/一般用于对类和方法进行说明vscode注释方法的快捷键:1.单行注释:选中区域Ctrl+/取消注释同样是这个快捷键2.多行注释:选中区域Alt+Shif......
  • UE5 修复 C++ 代码里的中文在蓝图中显示为乱码的问题
    1.打开VS2019,依次点击扩展->管理扩展,搜索“UTF8”,安装后源码文件会强制保存为UTF-8。注:可能需要重新保存一下,但是只要保存文件的动作生效就会自动检测-转换编码。 2.使用示例    ......
  • Redis 的 Java 客户端
    实际项目中,需要通过编程语言去访问并操作Redis。Redis官方提供了多种语言的客户端,具体可访问以下地址:https://redis.io/clientsJava语言访问Redis,常用的API包括:(1)Jedis:一个很小但很健全的redis的java客户端,通过Jedis可以像使用Redis命令行一样使用Redis;Jedis......
  • java.lang.NoClassDefFoundError: javax/servlet/jsp/jstl/core/ConditionalTagSuppor
    1.报错截图2.问题原因缺少对应的类3.问题解决<dependency><groupId>taglibs</groupId><artifactId>standard</artifactId><version>1.1.2</version></dependency><......
  • JAVA Spring Boot与海康威视摄像头的故事
    前言:JAVASpringBoot与海康威视摄像头的故事这两天因工作原因,需要对海康威视摄像头进行二次开发。说实话,刚打开开发手册的那一刻,很劝退。由于之前接触硬件开发不多,对于其中的嵌入式设备SDK、DLL动态组件库的内容不甚了解。挠破了头皮,冲!关于本贴刚开始的时候,真的是一步一个坑,虽......
  • Java代码规范和一些常见问题
     本文中的代码规范,是Java标准代码规范中的一小部分,在我看来,是最重要的一部分。  理想目标:不需要写注释,不需要和别人介绍,别人就知道你的项目大致是做什么的,每个类大概实现了什么功能。一.目的   一致性、快速阅读和理解  后期维护、提高工作效率 ......
  • Java并发和多线程4:使用通用同步工具CountDownLatch实现线程等待
    CountDownLatch,一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。 用给定的计数初始化CountDownLatch。由于调用了countDown()方法,所以在当前计数到达零之前,await方法会一直受阻塞。之后,会释放所有等待的线程,awai......