JNI全流程实例使用总结

本文涉及的产品
文档翻译,文档翻译 1千页
文本翻译,文本翻译 100万字符
语种识别,语种识别 100万字符
简介: JNI全流程实例使用总结

为了更好的获得一些比较独立的模块的性能,比如视频模块,寻路模块,通过对C++ 接口的封装,通过JNI技术对它进行跨语言调用。

那什么是JNI呢?JNI (Java Native Interface,Java本地接口)是一种编程框架,使得Java虚拟机中的Java程序可以调用本地应用/或库,也可以被其他程序调用。 本地程序一般是用其它语言(C、C++或汇编语言等)编写的,并且被编译为基于本机硬件和操作系统的程序。


生成头文件

首先要编辑头文件对应的JAVA文件,就是暴露出native接口的JAVA类。

public class GamiooJNI  {
    static {
        try {
            NativeUtils.loadLibrary("recast");
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
        }
    }
    /**
     * 获取寻路API版本号
     *
     * @return 获取寻路API版本号
     */
    public native String getVersion();
}

java ->*.h 工具

点击 File > Settings > Tools > External Tools,添加一个先的External Tools:

Name:Generate Header File
Description: 生成C++类的头文件
Program: $JDKPath$/bin/javah
Arguments: -jni -classpath $OutputPath$ -d ./jni $FileClass$
Working directory: $ProjectFileDir$

做好后,在某个java文件导出

在GamiooJNI.java文件中点击右键> External Tools > Generate Header File

会在项目根目录下的jni目录生成文件,如果生成不出来的话,编译下该Java文件,并把*.h文件给删了先。

实际上,就是执行了类似如下指令:

"D:\Program Files\Java\TencentKona-8.0.4/bin/javah" -jni -classpath F:\gamioo\out\production\classes -d ./jni com.gamioo.ooxx.GamiooJNI

生成的文件:com_gamioo_ooxx_GamiooJNI.h

