整理項目文檔時, 忽然發現當年的一篇以前公司裏關於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