最近在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上那麼慢,開銷應該還是可以接受的。