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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM