上面一篇我们介绍了JNI的基本使用,生成的DLL文件为Windows系统上动态连接库文件。但是在linux系统上,库文件分为静态和动态两种:
(1)、静态库:静态库(.a 文件),确保链接时指定了静态库文件。
(2)、动态库:动态库(.so 文件),确保在运行时库文件在LD_LIBRARY_PATH 中。
不论是哪一种库文件(.dll,.so,.a等),都相当于Java的jar包,可以通过JNI方式连接调用其内部的方法。
本篇我们介绍下如何生成linux上的.so动态库文件,且使用JNI调用库文件.so包内的方法。
一、vsCode安装ssh插件
本例的vsCode软件是如下的图标,和上一篇中的软件不一致哦。
安装ssh插件的可方便我们在本地编辑器内直接操作远程服务器上的文件,因为.so库文件只能运行在linux服务器上,不然我们每调整一次代码都要在发送到服务器上执行就非常麻烦,效率非常低了。
安装ssh插件步骤:
点击vscode的插件,搜索ssh,安装如图的Remote-SSH插件。
安装完成后,会在下面出来一个小电脑的标识,可以点击进去,输入ip,用户名和密码等进行连接。如下图
连接完成后,可以在第一个菜单下选择和编辑文件。这里已经把engine.zip进行解压过了
二、引擎库文件解读
常见的引擎服务代码结构如下(C++版本),常见的目录结构如下,这里仅以我常见的分享下:
说明:
bin目录是引擎库文件,里面包含一些.so或.a文件
conf目录是配置文件目录,主要放配置文件,如.cfu
resource目录下是模型文件,里面放.bin或.isf等模型文件
Include目录下是接口文件,里面放.h头文件,(雷士java的API接口定义)
thirdparty是引用的三方库文件,里面也是一些.so或.a文件(这里的so文件相当于引用的三方jar包)
三、C++调用验证
1、验证C++基本环境是否可用
创建目录cTset,在目录下创建test1.c文件,编写如下的c++代码
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <cstring>
int addTest(int a, int b) {
std::cout << a << " + " << b << " = " << (a + b) << std::endl;
return a + b;
}
int main(){
printf("hello weisian's engine!\n");
int result = addTest(1, 2);
printf("Result: %d\n", result);
return 0;
}
执行编译:
g++ ./cTest/test1.c -o ./cTest/test1
编译完成后,会产生test1的文件。
运行文件:
./cTest/test1
可以看到控制台正常打印了信息,说明C++环境正常
2、验证引擎服务是否可用
(1)、查看C++的接口
查看c++的API接口,即include下的.h的头文件。这里一般是开放给我们实现的接口,我们要按照这里的接口定义去写实现类的.c文件。
如下的Aaa_engine.h文件,这里面提供了引擎初始化和逆初始化(逆初始化即销毁)的方法。
(2)、编写C文件,调用C++的API接口
创建test2.c文件,编写c++代码
(代码逻辑类似java,引入目标头文件Aaa_engine.h,之后在main方法中调用这个.h中的初始化和逆初始化方法)
代码示例:
#include <stdio.h>
#include <stdlib.h>
#include <iostream>
#include <string>
#include <cstring>
#include "Aaa_engine.h"
int main(){
// 初始化引擎
printf("giti ae engine ...... \n");
const char* engineDir = "/data/projects/phm/zhangwei/engine/bin";
const char* confDir = "/data/projects/phm/zhangwei/engine/conf";
int a = AaaInitialize(engineDir, confDir);
printf("giti ae engine AaaInitialize = %d \n", a);
//逆向初始化
int b = AaaUninitialize();
printf("giti ae engine AaaUninitialize = %d \n", b);
}
(3)、编译C文件,生成可执行文件
编译test2.c源文件,执行命令如下:
g++ ./cTest/test2.c -o ./cTest/test2
这里报错,因为Aaa_engine不是系统的头文件,所以找不到,这里我们编译时需要在指定下头文件地址,修改编译命令如下:
g++ -I./include/ifly_aaa -o ./cTest/test2 ./cTest/test2.c
再次执行编译,发现头文件是不报错说明找到了,但是里面的初始化和逆初始化的方法又找不到了,这里需要在引入引擎库库文件,命令如下
g++ -I./include/ifly_aaa -o ./cTest/test2 ./cTest/test2.c -L./bin -lIflyAaaEngineI
说明(-L./bin指定了文件的目录;-l指定so库文件时不要前面的lib也不要最后的.so;或在-l:全名称(如:-l:libIflyAaaEngineI.so))
此时编译都成功。
(4)、运行可执行文件
运行可执行文件:
./cTest/test2
也出现了执行结果,但是结果编码是100000013
在错误码文件中可以看到应该是权限或证书相关不足的问题。
找对方要了一下有权限的证书文件(一般是过期或者ip端口不适配的原因),替换配置文件下的证书文件:
再次执行
./cTest/test2
发现返回码都是0,说明调用成功了。
四、JNI调用
1、编写java的引擎调用接口文件
Java的写法逻辑大致如下,先引入目标so库文件(不要前面的lib和后面的.so,这里的文件需要和之后创建的so文件名称保持一致),之后在定义native方法,参考模型include文件夹下的.h文件定义。这里我们编写TestZwEngine.java文件如下:
package cn.nivic.jni;
public class TestZwEngine {
static {
System.out.println("load ZW Engine test");
// 引入so库文件(windows上是.dll文件,linux是.so或.a文件)
System.loadLibrary("GitiAaaEngine03");
}
/**
* 初始话引擎
* @param enginePath
* @param confDir
* @return
*/
public static native int AaaInitialize(String enginePath, String confDir);
}
2、通过javah命令,生成.h头文件
执行maven package生成TestZwEngine.class文件
打开target/classes目录,同目录下打开cmd黑窗口,执行
javah cn.nivic.jni.TestZwEngine
会在当前目录下生成cn_nivic_jni_TestZwEngine.h的头文件
3、编写.c文件,实现.h头文件
在linux上,创建java目录,将cn_nivic_jni_TestZwEngine.h头文件放进去
同级目录下创建相同名称的.c文件,如cn_nivic_jni_TestZwEngine.c
编写cn_nivic_jni_TestZwEngine.c文件,使用include下的api实现cn_nivic_jni_TestZwEngine.h的接口
代码示例:
#include <jni.h> // 必须要带的
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "Aaa_engine.h" // 引擎库提供的实现api
#include "Aaa_type.h" // 引擎库提供的一些类型枚举
#include "cn_nivic_jni_TestZwEngine.h" // java的api接口,需要实现
JNIEXPORT jint JNICALL Java_cn_nivic_jni_TestZwEngine_AaaInitialize(JNIEnv *env, jclass jc, jstring enginePath, jstring confPath){ // 方法定义直接从cn_nivic_jni_TestZwEngine.h的接口中抄过来,然后补充下参数,方法内的部分需要了解c++语法编写下
int ret = 0;
if (enginePath == NULL)
{
// 抛出异常
jclass exceptionClass = env->FindClass("java/lang/NullPointerException");
if (exceptionClass != NULL) {
env->ThrowNew(exceptionClass, "enginePath not null");
}
return 1; // 返回一个空的jstring对象
}
//引擎初始化
const char* engineDir = env->GetStringUTFChars(enginePath, NULL); // 获取java参数
const char* confDir = env->GetStringUTFChars(confPath, NULL); // 获取java参数
printf("zw engine engineDir = %s \n", engineDir);
printf("zw engine confDir = %s \n", confDir);
ret = AaaInitialize(engineDir, confDir);
printf("giti ae engine AaaInitialize = %d \n", ret);
if(0 != ret)
{
printf("AaaInitialize failed %d\n",ret);
return ret;
}
return ret;
}
4、生成新的库文件so
(1)、引入jni.h
因为引入了jni.h,这个是jdk自带的。这里生成.so文件需要在linux上执行,所以我们组要找一下linux服务器上的jni.h文件路径
查找jni.h的命令:
find / -name jni.h
找到大致如下:
/data/software/java/java-se-8u41-ri/include/jni.h
(2)、生成.so库文件
执行命令:
g++ -shared -fPIC -I/data/software/java/java-se-8u41-ri/include -I/data/software/java/java-se-8u41-ri/include/linux -I…/include/ifly_aaa -o libGitiAaaEngine03.so ./cn_nivic_jni_TestZwEngine.c -L…/bin -l:libIflyAaaEngineI.so.2.0.6.1
解释(通过-I指定了三处头文件引入路径,前两个是jni需要的,第三个是引擎定义的;在最后-L指定依赖的库;-o指定输出文件的名称,这里需要和第一步java代码中的System.loadLibrary(“GitiAaaEngine03”);名称一致)
5、运行java的main方法验证
在第一步的TestZwEngine.java类中,我们添加一下main方法,
package cn.nivic.jni;
public class TestZwEngine {
static {
System.out.println("load ZW Engine test");
// 引入so库文件(windows上是.dll文件,linux是.so或.a文件)
System.loadLibrary("GitiAaaEngine03");
}
/**
* 初始话引擎
* @param enginePath
* @param confDir
* @return
*/
public static native int AaaInitialize(String enginePath, String confDir);
public static void main(String[] args) {
//引擎路径
String enginePath = "/data/projects/phm/zhangwei/engine/bin";
System.out.println("enginePath:" + enginePath);
//引擎配置文件路径
String confPath = "/data/projects/phm/zhangwei/engine/conf";
System.out.println("confPath:" + confPath);
int initResult = TestZwEngine.AaaInitialize(enginePath, confPath);
System.out.println("初始化引擎结果11:" + initResult);
}
}
使用idea生成一个可运行的jar文件(如:2.jar),上传到相同位置
运行jar,执行:
java -Djava.library.path=./ -cp bin -jar 2.jar
java自己找库方法的时候都是在jdk相关目录或者环境变量配置的java目录下去找,这里libGitiAaaEngine03.so在当前文件下,所以就找不到,需要我们自己在指定一下java.library.path。
可以看到java日志,c++日志,执行结果都正常。
学海无涯苦作舟!!!