1. 程式人生 > >Java呼叫C/C++生成的動態庫函式

Java呼叫C/C++生成的動態庫函式

問題背景

之前的文章中,筆者將超長整數的四則運算利用C語言實現,因個人需要在web專案中使用該功能,

此時能想到的辦法是重寫實現過程,即利用Java重寫一遍C的實現過程

不談工作量的多少,單單是這個重寫的過程就讓我望而生畏,程式設計師最頭疼的一個是bug找不到,還有一個就是重複勞動。

解決思路:

一個很偶然的機會讓我看到了不需要重複勞動的希望--JNI

由於之前從來沒有跨語種合作一個工程的經驗,因此本篇將記錄個人的實踐過程

解決過程:

1、匯入JNI的標頭檔案jni.hjni_md.h

用“匯入”這個詞其實就是一個包含關係,在實際編碼過程中使用者可以選擇將兩個標頭檔案複製到本地專案工程目錄或者根本就不用做任何移動操作

該檔案位於JDK安裝路徑的include目錄下(其實不止一處,開發人員可以根據自己的系統winows/Linux搜尋)。

2、Java側編寫native方法

package org.demo.jni;
 
   public abstract class ICalcHBInteger {
       public native int init_HBInt();
       public native void input_HBInt(String in);
       public native String output_HBInt();
       public native String add_HBInt(String a1,String a2);
       public native String sub_HBInt(String s1,String s2);
       public native String mul_HBInt(String m1,String m2);
       public native String div_HBInt(String d1,String d2);
       public native String mod_HBInt(String mod1,String mod2);
       public native String powerMod_HBInt(String p1,String p2, String p3);
  }
這些方法的名稱自定義,對引數個數、型別沒有任何限制,但是一定要加上native關鍵字

(注:示例中用的是抽象類,這只是筆者個人習慣,不是必須的,同時在類中也可以定義其它非native 的方法,使用者自行斟酌)

3、利用javah命令生成.h標頭檔案

利用定義好的含有native方法類進行轉換,改轉換由javah命令完成

即:javah -jni org.demo.jni.ICalcHBInteger

正確執行後,會在當前執行命令的目錄下生成org_demo_jni_ICalcHBInteger.h檔案

檔案內容類似如下:

/*
   * Class:     org_demo_jni_ICalcHBInteger
   * Method:    output_HBInt
   * Signature: ()Ljava/lang/String;
   */
  JNIEXPORT jstring JNICALL Java_org_demo_jni_ICalcHBInteger_output_1HBInt
    (JNIEnv *, jobject);
4、在自定義的C檔案中實現該標頭檔案定義的方法

生成了標頭檔案後,就是對標頭檔案宣告的方法進行實現,此時因為在C側已經完成了主要的方法(筆者採用的是C實現超長整數),

所以只需要在自定義的C檔案中利用已經實現的方法來填充

Java_org_demo_jni_ICalcHBInteger_output_1HBInt
例如,筆者自定義的是HBIntegerVM.c檔案,而已經實現的超長整數檔名為HBInteger.c(其對應的標頭檔案是HBInteger.h)

HBIntegerVM.c內容如下:

JNIEXPORT jstring JNICALL Java_com_zsf_jni_ICalcHBInteger_output_1HBInt(
          JNIEnv *env, jobject obj) {
      int ret;
      ret = writeHBInt(&last, result);
      if (ret) {
          printf(
                  "Error in exec func [writeHBInt(&last, result.\n");
      }
      return nativeTojstring(env, result);
 }
需要說明的是 writeHBInt 方法就是 已經實現在HBInteger.c檔案中的。類比於使用者,就是已經完成的C語言方法。
5、生成動態庫,以備Java側可以呼叫

因筆者使用的是Linux系統,因此編譯生成的是.so檔案,如果是windows,則需要生成dll檔案。

生成動態庫檔案編譯命令:gcc -I/usr/lib/jvm/java-7-openjdk-i386/include -fPIC -shared -o libHBIntegerVM.so HBIntegerVM.c HBInteger.c

需要特別說明的是 -I(是i的大寫) 引數必須有,因為使用了jni.h標頭檔案,至於路徑則依賴於各個系統安裝的JDK版本和路徑,筆者的路徑是預設JDK安裝路徑

如果是整合環境則需要把 -I 的路徑值加入到編譯path裡,因為整合工具可能不同,這裡無法具體操作,請使用者自行查詢解決辦法。

如果一切順利,則生成的是 libHBIntegerVM.so 檔案,一定要 使用lib開頭,並且以 .so 結尾(Linux要求),windows沒有這個限制,只要.dll結尾即可。

6、將動態庫加入Java專案的JNI路徑中

因筆者使用的是eclipse,所以直接在工程的 Build path 處 添加了Native JNI 路徑

(即動態庫所在目錄的路徑,可以自定義一個路徑,將生成的庫檔案拷貝到指定路徑,也可以直接定位到生成的庫檔案所在路徑)

7、使用庫檔案中的方法

例如:

 public class TestJNI extends ICalcHBInteger{
   
       static{
           System.loadLibrary("HBIntegerVM");
       }
       /**
        * @param args
       */
      public static void main(String[] args) {
          // TODO Auto-generated method stub
          TestJNI test = new TestJNI();
          test.init_HBInt();
  
          String get;
  
          test.add_HBInt("12345678909876543210", "9876543210123456789");
          get = test.output_HBInt();
          System.out.println("return: " + get);
     }
}

需要說明的是:
System.loadLibrary("HBIntegerVM");
就是在載入動態庫,其中 HBIntegerVM 就是去掉庫檔案的頭和尾 即:lib 和 .so  如果是windows平臺的dll檔案,則只去掉尾,即 .dll 部分

還看到

test.output_HBInt();
就是之前程式碼中的native方法名

小結:

1、JNI技術目前應用不是很廣泛,究其原因是因為這樣做影響了呼叫層的效能(主要是互動時間長)

2、在非必要情況下(比如筆者這種情況就屬於個人為了偷懶),儘量不要使用該技術,以免帶來效能和除錯上的麻煩。

3、在使用過程中,如果涉及到傳遞引數,一定要注意引數型別的轉換,具體轉換方法,根據型別的不同,方法各異。

4、在進行工程分塊時,特別是針對積木型別結構的工程,該方式可以值得借鑑並加以利用,甚至優化。