JNI全流程实例使用总结
Posted 流子
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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
如果生成不出来的话,编译下该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的报错消失了。
类型互转
有很多类型的互转需要注意,类型互转问题转不好,还有内存泄漏的问题,这里会陆续总结:
/**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
分别在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
linux安装llvm
JNI数组操作
JNI使用注意与避免内存泄露总结
CentOS 7 升级安装 gcc7
cmake高版本安装及踩坑
CMake 指定gcc编译版本
python3和pip3安装和问题解决
JNI C++调用Java(一)
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
以上是关于JNI全流程实例使用总结的主要内容,如果未能解决你的问题,请参考以下文章