程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> JNI的替代者:如何使用JNR訪問Java外部函數接口(jnr-ffi)

JNI的替代者:如何使用JNR訪問Java外部函數接口(jnr-ffi)

編輯:關於JAVA

1. JNR簡單介紹

繼上文“JNI的替代者—使用JNA訪問Java外部函數接口”,我們知道JNI越來越不受歡迎,JNI是編寫Java本地方法以及將Java虛擬機嵌入本地應用程序的標准編程接口。它管理著JVM和非托管的本地環境之間的邊界,提供數據編組和對象生命周期管理協議。

根據JEP(JDK增強提案) 191,JNI在下列幾個方面最令開發人員痛苦:

需要開發人員編寫C代碼,這意味著他們需要具備一個完全不同於Java的世界的專業知識。

由於開發人員必須對JVM如何管理內存和代碼多少有一些了解,所以典型的C和Java開發人員通常並不具備使用JNI所需的專業知識。

開發人員必須能夠為他們想要支持的每個平台構建代碼,或者為終端用戶提供適當的工具,由他們來完成這項工作。

相比於相同的庫綁定到本地應用程序,基於JNI的庫性能通常較差。

JNI充當了一個不透明的安全邊界。JDK並不知道庫中的函數可能會調用什麼,或者庫中的代碼是否會損害JVM的穩定或安全。

因此JNI創建本地函數的方式並不簡單,於是產生了像Java Native Access(JNA)和Java Native Runtime(JNR)這樣的庫。JNA和JNR都是基於JNI創建的,而JEP 191定義的Java Foreign Function Interface(FFI)可能會基於JNR。使用FFI API而不是JNI綁定本地代碼和內存將成為開發人員更喜歡的方式。

FFI API將提供下列特性:

一個描述本地庫調用和本地內存結構的元數據系統。

發現和加載本地庫的機制。

基於元數據將庫/函數或內存結構綁定到Java端點的機制。

用於Java數據類型和本地數據類型之間編組和解組的代碼。

對Java FFI的需求已經產生了JNA和JNR庫。JNA庫應用更廣泛(具體使用參見“JNI的替代者—使用JNA訪問Java外部函數接口”)。JNR庫更全面,因為它實現了不同層次的抽象,提供了函數和內存元數據,對庫和函數綁定進行了抽象。JNR已經在JRuby項目中大量使用,它可能會成為JEP 191的基礎。

上面段落來自JEP 191的描述(由參考文獻(1)翻譯),由此可見雖然JNA使用廣泛,但JNR可能更漸趨勢,也許在不久的將來JNR-FFI(jffi)就會內建在JDK中與JNI一樣成為Java訪問外部函數的標准接口。因此,學習使用JNR是非常有必要的。

JNR-FFI項目也托管自Github,其使用方法與JNA差不多,不過JNR並沒有給出相應的jar包,需要我們自己打包使用。

2. JNR項目打包(jnr-ffi.jar)——如何打包Github上的maven項目

首先要明確,Github上托管的項目一般是用maven管理構建的,而不是Eclipse/MyEclipse,因此如果你想通過從Github上直接下載項目源碼(Download Zip的方式下載)然後導入或拷貝進Eclipse裡打包是行不通的。我一開始也是這麼做的,發現項目不完整,缺少一些包,因此打成的jar包也是不能用的。

讓我驚訝的,在maven官方庫裡的jnr-ffi.jar包也是不完整的,下載下來也不能用,還有這個地方的所有jnr包,我都試過了,全部不完整,因此只能自己打包。

在打包之前,你首先需要將完整的源碼下載下來,然後有兩種方式打包成jar文件。

將maven項目導入Eclipse中打包

通過maven命令mvn打包

兩種方法都有需要注意的地方。不熟悉maven的人可以采取第一種方式,上手簡單。熟悉maven的當然推薦用mvn命令打包,不過需要注意這裡有第三方依賴包,不是一句簡單的命令就可搞定。

將maven項目導入Eclipse中打包

注意:雖然Eclipse內置了Maven插件,但表示不太好用,經常出現問題,建議卸載Eclipse的自帶的maven插件,然後安裝第三方的m2eclipse插件,該插件目前有效的安裝地址為:http://download.eclipse.org/technology/m2e/releases,通過Eclipse中Help—Install New Software...—Add Repository安裝即可。

有了maven插件後,打包的具體步驟如下:

(1)從Github下載源碼

這個其實非常關鍵,因為不能通過“Download Zip”的方式直接從Github網頁上下載,這樣下載的源碼缺少很多j依賴的ar包,需要通過git clone的方式下載

git clone https://github.com/jnr/jnr-ffi.git

下載後的項目源碼就在當前命令行路徑下。

查看本欄目

(2)導入maven項目

將剛下載的完整的jnr源碼導入到Eclipse中,注意導入的是Maven項目

選擇剛下載的項目根路徑

查看本欄目

這裡出現了錯誤,如果沒錯的就可以直接打包了,如果跟我一樣出現下面的錯誤,那麼請繼續

從出錯信息可以看出是缺少Maven-antrun插件,這是Maven的ant插件,用來自動構建項目的,沒有這個插件,maven配置文件pom.xml中的<execution></execution>之間的任務就執行不了,因此如果忽略這個出錯繼續點“Finish”那麼pom.xml文件就有錯誤,具體的出錯信息如下:

Plugin execution not covered by lifecycle configuration: org.apache.maven.plugins:maven-antrun-plugin:1.1:run
 (execution: default, phase: test-compile)

