700字范文,内容丰富有趣,生活中的好帮手!
700字范文 > JNI全流程实例使用总结

JNI全流程实例使用总结

时间:2021-12-26 21:27:19

相关推荐

JNI全流程实例使用总结

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

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

文章目录

生成头文件java ->*.h 工具 编辑C++ 文件类型互转 生成 DLL,SO 文件CMAKE 文件make_win64.bat & make_linux64.sh 文件 加载DLL,SO 文件调用JNI 接口查询内存泄漏

生成头文件

首先要编辑头文件对应的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 FileDescription: 生成C++类的头文件Program: $JDKPath$/bin/javahArguments: -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 __cplusplusextern "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 .3.3\bin\mingw\x86_64-w64-mingw32\include目录中,注意这两个头文件是一起放在MinGW的这个目录的,jni_md.h不需要另外创建一个win32目录来存放。完成后发现com_example_jni_JNIObject.h的报错消失了。

如果是在VS 环境下,则要在属性–>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++11set(CMAKE_CXX_STANDARD 11)# Require (at least) itset(CMAKE_CXX_STANDARD_REQUIRED ON)# Don't use e.g. GNU extension (like -std=gnu++11) for portabilityset(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 SConstructPATHS${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 build64cmake -G "Visual Studio 16 " -A x64 ..popdcmake --build build64 --config Releasemd Plugins\x86_64copy /Y build64\Release\RecastDll.dll Plugins\x86_64\recast.dllrmdir /S /Q build64pause

mkdir -p build_linux64 && cd build_linux64cmake ../cd ..cmake --build build_linux64 --config Releasecp build_linux64/libRecastDll.so Plugins/x86_64/recast.sorm -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-releaseyum 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 Packagescmake3.x86_643.17.5-1.el7epel

[root@VM-16-13-centos ~]# yum --showduplicates list cmake | expand Available Packagescmake.x86_642.8.12.2-2.el7base

wget /files/v3.22/cmake-3.22.6.tar.gzcd cmake-3.22.6yum install build-essential返回cmake-3.22.6的上层目录cd .. 执行chmod -R 777 cmake-3.22.6到目录cmake-3.22.6执行./bootstrap ,再执行make,再执行make install验证 cmake --versionln -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 下安装如下文件:

/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 是pidjcmd 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

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

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。