最近工作中,需要用到c++通过JNI去访问Java代码,在这里做下自己过程中遇到的问题,做个总结;
1.linux配置java jdk环境
第一步:去官网下载对应的linux包
https://www.oracle.com/java/technologies/javase-jdk11-downloads.html (这里我用的jdk11)
第二步:解压放在/usr/lib/下面
第三步:配置环境(/etc/profile)
export JAVA_HOME=/usr/lib/jdk-11.0.10
export CLASSPATH=.:${JAVA_HOME}/lib
export PATH=${JAVA_HOME}/bin:$PATH
这里因为下载的jdk11,因此不存在jre文件夹,不需要单独配置,下面是jdk8版本的配置
export JAVA_HOME=/usr/lib/jdk1.8.0_281
export JRE_HOME=${JAVA_HOME}/jre
export CLASSPATH=.:${JAVA_HOME}/lib/tools.jar:${JAVA_HOME}/lib/dt.jar:${JRE_HOME}/lib/
export PATH=${JAVA_HOME}/bin:${JRE_HOME}/bin:$PATH
配置就完成了,执行source /etc/profile 更新配置,可以进行命令检验一下是否成功
java -version
当打开新的终端出现找不到java command时,请记得执行source /etc/profile,就OK了。
2.c++ 编译过程
第一步:编译java class
javac *.java 编译所有的java文件
javac -cp core-4.1.1.jar:abi-4.1.1.jar:crypto-4.1.1.jar:rlp-4.1.1.jar:utils-4.1.1.jar:tuples-4.1.1.jar Web3Interface.java 带有第三库的编译
可能需要的java命令(demo指demo.class)
查看方法签名:javap -classpath ./ -s Demo
查询可执行程序依赖库: ldd 可执行程序
解压jar:jar -xvf project.jar
运行jar:java -jar project.jar
运行java: java demo
第二步:编写c++
流程:
配置jvm参数,初始化JVM
options[0].optionString = (char*)"-Djava.compiler=NONE";
options[1].optionString = (char*)"-Djava.class.path=.";
options[2].optionString = (char*)"-verbose:jni";
vm_args.version = JNI_VERSION_1_4;
vm_args.nOptions = 3;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
查找对应的类名findclass
获取方法id(构造函数)getmethodid
创建类对象 newObject
获取需要调用的函数方法id getmethodid
调用对象方法
jclass cls =env->FindClass("project/Web3Interface");
if(cls == NULL)
{
cout<<"find class is null"<<endl;
return str2jstring("null");
}
//Instantiate an object of that class
jmethodID mid = env->GetMethodID(cls,"<init>","()V");
if(mid == NULL)
{
cout<<"find mid is null"<<endl;
return str2jstring("null");
}
jobject jobj = env->NewObject(cls,mid);
if(jobj == NULL)
{
cout<<"find jobj is null"<<endl;
return str2jstring("null");
}
//get java transfor params
const char *pdCont = env->GetStringUTFChars(password,JNI_FALSE);
const char *pathCont = env->GetStringUTFChars(path,JNI_FALSE);
// //get method id
mid = env->GetMethodID(cls,"getdata","(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;");
if(mid == NULL)
{
cout<<"find mid is null"<<endl;
return str2jstring("null");
}
//call method
jstring result = (jstring)env->CallObjectMethod(jobj,mid,env->NewStringUTF(pdCont),env->NewStringUTF(pathCont));
return result;
调用jni接口过程中,是需要区分static 和非static的,如果函数是static修饰的则就改为getStaticMethodID()方法,其他类似;
注意事项:
① “-Djava.class.path= ”路径一定要配置正确,不然是找不到class名的;
②多路径时:windows 是;分割,unix 是:分割
例子:
//options[1].optionString = (char*)"-Djava.class.path=.:abi-4.1.1.jar:core-4.1.1.jar";
后面跟的是第三库的jar包路径
③java class 路径 要与findclass要匹配
jclass cls =env->FindClass("project/test"); 如: /代替. 如 packet ope.project ope/project/test
存放class和jar包的路径:c++ 执行程序test 当前 路径下创建project 文件夹(存放java.class和jar)
三者需要进行统一才能找得到class,否则找不到;(与“-Djava.class.path”也是一一对应的)
第三步:g++ 编译
jdk8版本
g++ testjni.cpp -o test -I/usr/lib/jdk1.8.0_281/include/ -I/usr/lib/jdk1.8.0_281/include/linux -I/usr/lib/jdk1.8.0_281/jre/lib/amd64/server/ -L/usr/lib/jdk1.8.0_281/jre/lib/amd64/ -L/usr/lib/jdk1.8.0_281/jre/lib/amd64/server/ -ljvm
jdk11版本
g++ test.cpp main.cpp -o test -I/usr/lib/jdk-11.0.10/include/ -I/usr/lib/jdk1.8.0_281/include/linux -L/usr/lib/jdk-11.0.10/lib/server/ -ljvm
注意:编译一定要有I/usr/lib/jdk-11.0.10/include/ -I/usr/lib/jdk1.8.0_281/include/linux这俩个路径
编译过程中可能会出现找不到libjvm.so的错误,此时可以通过下面方法去进行配置(/ect/profile添加):
export LD_LIBRARY_PATH=${LD_LIBRARY_PATH}:/usr/lib/jdk1.8.0_281/jre/lib/amd64
过程中也许可能会出现找不到libjava.so类似的错误,此时千万不要去复制相应的文件到对应的路径下,这是不行的,解决一个还有另外的,很多.so都要报错,最后全部复制了还不一定能成功;
原因分析:配置的环境的问题或者jdk问题,如果java -version 没有问题,配置也没问题,那么最好的办法就是删除原先的 重新下载 重新配置
3.问题
以上对于一个简单的测试demo,c++调用java代码是没有问题的。(不存在第三方库)
但目前遇到一个问题尚未解决,正在解决中:
问题 :运行env->CallObjectMethod(jobj, mid); 会奔溃的
原因:当需要使用第三方库时,java代码执行运行过程中有些库找不到,会出现奔溃的情况,一直未解决(尝试修改路径等),以及通过打包成jar包去调用,尝试了很久,还是未成功,希望大佬可以指导一下;
后面解决了此问题再补到文章后面。
如果不需要通过jni方式去调用java代码,可以使用比较笨的方式去实现,利用cmd 或者 system : java -jar project.jar 命令去拿数据(传参和拿返回数据)。哈哈哈 这是最简单最没技术含量的方式。