JNI編程注意事項

整理項目文檔時, 忽然發現當年的一篇以前公司裏關於JNI編程 的標準化文檔。做爲收藏,就貼在這裏吧。

注:關於JNI, 現在好像有一個OpenSource項目jace可以幫助進行JNI的開發。另外, 推薦IBM Developerworks網站上的一個教程《用jni進行java編程》


JNI編程注意事項:

1、  JNI的函數聲明:

JNI函數聲明方法爲JAVA_PackageName_ClassName_FuncName,其中特別要注意的是如果native方法所在的java類位於某個package中時,必須在函數聲明中加上packageName,否則在運行java程序時將出現:java.lang.UnsatisfiedLinkError錯誤。同時調用JNI方法:FindClass時需要使用PackageName/ClassName方式,否則會找不到相應Class

2、  編譯後DLL存放的位置:

Windows平臺下Dll必須可以存放在系統環境變量Path指示的目錄中,或者當前目錄中

3、  運行javah時的注意事項:

在調用javah時如果目標類處在一個包中,則必須加上包名,比如javah –jin package.ClassName,這樣才能產生可以被vm正確識別的頭文件

4、  簽名

在調用jni函數GetMethodID或者GetFieldID時需要獲得簽名,簽名可以通過javap –s classname來獲得

5、  使用jni接口重新封裝各服務器客戶端api(組裝代理api)時的注意事項:

a)         java中封裝類實現時的注意事項:原api在java中必須用一個類進行封裝,我們建議將所有這些api在java中定義爲static方式,並創建一個private的類的構造函數以阻止引用該類的java代碼中實例化該類的企圖。通過這種方法我們可以使在一個進程中只有一個api集合在工作,當然我們也可以在該api封裝類中提供一種機制創建多個api集合,並在類內部管理這些api集合的初始化和析構。這種方式將減少外部類使用該封裝類時的複雜度,也給提高了系統資源的利用率。參考如下代碼:

class EncapslateClass{

          private EncapslateClass() {}

          private static boolean bIsInit = false;

          public static long Init() {

if (!bIsInit){

return API_Init();

}

return ok;

                                    }

          public static long Destroy() {

if (bIsInit){

long lRet;

lRet = API_Destroy();

Thread.sleep(1000);                                 //很重要,java vm退出時不會等待API集合中的線程正常退出,我們必須保證在VM退出前自己的API集合全部正常退出,否則在退出時可能會出錯。該邏輯也可以放在JNI代理API集合中實現。

}

return ok;

                                    }

          private static native long API_Init();

          private static native long API_Destroy();

         

          public static native long API_Func1();

          public static native long API_Func2();

          static{

                      System.loadLibrary(“xxx”);

}

protected void finalize(){

   Destroy();                     //永遠不會執行到

}

                        }

上面的例子展示了我們的一種實現方式,外部類通過調用封裝類的Init()和Destroy()來初始化和析構,而不是直接調用本地方法API_Init()和API_Destroy(),通過在Init()和Destroy()中加入更多的邏輯,我們甚至可以初始化和析構多個API集合,但一般情況下以上用法已經夠用了。採用該方法時需要注意,我們必須在進程退出前顯式調用Destroy()來進行析構,原因是該類無法實例化,因此gc不會正確處理該類的析構,必須由我們自己來進行,一般可以在我們的控制類的finalize()函數中顯式調用該Destroy()。同時請參考上例中Destroy()函數中的註釋

b)        返回值的處理:在我公司大量api中使用返回值(一般爲long)指出該api執行是否成功,如果失敗則該返回值表示出錯代碼,而函數正常執行情況下的輸出參數採用傳引用(或者指針)方式返回。比如 long FuncA(long inparam1, long inparam2, long& outparam1)函數中函數返回值指出FuncA是否執行成功,如果執行成功則通過outparam1返回結果。這種做法在C++編程時是很常見的,但在jni中希望用同樣方式與JVM進行交互是行不通。主要原因是函數參數中如果是簡單類型(int,short,long,char,byte等非對象類型)jni不支持傳引用方式,換句話說原來我們的api通過傳引用方式返回簡單類型的函數值在jni中是行不通的(雖然java函數參數都是採用傳引用方式)。下面有一個例子:

---C++ API:long FuncA(long inparam1, long inparam2, long& outparam1)


 


---Java中的封裝:

   public class SampleNativeCall{

               public static long FuncA(long inparam1, long inparam2, long outparam1);

               static{

                           System.loadLibrary(“abc”);

               }

   }


 


---運行javah –jni SampleNativeCall後的結果

jlong JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2, jlong outparam1)

{

   …

   if  ((lRet = FuncA(inparam1 , inparam2, outparam1)) != OK_VALUE){

               return lRet

}

return lRet;

}


 