這裡有官方給出的解決方案,我就直接用第一種方法:在<plugins>前面加上<pluginManagement>,在</plugins>後面加上加上</pluginManagement>  即可。

其實我的Eclipse工程裡還有另外一個錯誤,就是在NativeClosureFactory.java文件中:

The method expunge(NativeClosureFactory.ClosureReference, Integer) in the type NativeClosureFactory is not applicable for 
the arguments (NativeClosureFactory<T>.ClosureReference, Integer)

屬於Java泛型錯誤,不知道完整的代碼你可能不知道具體的問題所在,下面舉個簡單的例子:

public final class Native<T> {  
           
        private void test1(Ref ref, Integer key) {  
           
        }  
           
        final class Ref {  
            private final Native factory;  
           
            private Ref(Native factory) {  
                this.factory = factory;  
            }  
           
            public void test2() {  
               factory.test1(this, 1);  
            }  
        }  
    }

你能看出問題所在嗎?Native類是個泛型類,但在其內置類Ref中使用時沒有加上泛型的標志,將Native當作普通類使用,忽略了泛型<T>標志。其實這可能與Java編譯器有關,有的版本可能不會報這個錯,那麼改正方法也很簡單,將

privatefinalNative factory;privateRef(Native factory){

改成

privatefinalNative<T> factory;privateRef(Native<T> factory){

即可。

至此,項目沒有任何錯誤產生了,就可以開始打包了(據我測試,前面的兩個錯誤不改正直接打包其實也沒什麼關系,jar包照樣能用,但是知錯改錯我們能學到更多額外的東西)。

查看本欄目

(3)用Build fat jar 打包

這裡為什麼說要用“Build fat jar”工具打包而不是直接的export出jar包的方式打包呢?因為該工程依賴了很多其它的第三方jar包,如果直接export而不作配置,這些依賴的jar包不會被打進去,也就錯了,需要自定義配置文件MANIFEST.MF,有些麻煩,具體配置可參考“Eclipse將引用了第三方jar包的Java項目打包成jar文件的兩種方法”。

使用Fat jar打包插件就不一樣了,無需任何配置,一鍵打包,該插件安裝方法也請參考上述文章:

修改jar包文件,加上目前的版本號即可。可以看到用Eclipse打包還是挺麻煩的,至少我遇到了N多問題,因此推薦用mvn命令打包。

通過maven命令mvn打包

如果你機子上沒有安裝maven,那麼請首先到這裡下載其二進制包,無需安裝,只要解壓到某個路徑下,然後將其路徑添加到環境變量PATH中即可在任何地方使用。

命令行進入到jnr-ffi所在根目錄,一般用mvn命令打jar命令如下即可:

mvn jar:jar

但是這樣的不對的,該命令打成的jar包不包含依賴的第三方jar文件,因此是錯誤的。其實我發現在網上找到的所有jnr-ffi的jar包都是直接用這個命令打包的,因此全部不能用。

正確的打包方式是:

將包含第三方依賴jar的maven項目打包成jar文件有兩種方法,我這裡使用比較簡單的方法:使用maven-assembly-plugin打包,步驟如下:

(1)pom.xml添加assembly插件

<plugin>  
        <artifactId>maven-assembly-plugin</artifactId>  
        <configuration>            
           <descriptorRefs>  
             <descriptorRef>jar-with-dependencies</descriptorRef> 
           </descriptorRefs>  
               
        </configuration>  
      </plugin>

由於第三方jar沒有main文件,所以不需要加manifest。

(2)執行如下命令

mvn assembly:assembly

這樣就在jnr-ffi根目錄下的target文件夾裡生成一個jnr-ffi-2.0.0-SNAPSHOT-jar-with-dependencies.jar文件。

這就是我們所需要的jar文件。

不管如何,如果你打包不順利的話,這裡有我打的jnr-ffi_2.0.0jar包下載地址

查看本欄目

3. JNR簡單實例

將打包好的jar文件加到Eclipse中,還是以“Hello World”為例,這次用C中的puts()函數打印,如下:

package helloworld;  
           
    import jnr.ffi.LibraryLoader;  
           
    public class HelloWorld {  
        public static interface LibC {  
            int puts(String s);  
        }  
           
        public static void main(String[] args) {  
            LibC libc = LibraryLoader.create(LibC.class).load("msvcrt");  
           
            libc.puts("Hello, World");  
        }  
    }

(1)定義一個靜態接口

與JNA不同的是,該靜態接口不用繼承JNR中的某個類,更加簡單。

接口裡的內容就是你要用的動態鏈接庫函數原型,同樣的,該原型必須與C/C++中的保持一致,這同樣是技術難點(詳見上篇文章中的技術難點詳述)。

(2)如何調用聲明的外部函數

首先通過LibraryLoader.create().laod()得到該接口的一個實例,然後通過該實例直接調用裡面的方法即可。

LibraryLoader.create().load()中第一個括號裡是該接口的Class類型,第二個括號是要加載的動態鏈接庫名稱,同樣沒有.dll/.so後綴。這兩個參數與JNA下的兩個參數是一樣的,使用情況也是一樣。

Java的類型與C類型的對應關系為:

byte - 8 bit signed integer

short - 16 bit signed integer

int - 32 bit signed integer

long - natural long (i.e. 32 bits wide on 32 bit systems, 64 bit wide on 64bit systems)

float - 32 bit float

double - 64 bit float

String - equivalent to "const char *"

Pointer - equivalent to "void *"

Buffer - equivalent to "void *"

這只是JNR的入門使用,更多的使用方法還期待官方給出更多的例子和說明文檔。

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved