虽然java用了很长的一段时间,但一直没有机会去碰JNI的东西。最近终于有项目需要用到JNI,并且我们是要用c++调用java代码。为什么我们需要这么做呢?
1. 第三方的功能比较符合我们想要的库是用java实现的;
2. 它是开源的并且能够用于商业用途;
3. 上层的应用对时间、效率又有一定的要求。
其实,上面三点需求在一定程度上来讲是相互违背的。讲究效率,那么必然用c/c++,所以,我们在能够控制的代码用c/c++,但不能控制的第三方库的实现用的是java,效率必然不会太高,但就是因为免费,我们也只能才去这种蹩脚的实现方法了。
不管怎么样,还是来个例子:
#include <iostream>
#include <string.h>
#include <jni.h>
#include <stdlib.h>
using namespace std;
#define PATH_SEPARATOR ';' /* define it to be ':' on Solaris */
#define USER_CLASSPATH1 "imported1.jar" /* where Prog.class is */
#define USER_CLASSPATH2 "imported2.jar" // imported2.jar 引用了imported1.jar中的类Prog.Java
#define TEST1 "edu/Test1" //edu/Test1 located in imported1.jar
#define TEST2 "edu/Test2" //edu/Test2 located in imported2.jar
JNIEnv *env;
JavaVM *jvm;
jint res;
int initJVM() {
#ifdef JNI_VERSION_1_2
JavaVMInitArgs vm_args;
JavaVMOption options[3];
options[0].optionString = "-Xms4096m";
options[1].optionString = "-Xmx8000m";
options[2].optionString ="-Djava.class.path=" USER_CLASSPATH1 ":" USER_CLASSPATH2; //import multiple jar file
//options[2].optionString = "-Djava.class.path = <path_to_my_java_class>:<path_to_my_jar_file>";
//options[3].optionString = "-Djava.library.path=" CLASSPATH; //The library path is the path for finding native libraries, not JAR files
vm_args.version = 0x00010002;
vm_args.nOptions = 3;
vm_args.options = options;
vm_args.ignoreUnrecognized = JNI_TRUE;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args);
#else
JDK1_1InitArgs vm_args;
char classpath[1024];
vm_args.version = 0x00010001;
JNI_GetDefaultJavaVMInitArgs(&vm_args);
/* Append USER_CLASSPATH to the default system class path */
sprintf(classpath, "%s%c%s",
vm_args.classpath, PATH_SEPARATOR, USER_CLASSPATH);
vm_args.classpath = classpath;
/* Create the Java VM */
res = JNI_CreateJavaVM(&jvm, &env, &vm_args);
#endif /* JNI_VERSION_1_2 */
//if(res==JNI_OK) cout<<"init VM Successfully!"<<endl;
return res;
}
void closeJVM() {
jvm->DestroyJavaVM();
//cout<<"Destory VM Successfully!"<<endl;
}
//test default java class
jstring JNU_NewStringNative(const char *str)
{
jclass strClass = env->FindClass("java/lang/String");
//if(strClass) {
// cout<<"strClass Successfully!"<<endl;
// }
jmethodID ctorID = env->GetMethodID(strClass, "<init>", "([BLjava/lang/String;)V");
jstring encoding = env->NewStringUTF("UTF-8");
jbyteArray bytes = env->NewByteArray(strlen(str));
env->SetByteArrayRegion(bytes, 0, strlen(str), (jbyte*)str);
return (jstring)env->NewObject(strClass, ctorID, bytes, encoding);
}
const char *Test2(char* input) {
jclass tagEngine;
jobject tagObject;
tagEngine = env->FindClass(TEST2);
if(env->ExceptionCheck()) {
return 0;
}
//get construct method ID
jmethodID initMethod = env->GetMethodID(tagEngine, "<init>", "()V");
//get tag method ID. If Test2 class has "String tag(String s)" method, return non-NULL; or return NULL
jmethodID tagMethod = env->GetMethodID(tagEngine, "tag", "(Ljava/lang/String;)Ljava/lang/String;");
if(initMethod && tagMethod) {
//new Test2 object
tagObject = env->NewObject(tagEngine,initMethod,path);
if(tagObject) {
// call tag method
jstring tagsOutput = (jstring)env->CallObjectMethod(tagObject,tagMethod,inputString);
// exception handing
if(env->ExceptionCheck()) return 0;
const jbyte *str = (jbyte *)(env)->GetStringUTFChars(tagsOutput, NULL);
return ((char*)str);
}
}
return 0;
}
int main() {
const char* resint;
if(JNI_OK==initJVM()) {
resint = Test2("这个例子不错。");//trove_version();//
if(resint)
printf("%s\n", resint);
else
printf("error\n");
closeJVM();
}
return 0;
}
针对JNI的初始化,可以参考:JNI: API的调用
针对JNI的设计,例如异常处理、对象引用,可以参考:JNI使用:设计综述
有了代码,离成功的编译还有很远的一段距离。其实使用JNI最难的问题是JVM参数的设置。下面列出了我的编译命令:
g++ -I/usr/java/latest/include/ -I/usr/java/latest/include/linux -L/usr/java/latest/jre/lib/amd64/ -L/usr/java/latest/jre/lib/amd64/server/ -Wl,-rpath,/usr/java/latest/jre/lib/amd64/server -ljvm -lstdc++ -o JarTest JarTest.cpp
上面的一长串是在终端输入(我用的centos系统,网上大部分都是针对window系统,由于window系统不需要管JVM的路径,我花了好多时间才找到JVM的库)。
一条一条来分析:
1、我的JAVA_HOME目录为:usr/java/latest/;
2、 -I 是添加头文件,所以我引入的头文件目录为/usr/java/latest/include/ 和/usr/java/latest/include/linux;其实这里是需要<jni.h>文件。注意:openjdk是没有include目录的,一定要从oracle的官网下载java jdk。
3、-L是添加库文件,注意我引入的库为:/usr/java/latest/jre/lib/amd64/ 和 /usr/java/latest/jre/lib/amd64/server/ ,其实这里需要的是libjvm.so文件。注意两点:
- 如果没有引入这两个库文件夹,那么编译是会出现编译错误,并且error提示为:"/usr/bin/ld: cannot find -lXXX",原因就是没有找到libjvm.so;
- 只有/usr/java/latest/jre/lib/amd64/server/里面采用libjvm.so,而/usr/java/latest/jre/lib/amd64/client/里面没有。而大部分安装的java都是client版本的,所以要注意一下。
4、 Wl是什么意思,可以用g++ --help直到Wl参数的作用,注意它的参数采用","分隔;
5、-ljvm、-lstdc++、-o就不多说了。
参考文献:
关于JNI的函数调用:参考:JNI: API的调用
关于JNI的异常处理,参考:JNI使用:设计综述