上例中希望java中FuncA與C++中FuncA保持一致的接口簡化代理api實現的過程,但是由於jlong指示簡單的傳值過程,我們無法通過outparam1將結果返回給jvm。

                        可以有兩種方式解決以上問題:

1、  將返回的簡單類型封裝成類,並在類中提供Set方式來設置該簡單變量(或是提供pulic成員變量直接賦值),jni代理api函數還是通過返回值方式返回錯誤,如下:

---C++ API:long FuncA(long inparam1, long inparam2, long& outparam1)


 


---Java中的封裝:

   public class SampleNativeCall{

               public static class FuncAOutput{

                           private long outparam;

                           public long Get() { return outparam; }

                           public void Set(long inOutparam) {outparam = inOutparam; }

               }

public static long FuncA(long inparam1, long inparam2, FuncAOutput outparam1);

               static{

                           System.loadLibrary(“abc”);

               }

   }


 


---運行javah –jni SampleNativeCall後的結果

jlong JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2, jobject outparam1)

{

   …

   if  ((lRet = FuncA(inparam1 , inparam2, out1)) != OK_VALUE){

               return lRet

}

if (outparam1 == NULL) {…}

   jclass cls = env->GetObjectClass(outparam1);

   if (cls ==NULL)

   { … }

   jmethodID mid = env->GetMethodID(cls, “Set”, “J”);

   if (mid == NULL)

   {…}

   env->CallVoidMethod(outparam1, mid, out1);


 


   …

}

---java中調用方式

public class SampleClass{

   static public main(String[] argvs){

   {

               …

SampleNativeCall.FuncAOutput output = new SampleNativeCall.FuncAOutput(); //很重要,必須先創建對象

SampleNativeCall.FuncA(inparam1, inparam2, output);

System.out.println(“output = ” + output.Get());

}

}

以上方式中在返回值的處理上,以及返回值的取值可以與原api保持一致,接口參數個數也保持一致,但返回參數如果是簡單類型則需封裝成類(在例子中採用內部類方式,正式實現時也建議採用這種方式,簡化代碼)。如果原api中返回值通過一個結構返回,則可以用類似的方式用一個類封裝該結構,並通過調用jni方式設置該類中成員變量的值。


 


2、  將結果封裝成類通過函數返回值返回,函數的出錯處理採用“異常”處理機制,即在jni中將錯誤throw出來而不是通過函數返回值返回,如下例

---C++ API:long FuncA(long inparam1, long inparam2, long& outparam1)


 


---Java中的封裝:

   public class SampleNativeCall{

               public class FuncAOutput{

                           private long outparam;

                           public long Get() { return outparam; }

                           public void Set(long inOutparam) {outparam = inOutparam; }

               }

public FuncAOutput FuncA(long inparam1, long inparam2) throws MyException;

               static{

                           System.loadLibrary(“abc”);

               }

   }


 


---運行javah –jni SampleNativeCall後的結果

jobject JAVA_SampleNativeCall_FuncA(JNIEnv *jEnv, jobject thisObject, jlong inparam1, jlong inparam2)

{

   …

   if  ((lRet = FuncA(inparam1 , inparam2, out1)) != OK_VALUE){

               return lRet

}

jclass cls = env->FindClass(“SampleNativeCall.FuncAOutput”);

if (cls == NULL)

{

…    

throw;

}

jobject obj = env->AllocObject(cls);

if (obj == NULL)

{

…    

throw;

}

jmethodID mid = env->GetMethodID(cls, “Set”, “J”);

   if (mid == NULL)

{

…    

throw;

}

   env->CallVoidMethod(outparam1, mid, out1);

   …

}

---java中調用方式

public class SampleClass{

   static public main(String[] argvs){

   {

               …

//很重要,必須先創建對象

try{

SampleNativeCall.FuncAOutput output = SampleNativeCall.FuncA(inparam1, inparam2);

}

                                                            catch ( xxx ){

                                                                        …

                                                                        return;

                                                            }

System.out.println(“output = ” + output.Get());

}

}

我們推薦採用第二種方式,此種方式在輸入輸出參數的安排上更爲清晰合理,而且採用的異常處理機制更符合java的思路。在此種方式裏jni通過AllocObject分配的對象同樣接受JVM中gc的管理,在沒有引用時將被正確釋放(已經經過驗正)

c)        原api中大量預定義宏和常量處理方式:通過一個類來封裝這些東西,這些宏和常量全部設置爲static final方式。


 

 

 

 

本文來自CSDN博客,轉載請標明出處:http://blog.csdn.net/jackiejuju/archive/2005/04/15/348522.aspx

發佈了71 篇原創文章 · 獲贊 30 · 訪問量 60萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章