/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class com.gamioo.ooxx.GamiooJNI */
#ifndef _Included_com_gamioo_ooxx_GamiooJNI
#define _Included_com_gamioo_ooxx_GamiooJNI
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_gamioo_ooxx_GamiooJNI
 * Method:    getVersion
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_gamioo_ooxx_GamiooJNI_getVersion
  (JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif

编辑C++ 文件

用CLion去创建dll工程,引入头文件com_gamioo_ooxx_GamiooJNI.h

创建cpp文件

#include "jni.h"
#include <iostream>
#include <exception>
#include <string>
#include <cstdint>
#include <map>
#include "com_gamioo_ooxx_GamiooJNI.h"
using namespace std;
static const int NAVMESHSET_VERSION = 1;
const char* NavMesh::Version()
{
  return  ""+ NAVMESHSET_VERSION;
}
/**
 * 获取寻路API版本号
 *
 * @return 获取寻路API版本号
 */
JNIEXPORT jstring JNICALL Java_com_gamioo_ooxx_GamiooJNI_getVersion
(JNIEnv* env, jobject jobj) {
  const char* version = NavMesh::GetInstace()->Version();
  return env->NewStringUTF(version);
}

这里需要注意,一开始第二行的#include <jni.h>报错了,这时因为MinGW编译器没有jni.h这个头文件,打开JDK的home目录,在include目录中可以找到jni.h头文件,除此之外,我们还需要include/win32目录下的jni_md.h头文件,一共两个,把这两个头文件都复制到MinGW安装目录(CLion 2021.3.3\bin\mingw\x86_64-w64-mingw32\include目录中,注意这两个头文件是一起放在MinGW的这个目录的,jni_md.h不需要另外创建一个win32目录来存放。完成后发现com_example_jni_JNIObject.h的报错消失了。

如果是在VS 2019环境下,则要在属性–>VC++目录–>包含目录里加上JDK目录:

类型互转

有很多类型的互转需要注意,类型互转问题转不好,还有内存泄漏的问题,这里会陆续总结:

/**JByteaArray -> char* */
   static char* ConvertJByteaArrayToChars(JNIEnv* env, jbyteArray bytearray)
  {
    char* chars = NULL;
    jbyte* bytes;
    bytes = env->GetByteArrayElements(bytearray, 0);
    size_t chars_len = env->GetArrayLength(bytearray);
    chars = new char[chars_len + 1];
    memset(chars, 0, chars_len + 1);
    memcpy(chars, bytes, chars_len);
    chars[chars_len] = 0;
    env->ReleaseByteArrayElements(bytearray, bytes, 0);
    return chars;
  }
    /** float* -> jfloatArray */
   static  jfloatArray ConvertFloatStarToJfloatArray(JNIEnv* env, float* array, int length) {
    jfloatArray ret = env->NewFloatArray(length);
    env->SetFloatArrayRegion(ret, 0, length, array);
    return ret;
  }
jstring stringTojstring(JNIEnv* env, const char* pat) {
    jclass strClass = (env)->FindClass("java/lang/String");
    jmethodID ctorID = (env)->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
    jbyteArray bytes = (env)->NewByteArray(strlen(pat));
    (env)->SetByteArrayRegion(bytes, 0, strlen(pat), (jbyte*)pat);
    jstring encoding = (env)->NewStringUTF("GB2312");
    return (jstring)(env)->NewObject(strClass, ctorID, bytes, encoding);
}
std::string jstringTostring(JNIEnv* env, jstring jstr) {
    char* rtn = NULL;
    jclass clsstring = env->FindClass("java/lang/String");
    jstring strencode = env->NewStringUTF("GB2312");
    jmethodID mid = env->GetMethodID(clsstring, "getBytes", "(Ljava/lang/String;)[B");
    jbyteArray barr = (jbyteArray)env->CallObjectMethod(jstr, mid, strencode);
    jsize alen = env->GetArrayLength(barr);
    jbyte* ba = env->GetByteArrayElements(barr, JNI_FALSE);
    if (alen > 0) {
        rtn = (char*)malloc(alen + 1);
        memcpy(rtn, ba, alen);
        rtn[alen] = 0;
    }
    env->ReleaseByteArrayElements(barr, ba, 0);
    std::string stemp(rtn);
    free(rtn);
    return stemp;
}
std::string toStr(JNIEnv* env, jstring jstr)
{
  return toStr(env, env->GetStringUTFChars(jstr, 0));
}
std::string toStr(JNIEnv* env, const char* chs)
{
  std::string s(chs);
  return s;
}
jstring toJstring(JNIEnv* env, std::string str)
{
  return toJstring(env, str.c_str());
}
jstring toJstring(JNIEnv* env, char* chs)
{
  return env->NewStringUTF(chs);
}

生成 DLL,SO 文件

CMAKE 文件

CMakeLists.txt 的内容如下,依照葫芦画瓢就行。

cmake_minimum_required(VERSION 3.22)
find_package(JNI REQUIRED)
# Use C++11
set(CMAKE_CXX_STANDARD 11)
# Require (at least) it
set(CMAKE_CXX_STANDARD_REQUIRED ON)
# Don't use e.g. GNU extension (like -std=gnu++11) for portability
set(CMAKE_CXX_EXTENSIONS OFF)
include_directories(${JNI_INCLUDE_DIRS})
if ( WIN32 AND NOT CYGWIN AND NOT ( CMAKE_SYSTEM_NAME STREQUAL "WindowsStore" ) )
    set(CMAKE_C_FLAGS_RELEASE "${CMAKE_C_FLAGS_RELEASE} /MT" CACHE STRING "")
    set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} /MTd" CACHE STRING "")
    set(CMAKE_CXX_FLAGS_RELEASE "${CMAKE_CXX_FLAGS_RELEASE} /MT" CACHE STRING "")
    set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} /MTd" CACHE STRING "")
