最近在研究C++中如何调用jar包。网上的JNI教程绝大部分都是讲的如何在Java里面调用C++编译好的so,而反过来的比较少。综合了一些网上的资料在自己的linux devbox上面把简单的C++调用jar的路径调通了,这里记录下步骤,文末会贴出过程中参考的文章链接。
配置环境
列下我这边devbox上的环境
- Debian GNU/Linux 9
- openjdk version "1.8.0_332"
- g++ (Debian 6.3.0-18+deb9u1) 6.3.0 20170516
安装Java的过程这里就不详细列出了,可以参考https://linuxize.com/post/install-java-on-debian-9/'
安装完Java以后打开 /etc/profile,在文件最后添加
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
export PATH=$JAVA_HOME/bin:$PATH
export CLASSPATH=.:$JAVA_HOME/lib/dt.jar:$JAVA_HOME/lib/tools.jar:$JAVA_HOME/jre/lib/rt.jar
然后运行 source /etc/profile 使配置文件立即生效。
查看下各个环境变量的值:
$ java -version
openjdk version "1.8.0_332"
OpenJDK Runtime Environment (build 1.8.0_332-8u332-ga-1~deb9u1-b09)
OpenJDK 64-Bit Server VM (build 25.332-b09, mixed mode)
$ echo $JAVA_HOME
/usr/lib/jvm/java-8-openjdk-amd64
编写Java程序并打成Jar包
接下来我们可以编写一个简单的Java程序。首先让我们先创建一个目录~/jni_example,后面所有的路径都会以这个目录为根目录。
~$ mkdir jni_example
~$ cd jni_example/
接下来我们在jni_example目录下面创建一个文件夹java_code用来存放我们接下来要编写的java文件。
~/jni_example$ mkdir java_code
~/jni_example$ cd java_code/
~/jni_example/java_code$
在~/jni_example/java_code这条路径下面,我们创建一系列路径以及最终的java文件。
~/jni_example/java_code$ mkdir -p com/example/javatest
~/jni_example/java_code$ cd com/example/javatest/
~/jni_example/java_code/com/example/javatest$ touch Test.java
在Test.java里面,我们创建一个Test类并添加一个静态方法和一个普通方法。
package com.example.javatest;
public class Test {
public Test(){
}
public static int add(int a, int b) {
return a + b;
}
public boolean reverse(boolean value) {
return !value;
}
}
然后我们编译这个java文件并且把编译好的.class文件封装成一个jar包。
~/jni_example/java_code/com/example/javatest$ javac Test.java
~/jni_example/java_code/com/example/javatest$ ls
Test.class Test.java
~/jni_example/java_code$ cd ~/jni_example/java_code/
~/jni_example/java_code$ ls
com
~/jni_example/java_code$ jar cvf test.jar com/example/javatest/Test.class
added manifest
adding: com/example/javatest/Test.class(in = 355) (out= 266)(deflated 25%)
~/jni_example/java_code$ ls
com test.jar
这样我们就把上面的java文件封装到了~/jni_example/java_code/test.jar这个jar包里面了。
编写C++程序并使用JNI调用jar包
接下来我们在~/jni_example这个目录下面创建一个存放cpp文件的路径
~/jni_example$ mkdir cpp_code
~/jni_example$ cd cpp_code/
~/jni_example/cpp_code$
然后我们在~/jni_example/cpp_code下创建一个文件 mytest.cpp,文件的示例代码如下
#include <stdio.h>
#include <iostream>
#include <jni.h>
using namespace std;
int main()
{
JavaVMOption options[2];
JNIEnv *env;
JavaVM *jvm;
JavaVMInitArgs vm_args;
long status;
jclass cls;
jmethodID mid;
jint jsum;
jboolean jnot;
jobject jobj;
options[0].optionString = (char*)"-Djava.compiler=NONE";
options[1].optionString = (char*)"-Djava.class.path=.:test.jar"; //在这里添加要使用的jar包
vm_args.version = JNI_VERSION_1_8;
vm_args.nOptions = 2;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
status = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
if (status != JNI_ERR) {
cout << "Succeeded creating JVM " << endl;
cls = env->FindClass("com/example/javatest/Test"); // 这里是jar包内Test类的具体路径
if (cls != 0) {
cout << "Succeeded finding target Java class" << endl;
// 调用构造函数创建对象
mid = env->GetMethodID(cls,"<init>","()V");
if (mid != 0) {
jobj = env->NewObject(cls, mid);
// 如果想要捕获异常,可以使用注释中的语句
/*if (env->ExceptionOccurred()) {
env->ExceptionDescribe();
env->ExceptionClear();
return 0;
}*/
cout << "Succeeded creating object using java constructor" << endl;
}
// 调用add函数
mid = env->GetStaticMethodID(cls, "add", "(II)I");
if (mid != 0) {
jsum = env->CallStaticIntMethod(cls, mid, 100, 28);
cout << "Result of calling add method is " << jsum << endl;
}
// 调用reverse函数
mid = env->GetMethodID(cls, "reverse","(Z)Z");
if(mid != 0) {
jnot = env->CallBooleanMethod(jobj, mid, 1);
cout << "Result of calling reverse method is " << jnot << endl;
}
}
else{
fprintf(stderr, "FindClass failed\n");
}
jvm->DestroyJavaVM();
fprintf(stdout, "Java VM destory.\n");
return 0;
}
else{
cout << "Failed creating JVM " << endl;
return -1;
}
}
然后我们把之前编译好的jar包copy到当前目录下(这一步其实不用做,但是我这边不方便在上面的java classpath中引入绝对路径,所以简单点就把这个jar包拷到c++文件的目录下。
~/jni_example/cpp_code$ cp ~/jni_example/java_code/test.jar .
~/jni_example/cpp_code$ ls
mytest.cpp test.jar
在编译c++文件之前,我们还需要设置另一个环境变量
~/jni_example/cpp_code$ export LD_LIBRARY_PATH=$JAVA_HOME/jre/lib/amd64/server/
然后我们用下面的命令编译c++程序
~/jni_example/cpp_code$ g++ -o mytest mytest.cpp -I ${JAVA_HOME}/include -I ${JAVA_HOME}/include/linux -L ${JAVA_HOME}/jre/lib/amd64/server -ljvm
运行C++程序
完成上面的步骤之后,我们就可以运行编译好的C++程序了
~/jni_example/cpp_code$ ./mytest
Succeeded creating JVM
Succeeded finding target Java class
Succeeded creating object using java constructor
Result of calling add method is 128
Result of calling reverse method is
Java VM destory.
引用
这篇简易教程中参考了下面的一些blog。如果想要了解上面C++文件中一些参数的意义,或者想要从命令行中通过C++传递参数给Java方法,都可以参考我列出来的这些文章。
linux下通过JNI用C/C++中调用JAVA类_qq_14898543的博客-CSDN博客
基于jni实现c/c++调用jar包_vah101的博客-CSDN博客_c++ jni调用jar
Linux上c++通过JNI调用java代码笔记_Tjmies的博客-CSDN博客
如何在c++中调用java代码(转载) - Java世界 - BlogJava
C++项目通过JNI使用Java第三方jar包_Louka的博客-CSDN博客
C/C++如何调用Java_笑看红尘花落一梦的博客-CSDN博客_c++调用java
基本JNI调用技术(c/c++与java互调)-阿里云开发者社区
Calling Java from C++ with JNI - CodeProject
PS:
1. 在C++中,如果传入的是jobject需要通过GetObjectClass(jobject)获取jclass, 而如果没有jobject需要通过FindClass("javapackage/class") 来获类的jclass
2.对于java方法的类型,可以通过javap -p -s [package].[classname]来获得。比如:
~/jni_example/java_code$ javap -p -s com.example.javatest.Test
Compiled from "Test.java"
public class com.example.javatest.Test {
public com.example.javatest.Test();
descriptor: ()V
public static int add(int, int);
descriptor: (II)I
public boolean reverse(boolean);
descriptor: (Z)Z
}