C++项目通过JNI使用Java第三方jar包
最近在C++项目中碰到了需要使用第三方公司开发的Java jar包的问题,最后使用了JNI来解决。
参考了网络上不少的方法介绍, 大多数介绍JNI的文章讲的的都是Java通过JNI来调C的本地代码,其实这个也可以反过来用就是C的本地代码通过创建Java虚拟机调用java方法。下面贴一下解决实例C2JavaJym.c,注释不是很多。
#include <jni.h> #include <stdlib.h> #include <string.h> /*C字符串转JNI字符串*/ jstring stoJstring(JNIEnv* env, const char* pat) { jclass strClass = (*env)->FindClass(env, "Ljava/lang/String;"); jmethodID ctorID = (*env)->GetMethodID(env, strClass, "<init>", "([BLjava/lang/String;)V"); jbyteArray bytes = (*env)->NewByteArray(env, strlen(pat)); (*env)->SetByteArrayRegion(env, bytes, 0, strlen(pat), (jbyte*)pat); jstring encoding = (*env)->NewStringUTF(env, "utf-8"); return (jstring)(*env)->NewObject(env, strClass, ctorID, bytes, encoding); } /*JNI字符串转C字符串*/ char* jstringTostring(JNIEnv* env, jstring jstr) { char* rtn = NULL; jclass clsstring = (*env)->FindClass(env, "java/lang/String"); jstring strencode = (*env)->NewStringUTF(env, "utf-8"); jmethodID mid = (*env)->GetMethodID(env, clsstring, "getBytes", "(Ljava/lang/String;)[B"); jbyteArray barr= (jbyteArray)(*env)->CallObjectMethod(env, jstr, mid, strencode); jsize alen = (*env)->GetArrayLength(env, barr); jbyte* ba = (*env)->GetByteArrayElements(env, barr, JNI_FALSE); if (alen> 0) { rtn = (char*)malloc(alen + 1); memcpy(rtn, ba, alen); rtn[alen] = 0; } (*env)->ReleaseByteArrayElements(env, barr, ba, 0); return rtn; } /*C和Java的字符串类型不同需要在这里进行装换*/ int main(int argc, char **argv) { if(argc<7) { fprintf(stderr, "参数个数不足\n"); return -1; } int res; JavaVM *jvm; JNIEnv *env; JavaVMInitArgs vm_args; JavaVMOption options[3]; /*设置初始化参数*/ options[0].optionString = "-Djava.compiler=NONE"; options[1].optionString = "-Djava.class.path=.:../lib/jym.jar:../lib/codeutil.jar"; //这里指定了要使用的第三方Jar包 options[2].optionString = "-verbose:NONE"; //用于跟踪运行时的信息 /*版本号设置不能漏*/ vm_args.version=JNI_VERSION_1_4;//jdk版本目前有1.1,1.2,1.4 只要比你的jdk的版本低就可以 我用的是jdk1.5.0的版本 vm_args.nOptions = 3; vm_args.options = options; vm_args.ignoreUnrecognized = JNI_TRUE; res = JNI_CreateJavaVM(&jvm, (void**)&env, &vm_args); if (res < 0 || jvm == NULL || env == NULL) { fprintf(stderr, "Can't create Java VM\n"); return -1; } fprintf(stdout, "ok 调用JNI_CreateJavaVM创建虚拟机\n"); /*获取实例的类定义*/ jclass cls = (*env)->FindClass(env, "ptest/JymProduce"); //这里是jar包内JymProduce类的具体路径 if (cls == 0) { fprintf(stderr, "FindClass failed\n"); (*jvm)->DestroyJavaVM(jvm); fprintf(stdout, "Java VM destory.\n"); return -1; } fprintf(stdout, "ok 返回JAVA类的CLASS对象\n"); /*创建对象实例*/ jobject obj = (*env)->AllocObject(env, cls); if (obj == NULL) { fprintf(stderr, "AllocObject failed\n"); (*jvm)->DestroyJavaVM(jvm); fprintf(stdout, "Java VM destory.\n"); return -1; } fprintf(stdout, "ok 获取该类的实例\n"); /*获取构造函数,用于创建对象*/ /***1.1可用""作为构造函数, 1.2用"<init>"参数中不能有空格 "(Ljava/lang/String;)V"*/ jmethodID mid = (*env)->GetMethodID(env, cls, "getGertWord", "(Ljava/lang/String;Ljava/lang/String;JLjava/lang/String;Ljava/lang/String;)Ljava/lang/String;"); if (mid == 0) { fprintf(stderr, "GetMethodID failed\n"); (*jvm)->DestroyJavaVM(jvm); fprintf(stdout, "Java VM destory.\n"); return -1; } fprintf(stdout, "ok 获取类中的方法\n"); //构造参数并调用对象的方法 //发票代码 jstring fpdm = stoJstring(env, argv[2]); //发票号码 jstring fphm = stoJstring(env, argv[3]); //开票金额 jlong kpje = (jlong)atoi(argv[4]); //开票时间,格式为YYYYMMDD jstring kpsj = stoJstring(env, argv[5]); //行业分类代码 jstring hydm = stoJstring(env, argv[6]); char szJym[8]; memset(szJym, 0, sizeof(szJym)); jstring msg = (jstring) (*env)->CallObjectMethod(env, obj, mid, fpdm, fphm, kpje, kpsj, hydm); strcpy(szJym,jstringTostring( env, msg)); fprintf(stdout, szJym); fprintf(stdout, "\n"); fprintf(stdout, "ok Java返回参数\n"); PrintToFile(argv[1],szJym); /*捕捉异常*/ if ((*env)->ExceptionOccurred(env)) { (*env)->ExceptionDescribe(env); return -1; } /*销毁JAVA虚拟机*/ (*jvm)->DestroyJavaVM(jvm); fprintf(stdout, "Java VM destory.\n"); } int PrintToFile(const char* filename,const char* content) { FILE *fp; if((fp=fopen(filename,"w"))==NULL) return(-1); fputs(content, fp); fclose (fp); fflush(stdin) ; fflush(stdout) ; return 0; }
这里是将C调用Jar包获取jym的过程生成了一个C2JavaJym的可执行程序,通过命令行来调用生成包含jym的临时文件供C++项目来读取。
编译命令 gcc -o C2JavaJym C2JavaJym.c -I${JAVA_HOME}/include -I${JAVA_HOME}/include/linux -L${JAVA_HOME}/jre/lib/amd64/server -ljvm
char sCmd[101]; memset(sCmd, 0, sizeof(sCmd)); strcpy(sCmd, "C2JavaJym "); char sFile[21]; memset(sFile, 0, sizeof(sFile)); sprintf(sFile, "Jym%d.j", getpid() ); strcat(sCmd, sFile); strcat(sCmd, " "); strcat(sCmd, sFPDM); strcat(sCmd, " "); strcat(sCmd, sFPHM); strcat(sCmd, " "); strcat(sCmd, sFPJE); strcat(sCmd, " "); strcat(sCmd, sDate); strcat(sCmd, " "); strcat(sCmd, sHYDM); strcat(sCmd, " 1>/dev/null"); system(sCmd);
/*以上是调用生成校验码*/
char buf[101]; memset(buf, 0, sizeof(buf)); FILE* pf = fopen(sFile, "r"); if (pf!=NULL) { if (!feof(pf)) { fgets(buf, sizeof(buf)-1, pf); } else { tuxData.setRsp("4401","获取校验码失败!"); return false; } } else { tuxData.setRsp("4401","获取校验码失败!"); return false; } StrNCpy(sJYM,buf,7);
/*通过读取文件获取校验码*/
memset(sCmd, 0, sizeof(sCmd)); strcpy(sCmd, "rm "); strcat(sCmd, sFile); strcat(sCmd, " 1>/dev/null"); system(sCmd);
/*删除临时文件*/
也可以在命令行之间执行 C2JavaJym Jym2.j 235051102210 00002520 3456 20110330 04 来调用
这个方案比较土,不过还是有效的。我也试过将这个过程编译到CPP源代码中和tuxexdo服务端的pc文件中,但是都在创建虚拟机后,找不到指定的类,虚拟机的销毁也有问题,感觉是虚拟机创建的有问题。
另外还有2个方案只有构想还没有尝试过。一个是利用linux的消息机制,将Java虚拟机作为守护进程一样起在后台,C++项目往A消息队列上扔需要校验的数据,启动Java虚拟机的进程从这个A队列上获取数据,并计算出校验码,再扔到B队列上,C++项目再从这个B队列上获取算出来的校验码。这个过程可以减少Java虚拟机被频繁的创建和销毁,减少开销,但是如果并发量上来的话,等在B队列上获取校验码的C++进程比较多,怎么保证从B队列上取到的就是自己发送的内容的校验码是个问题。另一个也类似了就是利用socket来代替消息队列进行通讯。
不过实际项目中测试虚拟机的从创建到销毁的整个过程还是很快的,不像在我本机windows上那么慢,开销应该还是可以接受的。