endif ()
project(RecastDll)
find_path(RecastDll_PROJECT_DIR NAMES SConstruct
    PATHS
    ${CMAKE_SOURCE_DIR}
    NO_DEFAULT_PATH
)
MARK_AS_ADVANCED(RecastDll_PROJECT_DIR)
# 配置cpp文件
file(GLOB RECASTDLL_SOURCES 
  Source/*.cpp
  ../Detour/Source/*.cpp 
  ../DetourCrowd/Source/*.cpp 
  ../DetourTileCache/Source/*.cpp 
  ../Recast/Source/*.cpp
)
# 配置头文件
include_directories(
  Include
  ../DebugUtils/Include
  ../Detour/Include
  ../DetourCrowd/Include
  ../DetourTileCache/Include
  ../Recast/Include
)
macro(source_group_by_dir proj_dir source_files)
    if(MSVC)
        get_filename_component(sgbd_cur_dir ${proj_dir} ABSOLUTE)
        foreach(sgbd_file ${${source_files}})
            get_filename_component(sgbd_abs_file ${sgbd_file} ABSOLUTE)
            file(RELATIVE_PATH sgbd_fpath ${sgbd_cur_dir} ${sgbd_abs_file})
            string(REGEX REPLACE "\(.*\)/.*" \\1 sgbd_group_name ${sgbd_fpath})
            string(COMPARE EQUAL ${sgbd_fpath} ${sgbd_group_name} sgbd_nogroup)
            string(REPLACE "/" "\\" sgbd_group_name ${sgbd_group_name})
            if(sgbd_nogroup)
                set(sgbd_group_name "\\")
            endif(sgbd_nogroup)
            source_group(${sgbd_group_name} FILES ${sgbd_file})
        endforeach(sgbd_file)
    endif(MSVC)
endmacro(source_group_by_dir)
source_group_by_dir(${CMAKE_CURRENT_SOURCE_DIR} RECASTDLL_SOURCES)
add_library(RecastDll SHARED ${RECASTDLL_SOURCES})
if ( WIN32 AND NOT CYGWIN )
    target_compile_definitions (RecastDll PRIVATE DLL_EXPORTS)
endif ( )

make_win64.bat & make_linux64.sh 文件

然后在写好windows下的bat,和linux 下的sh脚本:

make_win64.bat

mkdir build64 & pushd build64
cmake -G "Visual Studio 16 2019" -A x64 ..
popd
cmake --build build64 --config Release
md Plugins\x86_64
copy /Y build64\Release\RecastDll.dll Plugins\x86_64\recast.dll
rmdir /S /Q build64
pause
mkdir -p build_linux64 && cd build_linux64
cmake ../
cd ..
cmake  --build build_linux64 --config Release
cp build_linux64/libRecastDll.so Plugins/x86_64/recast.so
rm -rf build_linux64

这里会遇到cmake 的版本问题,版本要求

CMake 3.22 or higher is required. You are running version 2.8.12.2

先卸载本地的cmake

yum erase cmake

安装高版本的

yum install -y epel-release
yum install -y cmake3

验证版本:

cmake3 --version

可以做个软链接生效cmake 指令

ln -s /usr/bin/cmake3 /usr/bin/cmake

如果遇到异常 :Could NOT find OpenGL (missing: OPENGL_opengl_LIBRARY OPENGL_glx_LIBRARY

OPENGL_INCLUDE_DIR), 缺少OpenGL库, libgl1-mesa-dev是有关OpenGL的库

yum install mesa-libGL-devel mesa-libGLU-devel  -y

如果遇到异常: Could NOT find SDL2 (missing: SDL2_LIBRARY SDL2_INCLUDE_DIR)

yum install SDL2-devel -y

如果版本不够的话手工下载安装:

因为通过yum 安装的版本都相对很低:

[root@VM-16-13-centos ~]# yum --showduplicates list cmake3 | expand        
Available Packages
cmake3.x86_64                         3.17.5-1.el7                          epel
[root@VM-16-13-centos ~]# yum --showduplicates list cmake | expand        
Available Packages
cmake.x86_64                         2.8.12.2-2.el7                         base
wget https://cmake.org/files/v3.22/cmake-3.22.6.tar.gz
cd cmake-3.22.6
yum install build-essential
返回cmake-3.22.6的上层目录cd .. 执行chmod -R 777 cmake-3.22.6
到目录cmake-3.22.6执行./bootstrap ,再执行make,再执行make install
验证  cmake --version
ln -s /usr/local/bin/cmake /usr/bin

如果遇到:Could NOT find JNI (missing: JAVA_AWT_LIBRARY JAVA_INCLUDE_PATH

JAVA_INCLUDE_PATH2 JAVA_AWT_INCLUDE_PATH),看看环境变量里JAVA_HOME有没有。

顺利的话应该会导出结果如下:

[root@VM-16-13-centos RecastDll]# sh make_linux64.sh 
-- Found JNI: /usr/local/j2sdk-image/jre/lib/amd64/libjawt.so  
-- The C compiler identification is GNU 4.8.5
-- The CXX compiler identification is GNU 4.8.5
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Configuring done
-- Generating done
....
....
[100%] Built target RecastDll

windows 下安装如下文件:

https://cmake.org/files/v3.22/cmake-3.22.6-windows-x86_64.msi,执行make_win64.bat

分别在windows下和linux 下导出:

加载DLL,SO 文件

一般我们会把dll文件和so文件随着对应JNI暴露的JAVA文件所在的jar包一起导出,

–jni.java

*.dll

*.so

但调用的动态链接库文件又必须是独立的,如何做到呢,我们需要把文件复制到临时目录下,然后用System.load()调用。

/**
 * 用于加载native dll的工具类
 *
 * @author Allen Jiang
 */
public class NativeUtils {
    public static void loadLibrary(String name) throws IOException {
        String suffix = "";
        //TODO 暂时只为两种系统服务
        if (SystemUtils.IS_OS_LINUX) {
            suffix += ".so";
        } else {
            suffix += ".dll";
        }
        try (InputStream inputStream = FileUtils.getInputStream(name + suffix); ByteArrayOutputStream out = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int n = 0;
            while (-1 != (n = inputStream.read(buffer))) {
                out.write(buffer, 0, n);
            }
            File file = File.createTempFile(name, suffix);
            try (FileOutputStream fileOutputStream = new FileOutputStream(file)) {
                fileOutputStream.write(out.toByteArray());
            }
            System.load(file.getAbsolutePath());
        }
    }
}

然后在JNI接口类里加载进来:

static {
        try {
            NativeUtils.loadLibrary("recast");
        } catch (IOException e) {
            LOGGER.error(e.getMessage(), e);
        }
    }

调用JNI 接口

调用接口就像调用JAVA 普通的API一样,

GamiooJNI  jni=new GamiooJNI  ();
String version=jni.getVersion();

JNI中接下去需要探索的: 自定义对象的转换

查询内存泄漏

//18 是pid
jcmd 18 VM.native_memory detail scale=MB >leak.log

如果返回Native memory tracking is not enabled,那么就是在启动参数里忘记设置了 -XX:NativeMemoryTracking=detail

如果有内存泄漏,那么,你会在log文件里看到你写的JNI接口

NMT必须先通过VM启动参数中打开,不过要注意的是,打开NMT会带来5%-10%的性能损耗。

-XX:NativeMemoryTracking=[off | summary | detail]
# off: 默认关闭
# summary: 只统计各个分类的内存使用情况.
# detail: Collect memory usage by individual call sites.
例如:-XX:NativeMemoryTracking=detail

其中,reserved表示应用可用的内存大小,committed表示应用正在使用的内存大小

linux安装llvm

JNI数组操作

JNI使用注意与避免内存泄露总结

CentOS 7 升级安装 gcc7

cmake高版本安装及踩坑

CMake 指定gcc编译版本

python3和pip3安装和问题解决

JNI C++调用Java(一)

Premake文档

fastFFI 官宣开源,一款高效的Java跨语言通信框架

GraphScope analytics in Java:打破大规模图计算的跨语言障碍

JNI的替代者—使用JNA访问Java外部功能接口

用CLion实现本地方法并给java调用

JVM NATIVEMEMORYTRACKING ;JCMD PROCESS_ID VM.NATIVE_MEMORY;NATIVE MEMORY TRACKING IS NOT ENABLED

目录
相关文章
|
6月前
|
自然语言处理 供应链 Java
【Java】云HIS源码 提供“一个中心多个医院”平台
财务管理系统是医院经济核算的中心,连接门诊与住院的各个子系统,并对其自动生成各种报表;生成财务明细帐、分类帐,提供日、月、年统计报表;打印平衡表;按部门、科室、医生、收费员进行分别统计等。
67 2
|
Java
23、【支付模块开发】——Java对接支付宝步骤(沙箱环境)
1、下载导入项目 https://docs.open.alipay.com/54/104506 打开支付宝接口官网: image.png 我们下载Java版Demo 下载之后解压,然后我们用IDEA导入这个Demo项目~ image.
2282 0
|
2月前
|
安全 Java API
【本地与Java无缝对接】JDK 22外部函数和内存API:JNI终结者,性能与安全双提升!
【9月更文挑战第6天】JDK 22的外部函数和内存API无疑是Java编程语言发展史上的一个重要里程碑。它不仅解决了JNI的诸多局限和挑战,还为Java与本地代码的互操作提供了更加高效、安全和简洁的解决方案。随着FFM API的逐渐成熟和完善,我们有理由相信,Java将在更多领域展现出其强大的生命力和竞争力。让我们共同期待Java编程新纪元的到来!
87 11
|
2月前
|
安全 Java API
【性能与安全的双重飞跃】JDK 22外部函数与内存API:JNI的继任者,引领Java新潮流!
【9月更文挑战第7天】JDK 22外部函数与内存API的发布,标志着Java在性能与安全性方面实现了双重飞跃。作为JNI的继任者,这一新特性不仅简化了Java与本地代码的交互过程,还提升了程序的性能和安全性。我们有理由相信,在外部函数与内存API的引领下,Java将开启一个全新的编程时代,为开发者们带来更加高效、更加安全的编程体验。让我们共同期待Java在未来的辉煌成就!
62 11
|
1月前
|
缓存 监控 安全
电商行业中 API 接口的常见问题和解决方法?
电商行业中 API 接口面临的常见问题包括安全性问题、性能问题、兼容性问题、可靠性问题和可维护性问题。针对这些问题,可以采取相应的解决方法,如采用加密技术、优化数据库查询、制定统一的接口规范、进行容错处理、采用良好的代码结构等。
104 0
|
5月前
|
Java Android开发 C++
JNI中如何实现日志功能
JNI中如何实现日志功能
74 0
|
6月前
|
人工智能 监控 数据可视化
Java智慧工地云平台源码带APP SaaS模式 支持私有化部署和云部署
智慧工地是指应用智能技术和互联网手段对施工现场进行管理和监控的一种工地管理模式。它利用传感器、监控摄像头、人工智能、大数据等技术,实现对施工现场的实时监测、数据分析和智能决策,以提高工地的安全性、效率和质量(技术架构:微服务+Java+Spring Cloud +UniApp +MySql)。
125 4
|
6月前
|
运维 Serverless API
Serverless 应用引擎产品使用之在阿里函数中sdk可以被中层引用如何解决
阿里云Serverless 应用引擎(SAE)提供了完整的微服务应用生命周期管理能力,包括应用部署、服务治理、开发运维、资源管理等功能,并通过扩展功能支持多环境管理、API Gateway、事件驱动等高级应用场景,帮助企业快速构建、部署、运维和扩展微服务架构,实现Serverless化的应用部署与运维模式。以下是对SAE产品使用合集的概述,包括应用管理、服务治理、开发运维、资源管理等方面。
|
6月前
|
机器学习/深度学习 API 网络安全
视觉智能平台常见问题之服务部署在pdd的服务器上调用会报错如何解决
视觉智能平台是利用机器学习和图像处理技术,提供图像识别、视频分析等智能视觉服务的平台;本合集针对该平台在使用中遇到的常见问题进行了收集和解答,以帮助开发者和企业用户在整合和部署视觉智能解决方案时,能够更快地定位问题并找到有效的解决策略。
232 4
|
6月前
|
SQL 分布式计算 DataWorks
DataWorks常见问题之买多了一台独立集成资源组没找到释放入口如何解决
DataWorks是阿里云提供的一站式大数据开发与管理平台,支持数据集成、数据开发、数据治理等功能;在本汇总中,我们梳理了DataWorks产品在使用过程中经常遇到的问题及解答,以助用户在数据处理和分析工作中提高效率,降低难度。