如果熟悉Android的朋友应该会有影响,Gradle会帮我们把Java和JNI进行捆绑,方便开发。而Java则会复杂一点,gradle的工作都需要我们来做。
环境
macos X86_64
java 1.8
模拟环境 springboot
包结构project
+-build
+-src
| +-main
| +-cpp
| +-java
| +-resources
| +JniLibs
|
+-CMakeLists.txt
+-pom.xml
可以看到cpp
中存放我们的c/c++代码,java中有我们的业务功能以及匹配cpp
的jni接口文件,resources
中的JniLibs
存放的是生成的静态、动态库文件。CMakeList.txt
则是我们c/c++代码的管理包,这里面需要配置本地的编译环境及生产静态、动态库的库管理。
最后build
是我们在打包动态链接库的时候生成的临时文件夹。
开发工具:IDEA、CLION
这里推荐Clion是方便开发c/c++代码。
准备JNI Java部分代码
首先是构建java开发环境及工程,这里就略过了。然后在java
文件夹中,创建与C沟通的JNI接口文件:public class PrintNative {
static {
System.loadLibrary("PrintNative");
}
public native void print();
}static
让其在加载类的时候同时加载本地的资源PrintNative
文件。但是目前还没有,我们后续在建。
接下来,需要对这个文件进行编译,转为头文件:cd project/src/main/java
javac com/xx/x/PrintNative.java
javah com.xx.x.PrintNative
完成后会生成对应的class文件和头文件,class文件可以删除掉,我们需要的是.h
的头文件。我们需要将头文件移动到src/main/cpp/include
文件夹下面。
构建C/C++项目
首先创建CMakeLists.txt
文件,根据上面的路径创建,填入下面内容:cmake_minimum_required(VERSION 3.4.1)
project(test)
add_definitions(-std=c++11)
set(java_home "xxxxx/jdk1.8.0_101.jdk/Contents/Home/")
set(jdk_home "xxxxxx/jdk1.8.0_101.jdk/Contents/Home/")
include_directories(${jdk_home}/include/
${jdk_home}/include/darwin/
src/main/cpp/include)
add_library(
PrintNative
SHARED
src/main/cpp/print-native.cpp)
target_link_libraries(PrintNative)
首先建立了cmake版本、项目的名称、c++版本。
然后创建了两个path路径,这里需要根据自己电脑来获取。
接下来需要指定include文件夹路径,这里我们需要三个,前两个是java中的头文件路径,最后一个是我们项目中的头文件路径。
最后就是建立当前库名称、动态/静态、cpp文件进行打包、链接,这里我们并没有其他的库添加到项目中,因此比较简单。
上面我们添加了一个cpp文件,因此我们就在这个目录下创建这个文件即可。
编写测试用的cpp文件
下面是我们创建的头文件/* DO NOT EDIT THIS FILE - it is machine generated */
#include <jni.h>
/* Header for class xx_xx_x_PrintNative */
#ifndef _Included_xx_xx_x_PrintNative
#define _Included_xx_xx_x_PrintNative
#ifdef __cplusplus
extern "C" {
#endif
/*
* Class: xx_xx_x_PrintNative
* Method: print
* Signature: (Ljava/lang/String;)Ljava/lang/String;
*/
JNIEXPORT void JNICALL Java_xx_xx_x_PrintNative_print
(JNIEnv *, jobject);
#ifdef __cplusplus
}
#endif
#endif
需要注意,在使用clion打开这个文件的时候,如果cmake配置错误,头文件第二行的jni.h
库引用失败导致编译报错。
接下来就需要将这个定义的jni接口函数,复制到cpp中,并配置好括号即可。#include "Java_xx_xx_xx_PrintNative.h"
JNIEXPORT void JNICALL Java_xx_xx_x_PrintNative_print
(JNIEnv *, jobject) {
}
这里引用了我们加入的头文件,以及对应的Jni接口函数。
接下来就可以进行编写工作。
打包动态库
完成工作后,我们就需要开始打包。
首先在CMakeLists.txt
文件的同级目录下,创建build
文件夹。
然后在build
文件的目录下运行下面的指令:cmake -DCMAKE_BUILD_TYPE=Release ..
cmake --build .
此时就会在该目录下生成动态链接库了,注意如果报错,部分问题可能是代码错误,部分问题可能是CMakeLists.txt
配置错误。
由于我的电脑是mac环境,因此生产的文件是libPrintNative.dylib
。因此将它复制到resources中的
JniLibs`即可。
java调用动态链接库
最后我们就需要在java调用了。
首先需要修改PrintNative
中的代码:public class PrintNative {
static {
URL url = PrintNative.class.getClassLoader().getResource("jniLibs/libPrintNative.dylib");
System.load(url.getPath());
}
这里看到是根据路径进行添加,此时是为了我们后续更好的兼容其他版本的架构,因为并不可能只支持x86(假如)也有arm等架构需要支持,此时就需要在jniLibs
文件夹下增加一层文件夹,命名就是架构名称,当服务启动时,首先需要检查当前架构名称,然后对该字符串进行拼接得到正确的链接库地址,进行链接即可。
修改完成后,就可以编写一个main来进行测试啦。
特殊bug
需要注意在运行时可能会出现下面的异常Caused by: java.lang.UnsatisfiedLinkError: /xxx/target/classes/lib/xxx.so: dlopen(/xxx/target/classes/lib/xxx.so, 1): no suitable image found. Did find:
/xxx/target/classes/lib/xxx.so: unknown file type, first eight bytes: 0xEF 0xBF 0xBD 0xEF 0xBF 0xBD 0xEF 0xBF
/xxx/target/classes/lib/xxx.so: unknown file type, first eight bytes: 0xEF 0xBF 0xBD 0xEF 0xBF 0xBD 0xEF 0xBF
这需要注意,使用idea编译代码后,由于将二进制文件从resource拷贝到输出路径可能会导致文件格式被破坏。
因此需要在pom下提示:<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.1</version>
<configuration>
<nonFilteredFileExtensions>
<nonFilteredFileExtension>so</nonFilteredFileExtension>
<nonFilteredFileExtension>dylib</nonFilteredFileExtension>
</nonFilteredFileExtensions>
</configuration>
</plugin>
</plugins>
</build>
重新编译即可。