Java 調用其他開發語言開發的動態鏈接庫文件(一)

一,JAVA 調用delphi開發的動態庫文件


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 代碼:

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

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

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



這段代碼首先導入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類如下:


運行它,如果控制檯輸出了“您好!中國。”,恭喜你,你已經成功地用Delphi開發出第一個JNI應用了。

 

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

public native void displayHelloWorld(String str);

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

在該過程的參數表中我們增加了一個參數 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類型的字段,代碼如下:

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

public native void displayBookInfo(Book b);

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


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


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章