<rt id="bn8ez"></rt>
<label id="bn8ez"></label>

  • <span id="bn8ez"></span>

    <label id="bn8ez"><meter id="bn8ez"></meter></label>

    談笑有鴻儒,往來無白丁

    在恰當的時間、地點以恰當的方式表達給恰當的人...  閱讀的時候請注意分類,佛曰我日里面是談笑文章,其他是各個分類的文章,積極的熱情投入到寫博的隊伍中來,支持blogjava做大做強!向dudu站長致敬>> > 我的微博敬請收聽

    JNI(Java Native Interface Java 本地接口 ) 技術大家都不陌生,它可以幫助解決 Java 訪問底層硬件的局限和執行效率的提高。關于 JNI 的開發,大多數資料討論的都是如何用 C/C++ 語言開發 JNI ,甚至于 JDK 也提供了一個 javah 工具來自動生成 C 語言程序框架。但是,對于廣大的 Delphi 程序員來說,難道就不能用自己喜愛的 Delphi Java 互通消息了嗎?

    通過對 javah 生成的 C 程序框架和 JDK 中的 jni.h 文件的分析,我們發現, Java 利用 JNI 訪問本地代碼的關鍵在于 jni.h 中定義的 JNINativeInterface_ 這個結構 (Struct) ,如果用 Delhpi 語言改寫它的定義,應該也可以開發 JNI 的本地代碼。幸運的是,在網上有現成的代碼可以幫助你完成這個繁雜的工作,在 http://delphi-jedi.org 上提供了一個 jni.pas 文件,就是用 Delphi 語言重寫的 jni.h 。我們只需在自己的 Delphi 工程中加入 jni.pas 就可以方便地開發出基于 Delphi 語言的 JNI 本地代碼。

    本文將利用 jni.pas ,討論用 Delphi 語言開發 JNI 本地代碼的基本方法。

    先來看一個經典的 HelloWorld 例子。編寫以下 Java 代碼:

    class HelloWorld

    {

    ? public native void displayHelloWorld();

    ? static

    ? {

    ??? System.loadLibrary("HelloWorldImpl");

    ? }

    }

    這段代碼聲明了一個本地方法 displayHelloWorld ,它沒有參數,也沒有返回值,但是希望它能在屏幕上打印出“您好!中國?!弊謽印_@個任務我們打算交給了本地的 Delphi 來實現。同時,在這個類的靜態域中,用 System.loadLibrary() 方法裝載 HelloWorldImpl.dll 。注意,這里只需要給出文件名而不需要給出擴展名 dll 。

    這時候,如果在我們的 Java 程序中使用 HelloWorld 類的 displayHelloWorld 方法,系統將拋出一個 java.lang.UnsatisfiedLinkError 的錯誤,因為我們還沒有為它實現本地代碼。

    下面再看一下在 Delphi 中的本地代碼的實現。新建一個 DLL 工程,工程名為 HelloWorldImpl ,輸入以下代碼:

    Uses

    ? JNI;

    procedure Java_HelloWorld_displayHelloWorld(PEnv: PJNIEnv; Obj: JObject);stdcall;

    begin

    ? Writeln(' 您好!中國。 ');

    end;

    exports

    ? Java_HelloWorld_DisplayHelloWorld;

    end.

    這段代碼首先導入 jni.pas 單元。然后實現了一個叫 Java_HelloWorld_displayHelloWorld 的過程,這個過程的命名很有講究,它以 Java 開頭,用下劃線將 Java 類的包名、類名和方法名連起來。這個命名方法不能有誤,否則, Java 類將無法將 nativ 方法與它對應起來。同時,在 Win32 平臺上,此過程的調用方式只能聲明為 stdcall 雖然在 HelloWorld 類中聲明的本地方法沒有參數,但在 Delphi 中實現的具體過程則帶有兩個參數: PEnv : PJNIEnv Obj : JObject 。(這兩種類型都是在 jni.pas 中定義的)。其中, PEnv 參數代表了 Jvm 環境,而 Obj 參數則代表調用此過程的 Java 對象。當然,這兩個參數,在我們這個簡單的例子中是不會用到的。因為我們編譯的是 dll 文件,所以在 exports 需要輸出這個方法。

    編譯 Delphi 工程,生成 HelloWorldImp.dll 文件,放在運行時系統能夠找到的目錄,一般是當前目錄下, 并編寫調用 HelloWorld 類的 Java 類如下:

    class MainTest

    {

    ? public static void main(String[] args)

    ? {

    ??? new HelloWorld().displayHelloWorld();

    ? }

    }

    運行它,如果控制臺輸出了“您好!中國?!保材悖阋呀洺晒Φ赜?/span> Delphi 開發出第一個 JNI 應用了。

    接下來,我們稍稍提高一點,來研究一下參數的傳遞。還是 HelloWorld ,修改剛才寫的 displayHelloWorld 方法,讓顯示的字符串由 Java 類動態確定。新的 displayHelloWorld 方法的 Java 代碼如下:

    public native void displayHelloWorld(String str);

    修改 Delphi 的代碼,這回用到了過程的第一個固有參數 PEnv ,如下:

    procedure Java_HelloWorld_displayHelloWorld(PEnv: PJNIEnv; Obj: JObject; str: JString); stdcall;

    var

    ? JVM: TJNIEnv;

    begin

    ? JVM := TJNIEnv.Create(PEnv);

    ? Writeln(JVM.UnicodeJStringToString(str));

    ? JVM.Free;

    end;

    在該過程的參數表中我們增加了一個參數 str : JString ,這個 str 就負責接收來自 HelloWorld 傳入的 str 實參。注意實現代碼的不同,因為使用了參數,就涉及到參數的數據類型之間的轉換。從 Java 程序傳過來的 Java String 對象現在成了特殊的 JString 類型,而 JString Delphi 中是不可以直接使用的。需要借助 TJNIEnv 提供的 UnicodeJStringToString() 方法來轉換成 Delphi 能識別的 string 類型。所以,需要構造出 TJNIEnv 的實例對象,使用它的方法( TJNIEnv 提供了眾多的方法,這里只使用了它最基本最常用的一個方法),最后,記得要釋放它。對于基本數據類型的參數,從 Java 傳到 Delphi 中并在 Delphi 中使用的步驟就是這么簡單。

    我們再提高一點點難度,構建一個自定義類 Book ,并把它的實例對象作為參數傳入 Delphi ,研究一下在本地代碼中如何訪問對象參數的公共字段。

    首先,定義一個簡單的 Java Book ,為了把問題弄得稍微復雜一點,我們在 Book 中增加了一個 java.util.Date 類型的字段,代碼如下:

    public class Book

    {

    ? public String title;? // 標題

    ? public double price; // 價格

    ? public Date pdate;? // 購買日期

    }

    同樣,在 HelloWorld 類中增加一個本地方法 displayBookInfo ,代碼如下:

    public native void displayBookInfo(Book b);

    Delphi 的代碼相對于上面幾個例子來說,顯得復雜了一點,先看一下代碼:

    procedure Java_HelloWorld_displayBookInfo(PEnv: PJNIEnv; Obj: JObject; b:JObject); stdcall;

    var

    ?JVM: TJNIEnv;

    ?c,c2: JClass;

    ?fid:JFieldID;

    mid:JMethodID;

    title,datestr:string;

    price:double;

    pdate:JObject;

    begin

    ? JVM := TJNIEnv.Create(PEnv);

    ? c:=JVM.GetObjectClass(b);

    ? fid:=JVM.GetFieldID(c,'title','Ljava/lang/String;');

    ? title:=JVM.UnicodeJStringToString(JVM.GetObjectField(b,fid));

    ? fid:=JVM.GetFieldID(c,'price','D');

    ? price:=JVM.GetDoubleField(b,fid);

    ? fid:=JVM.GetFieldID(c,'pdate','Ljava/util/Date;');

    ? pdate:=JVM.GetObjectField(b,fid);

    ? c2:=JVM.GetObjectClass(pdate);

    ? mid:=JVM.GetMethodID(c2,'toString','()Ljava/lang/String;');

    ? datestr:=JVM.JStringToString(JVM.CallObjectMethodA(pdate,mid,nil));

    ?

    ? WriteLn(Format('%s? %f ?%s',[title,price,datestr]));

    ?

    ? JVM.Free;

    end;

    參數 b:JObject 就是傳入的 Book 對象。先調用 GetObjectClass 方法,根據 b 對象獲得它所屬的類 c ,然后調用 GetFieldID 方法從 ? 中獲取一個叫做 title 的屬性的字段 ID 一定要傳入正確的類型簽名。然后通過 GetObjectField 方法就可以根據得到的字段 ID 從對象中得到字段的值。注意這里的次序:我們得到傳入的對象參數 (Object) ,就要先得到它的類 (Class) ,這樣既有了對象實例,又有了類,以后就從類中得到字段 ID ,根據字段 ID 從對象中得到字段值。對于類的靜態字段,則可以直接從類中獲取它的值而不需要通過對象。 如果要調用對象的方法,操作步驟也基本類似,也需要從類中獲取方法 ID ,再執行對象的相應方法。在本例中,因為我們增加了一個 java.util.Date 類型的字段,要訪問這樣的字段,也只能先把它做為 JObject 讀入,再以同樣的方法進一步去訪問它的成員(屬性或方法)。本例中演示了如何訪問 Date 對象的成員方法 toString 。

    要正確地訪問類對象的成員屬性(字段)及成員方法,最重要的一點是一定要給出正確的簽名,在 Java 中對于數據類型和方法的簽名有如下的約定:

    數據類型 / 方法

    簽名

    byte

    B

    char

    C

    double

    D

    float

    F

    int

    I

    long

    J ( 注意:是 J 不是 L)

    short

    S

    void

    V

    boolean

    Z (注意:是 Z 不是 B

    類類型

    L 跟完整類名,如 Ljava/lang/String; (注意:以 L 開頭,要包括包名,以斜杠分隔,最后有一個分號作為類型表達式的結束)

    數組 type[]

    [type ,例如 float[] 的簽名就是 [float ,如果是二維數組,如 float[][] ,則簽名為 [[float ,(注意:這里是兩個 [ 符號)。

    方法

    ( 參數類型簽名 ) 返回值類型簽名,例如方法: float fun(int a,int b) ,它的簽名為 (II)F , ( 注意:兩個 I 之間沒有逗號! ) ,而對于方法 String toString() ,則是 ()Ljava/lang/String; 。

    通過上面的例子,我們了解了訪問對象參數的成員屬性或方法的基本步驟和多個 Get 方法的使用。 TJNIEnv 同時提供了多個 Set 方法,可以修改傳入的對象參數的字段值,因為 Java 對象參數都是以傳址的方式進行傳遞的,所以修改的結果可以在 Java 程序中得到反映。 TJNIEnv 提供的 Get/Set 方法,都需要兩個基本參數:對象實例( JObject 類型)和字段 ID JField 類型),就可以根據提供的對象和字段 ID 來獲取或設置這個對象的這個字段的值。

    現在我們了解了在 Delphi 代碼中使用以及修改 Java 對象的操作步驟。進一步,如果需要在 Delphi 中從無到有地創建一個新的 Java 對象,可以嗎?再來看一個例子,在 Delphi 中創建 Java 類的實例,操作方法其實也非常簡單。

    先在 Java 代碼中增加一個本地方法,如下:

    ?public native Book findBook(String t);

    然后,修改 Delphi 代碼,增加一個函數(因為有返回值,所以不再是過程而是函數了):

    function Java_HelloWorld_findBook(PEnv: PJNIEnv; Obj: JObject; t:JString):JObject; stdcall;

    var

    ?JVM: TJNIEnv;

    ?c: JClass;

    ?fid:JFieldID;

    ?b:JObject;

    ?mid:JMethodID;

    begin

    ? JVM := TJNIEnv.Create(PEnv);

    ?

    ? c:=JVM.FindClass('Book');

    ? mid:=JVM.GetMethodID(c,'<init>','()V');

    ? b:=JVM.NewObjectV(c,mid,nil);

    ? fid:=JVM.GetFieldID(c,'title','Ljava/lang/String;');

    ? JVM.SetObjectField(b,fid,t);

    ? fid:=JVM.GetFieldID(c,'price','D');

    ? JVM.SetDoubleField(b,fid,99.8);

    ? Result:=b;

    ?

    ? JVM.Free;

    end;

    這里先用 FindClass 方法根據類名查找到類,然后獲取構造函數的方法 ID ,構造函數名稱固定為“ <init> ”,注意簽名為“ ()V ”說明使用了 Book 類的一個空的構造函數。然后就是使用方法 NewObjectV 根據類和構造函數的方法 ID 來創建類的實例。創建了類實例,再對它進行操作就與前面的例子沒有什么兩樣了。對于非空的構造函數,則略為復雜一點。需要設置它的參數表。還是上面的例子,在 Book 類中增加一個非空構造函數:

    public Book(Strint t,double p){

    ?this.title=t;

    this.price=p;

    }

    Delphi 代碼中, findBook 函數修改獲取方法 ID 的代碼如下:

    mid:=JVM.GetMethodID(c,'<init>','(Ljava/lang/String;D)V');

    構造函數名稱仍是“ <init> ”,方法簽名表示它有兩個參數,分別是 String double 。然后就是參數的傳入了,在 Delphi 調用 Java 對象的方法如果需要傳入參數,都需要構造出一個參數數組。在變量聲明中加上:

    args : array[0..1] of JValue;

    注意!參數都是 JValue 類型,不管它是基本數據類型還是對象,都作為 JValue 的數組來處理。在代碼實現中為參數設置值,并將數組的地址作為參數傳給 NewObjectA 方法:

    ? args[0].l:=t; // t 是傳入的 JString 參數

    ? args[1].d:=9.8;

    ?

    ? b:=JVM.NewObjectA(c,mid,@args);

    JValue 類型的數據設置值的語句有點特殊,是吧?我們打開 jni.pas ,查看一下 JValue 的定義,原來它是一個 packed record ,已經包括了多種數據類型, JValue 的定義如下:

    ? JValue = packed record

    ? case Integer of

    ??? 0: (z: JBoolean);

    ??? 1: (b: JByte?? );

    ??? 2: (c: JChar?? );

    ??? 3: (s: JShort? );

    ??? 4: (i: JInt??? );

    ??? 5: (j: JLong?? );

    ??? 6: (f: JFloat? );

    ??? 7: (d: JDouble );

    ??? 8: (l: JObject );

    ? end;

    下面再來看一下錯誤處理,在調試前面的例子中,大家也許看到了一旦在 Delphi 的執行過程中發生了錯誤,控制臺就會輸出一大堆錯誤信息,如果想要屏蔽這些信息,也就是說希望在 Delphi 中捕獲錯誤并直接處理它,應該怎么做?也很簡單,在 TJNIEnv 中提供了兩個方法可以方便地處理在訪問 Java 對象時發生的錯誤。

    var

    … …

    ae:JThrowable;

    begin

    … …

    ae:=JVM.ExceptionOccurred;

    ? if ( ae<>nil ) then

    ?? begin

    ??? Writeln(Format('Exception handled in Main.cpp: %d', [longword(ae)]));

    ??? JVM.ExceptionDescribe;

    ??? JVM.ExceptionClear;

    ?? end;

    … …

    用方法 ExceptionOccurred 可以捕獲 Java 拋出的錯誤,并存入 JThrowable 類型的變量中。用 ExceptionDescribe 可以顯示出 Java 的錯誤信息,而 ExceptionClear 顯然就是清除錯誤,讓它不再被拋出。

    至此,我們已經把從 Java 代碼通過 JNI 技術訪問 Delphi 本地代碼的步驟做了初步的探討。在 jni.pas 中也提供了從 Delphi 中打開 Java 虛擬機執行 Java 代碼的方法,有興趣的讀者不妨自己研究一下。

    posted on 2006-12-19 05:41 壞男孩 閱讀(1296) 評論(1)  編輯  收藏 所屬分類: java命令學習

    FeedBack:
    # re: 用Delphi開發JNI(Java Native Interface)應用
    2006-12-22 14:46 | ghost
    東西真多,慢慢看  回復  更多評論
      
    主站蜘蛛池模板: 亚洲视频在线视频| 国产综合成人亚洲区| 性感美女视频免费网站午夜| 亚洲成a人无码亚洲成www牛牛| 亚洲成a人片在线观看久| A级毛片高清免费视频在线播放| 亚洲免费在线视频观看| 亚洲国产精品综合久久网络| 亚欧免费一级毛片| 日本亚洲中午字幕乱码| 亚洲第一福利视频| 永久黄网站色视频免费| 日韩精品在线免费观看| 精品亚洲国产成人av| 亚洲av网址在线观看| 免费在线观看a级毛片| 鲁大师在线影院免费观看| 国产精品亚洲精品久久精品| 久久久久久久久亚洲| 国产在线a不卡免费视频| 最近2019中文字幕免费大全5| 黄网站在线播放视频免费观看| 中文字幕亚洲综合久久| 亚洲欧洲自拍拍偷精品 美利坚 | 久久亚洲日韩看片无码| 免费少妇a级毛片| 亚洲精品在线免费观看视频| 九九热久久免费视频| 亚洲av无码成人影院一区| 久久夜色精品国产嚕嚕亚洲av| 免费人成视频在线观看不卡| 国产精品成人免费视频网站京东| 国产午夜成人免费看片无遮挡 | 国产中文字幕免费观看| 18pao国产成视频永久免费| 丰满少妇作爱视频免费观看| 亚洲日本在线电影| 亚洲精品偷拍无码不卡av| 亚洲精品无码乱码成人| 亚洲国产成人精品无码久久久久久综合 | 久久精品私人影院免费看|