教你在JNA中將本地方法映射到JAVA代碼中的示例
簡介
不管是JNI還是JNA,最終調(diào)用的都是native的方法,但是對于JAVA程序來說,一定需要一個調(diào)用native方法的入口,也就是說我們需要在JAVA方法中定義需要調(diào)用的native方法。
對于JNI來說,我們可以使用native關(guān)鍵字來定義本地方法。那么在JNA中有那些在JAVA代碼中定義本地方法的方式呢?
Library Mapping
要想調(diào)用本地的native方法,首選需要做的事情就是加載native的lib文件。我們把這個過程叫做Library Mapping,也就是說把native的library 映射到j(luò)ava代碼中。
JNA中有兩種Library 映射的方法,分別是interface和direct mapping。
先看下interface mapping,假如我們要加載 C library, 如果使用interface mapping的方式,我們需要創(chuàng)建一個interface繼承Library:
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)Native.load("c", CLibrary.class);
}上面代碼中Library是一個interface,所有的interface mapping都需要繼承這個Library。
然后在interface內(nèi)部,通過使用Native.load方法來加載要使用的c library。
上面的代碼中,load方法傳入兩個參數(shù),第一個參數(shù)是library的name,第二個參數(shù)是interfaceClass.
下面的表格展示了Library Name和傳入的name之間的映射關(guān)系:
| OS | Library Name | String |
|---|---|---|
| Windows | user32.dll | user32 |
| Linux | libX11.so | X11 |
| Mac OS X | libm.dylib | m |
| Mac OS X Framework | /System/Library/Frameworks/Carbon.framework/Carbon | Carbon |
| Any Platform | current process | null |
事實上,load還可以接受一個options的Map參數(shù)。默認情況下JAVA interface中要調(diào)用的方法名稱就是native library中定義的方法名稱,但是有些情況下我們可能需要在JAVA代碼中使用不同的名字,在這種情況下,可以傳入第三個參數(shù)map,map的key可以是 OPTION_FUNCTION_MAPPER,而它的value則是一個 FunctionMapper ,用來將JAVA中的方法名稱映射到native library中。
傳入的每一個native library都可以用一個NativeLibrary的實例來表示。這個NativeLibrary的實例也可以通過調(diào)用NativeLibrary.getInstance(String)來獲得。
另外一種加載native libary的方式就是direct mapping,direct mapping使用的是在static block中調(diào)用Native.register方式來加載本地庫,如下所示:
public class CLibrary {
static {
Native.register("c");
}
}Function Mapping
當我們加載完native library之后,接下來就是定義需要調(diào)用的函數(shù)了。實際上就是做一個從JAVA代碼到native lib中函數(shù)的一個映射,我們將其稱為Function Mapping。
和Library Mapping一樣,F(xiàn)unction Mapping也有兩種方式。分別是interface mapping和direct mapping。
在interface mapping中,我們只需要按照native library中的方法名稱定義一個一樣的方法即可,這個方法不用實現(xiàn),也不需要像JNI一樣使用native來修飾,如下所示:
public interface CLibrary extends Library {
int atol(String s);
}注意,上面我們提到了JAVA中的方法名稱不一定必須和native library中的方法名稱一致,你可以通過給Native.load方法傳入一個FunctionMapper來實現(xiàn)。
或者,你可以使用direct mapping的方式,通過給方法添加一個native修飾符:
public class HelloWorld {
public static native double cos(double x);
public static native double sin(double x);
static {
Native.register(Platform.C_LIBRARY_NAME);
}
public static void main(String[] args) {
System.out.println("cos(0)=" + cos(0));
System.out.println("sin(0)=" + sin(0));
}
}對于direct mapping來說,JAVA方法可以映射到native library中的任何static或者對象方法。
雖然direct mapping和我們常用的java JNI有些類似,但是direct mapping存在著一些限制。
大部分情況下,direct mapping和interface mapping具有相同的映射類型,但是不支持Pointer/Structure/String/WString/NativeMapped數(shù)組作為函數(shù)參數(shù)值。
在使用TypeMapper或者NativeMapped的情況下,direct mapping不支持 NIO Buffers 或者基本類型的數(shù)組作為返回值。
如果要使用基礎(chǔ)類型的包裝類,則必須使用自定義的TypeMapper.
對象JAVA中的方法映射來說,該映射最終會創(chuàng)建一個Function對象。
Invocation Mapping
講完library mapping和function mapping之后,我們接下來講解一下Invocation Mapping。
Invocation Mapping代表的是Library中的OPTION_INVOCATION_MAPPER,它對應(yīng)的值是一個InvocationMapper。
之前我們提到了FunctionMapper,可以實現(xiàn)JAVA中定義的方法名和native lib中的方法名不同,但是不能修改方法調(diào)用的狀態(tài)或者過程。
而InvocationMapper則更進一步, 允許您任意重新配置函數(shù)調(diào)用,包括更改方法名稱以及重新排序、添加或刪除參數(shù)。
下面舉個例子:
new InvocationMapper() {
public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) {
if (m.getName().equals("stat")) {
final Function f = lib.getFunction("_xstat");
return new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
Object[] newArgs = new Object[args.length+1];
System.arraycopy(args, 0, newArgs, 1, args.length);
newArgs[0] = Integer.valueOf(3); // _xstat version
return f.invoke(newArgs);
}
};
}
return null;
}
}
看上面的調(diào)用例子,感覺有點像是反射調(diào)用,我們在InvocationMapper中實現(xiàn)了getInvocationHandler方法,根據(jù)給定的JAVA代碼中的method去查找具體的native lib,然后獲取到lib中的function,最后調(diào)用function的invoke方法實現(xiàn)方法的最終調(diào)用。
在這個過程中,我們可以修改方傳入的參數(shù),或者做任何我們想做的事情。
還有一種情況是c語言中的內(nèi)聯(lián)函數(shù)或者預(yù)處理宏,如下所示:
// Original C code (macro and inline variations)
#define allocblock(x) malloc(x * 1024)
static inline void* allocblock(size_t x) { return malloc(x * 1024); }
上面的代碼中定義了一個allocblock(x)宏,它實際上等于malloc(x * 1024),這種情況就可以使用InvocationMapper,將allocblock使用具體的malloc來替換:
// Invocation mapping
new InvocationMapper() {
public InvocationHandler getInvocationHandler(NativeLibrary lib, Method m) {
if (m.getName().equals("allocblock")) {
final Function f = lib.getFunction("malloc");
return new InvocationHandler() {
public Object invoke(Object proxy, Method method, Object[] args) {
args[0] = ((Integer)args[0]).intValue() * 1024;
return f.invoke(newArgs);
}
};
}
return null;
}
}
防止VM崩潰
JAVA方法和native方法映射肯定會出現(xiàn)一些問題,如果映射方法不對或者參數(shù)不匹配的話,很有可能出現(xiàn)memory access errors,并且可能會導致VM崩潰。
通過調(diào)用Native.setProtected(true),可以將VM崩潰轉(zhuǎn)換成為對應(yīng)的JAVA異常,當然,并不是所有的平臺都支持protection,如果平臺不支持protection,那么Native.isProtected()會返回false。
如果要使用protection,還要同時使用 jsig library,以防止信號和JVM的信號沖突。libjsig.so一般存放在JRE的lib目錄下, j a v a . h o m e / l i b / {java.home}/lib/ java.home/lib/{os.arch}/libjsig.so, 可以通過將環(huán)境變量設(shè)置為LD_PRELOAD (或者LD_PRELOAD_64)來使用。
性能考慮
上面我們提到了JNA的兩種mapping方式,分別是interface mapping和direct mapping。相較而言,direct mapping的效率更高,因為direct mapping調(diào)用native方法更加高效。
但是上面我們也提到了direct mapping在使用上有一些限制,所以我們在使用的時候需要進行權(quán)衡。
另外,我們需要避免使用基礎(chǔ)類型的封裝類,因為對于native方法來說,只有基礎(chǔ)類型的匹配,如果要使用封裝類,則必須使用Type mapping,從而造成性能損失。
總結(jié)
JNA是調(diào)用native方法的利器,如果數(shù)量掌握的話,肯定是如虎添翼。
到此這篇關(guān)于教你在JNA中將本地方法映射到JAVA代碼中的文章就介紹到這了,更多相關(guān)JNA本地方法映射JAVA代碼內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實現(xiàn)的權(quán)重算法(按權(quán)重展現(xiàn)廣告)
這篇文章主要介紹了Java實現(xiàn)的權(quán)重算法(按權(quán)重展現(xiàn)廣告),本文講解了算法實現(xiàn)原理和實現(xiàn)代碼,需要的朋友可以參考下2015-04-04
SpringBoot如何使用RequestBodyAdvice進行統(tǒng)一參數(shù)處理
這篇文章主要介紹了SpringBoot使用RequestBodyAdvice進行統(tǒng)一參數(shù)處理方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06
解決restlet client報錯No response.Is the cer
這篇文章主要介紹了解決restlet client報錯No response.Is the certificate valid? Click here to check.問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教2024-01-01

