java高级用法之:调用本地方法的利器JNA
文章目錄
- 簡介
- JNA初探
- JNA加載native lib的流程
- 本地方法中的結構體參數
- 總結
簡介
JAVA是可以調用本地方法的,官方提供的調用方式叫做JNI,全稱叫做java native interface。要想使用JNI,我們需要在JAVA代碼中定義native方法,然后通過javah命令創建C語言的頭文件,接著使用C或者C++語言來實現這個頭文件中的方法,編譯源代碼,最后將編譯后的文件引入到JAVA的classpath中,運行即可。
雖然JAVA官方提供了調用原生方法的方式,但是好像這種方法有點繁瑣,使用起來沒有那么的方便。
那么有沒有更加簡潔的調用本地方法的形式嗎?答案是肯定的,這就是今天要講的JNA。
JNA初探
JNA的全稱是Java Native Access,它為我們提供了一種更加簡單的方式來訪問本地的共享庫資源,如果你使用JNA,那么你只需要編寫相應的java代碼即可,不需要編寫JNI或者本地代碼,非常的方便。
本質上JNA使用的是一個小的JNI library stub,從而能夠動態調用本地方法。
JNA就是一個jar包,目前最新的版本是5.10.0,我們可以像下面這樣引用它:
<dependency><groupId>net.java.dev.jna</groupId><artifactId>jna</artifactId><version>5.10.0</version></dependency>JNA是一個jar包,它里面除了包含有基本的JAVA class文件之外,還有很多和平臺相關的文件,這些平臺相關的文件夾下面都是libjnidispatch*的庫文件。
可以看到不同的平臺對應著不同的動態庫。
JNA的本質就是將大多數native的方法封裝到jar包中的動態庫中,并且提供了一系列的機制來自動加載這個動態庫。
接下來我們看一個具體使用JNA的例子:
public class JNAUsage {public interface CLibrary extends Library {CLibrary INSTANCE = (CLibrary)Native.load((Platform.isWindows() ? "msvcrt" : "c"),CLibrary.class);void printf(String format, Object... args);}public static void main(String[] args) {CLibrary.INSTANCE.printf("Hello, World\n");for (int i=0;i < args.length;i++) {CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);}} }這個例子中,我們想要加載系統的c lib,從而使用c lib中的printf方法。
具體做法就是創建一個CLibrary interface,這個interface繼承自Library,然后使用Native.load方法來加載c lib,最后在這個interface中定義要使用的lib中的方法即可。
那么JNA到底是怎么加載native lib的呢?我們一起來看看。
JNA加載native lib的流程
在講解JNA加載native lib之前,我們先回顧一下JNI是怎么加載native lib的呢?
在JNI中,我們首先在java代碼中定義要調用的native方法,然后使用javah命令,創建C的頭文件,然后再使用C或者C++來對這個頭文件進行實現。
接下來最重要的一步就是將生成的動態鏈接庫添加到JAVA的classpath中,從而在JAVA調用native方法的時候,能夠加載到對應的庫文件。
對于上面的JNA的例子來說,直接運行可以得到下面的結果:
Hello, World我們可以向程序添加JVM參數:-Djna.debug_load=true,從而讓程序能夠輸出一些調試信息,再次運行結果如下所示:
12月 24, 2021 9:16:05 下午 com.sun.jna.Native extractFromResourcePath 信息: Looking in classpath from jdk.internal.loader.ClassLoaders$AppClassLoader@251a69d7 for /com/sun/jna/darwin-aarch64/libjnidispatch.jnilib 12月 24, 2021 9:16:05 下午 com.sun.jna.Native extractFromResourcePath 信息: Found library resource at jar:file:/Users/flydean/.m2/repository/net/java/dev/jna/jna/5.10.0/jna-5.10.0.jar!/com/sun/jna/darwin-aarch64/libjnidispatch.jnilib 12月 24, 2021 9:16:05 下午 com.sun.jna.Native extractFromResourcePath 信息: Extracting library to /Users/flydean/Library/Caches/JNA/temp/jna17752159487359796115.tmp 12月 24, 2021 9:16:05 下午 com.sun.jna.NativeLibrary loadLibrary 信息: Looking for library 'c' 12月 24, 2021 9:16:05 下午 com.sun.jna.NativeLibrary loadLibrary 信息: Adding paths from jna.library.path: null 12月 24, 2021 9:16:05 下午 com.sun.jna.NativeLibrary loadLibrary 信息: Trying libc.dylib 12月 24, 2021 9:16:05 下午 com.sun.jna.NativeLibrary loadLibrary 信息: Found library 'c' at libc.dylib Hello, World仔細觀察上面的輸出結果,我們可以大概了解JNA的工作流程。JNA的工作流程可以分為兩部分,第一部分是Library Loading,第二部分是Native Library Loading。
兩個部分分別對應的類是com.sun.jna.Native和com.sun.jna.NativeLibrary。
第一部分的Library Loading意思是將jnidispatch這個共享的lib文件加載到System中,加載的順序是這樣的:
當jnidispatch被加載之后,會設置系統變量 jna.loaded=true,表示jna的lib已經加載完畢。
默認情況下我們加載的lib文件名字叫jnidispatch,你也可以通過設置jna.boot.library.name來對他進行修改。
我們看一下loadNativeDispatchLibrary的核心代碼:
String libName = "/com/sun/jna/" + Platform.RESOURCE_PREFIX + "/" + mappedName;File lib = extractFromResourcePath(libName, Native.class.getClassLoader());if (lib == null) {if (lib == null) {throw new UnsatisfiedLinkError("Could not find JNA native support");}}LOG.log(DEBUG_JNA_LOAD_LEVEL, "Trying {0}", lib.getAbsolutePath());System.setProperty("jnidispatch.path", lib.getAbsolutePath());System.load(lib.getAbsolutePath());jnidispatchPath = lib.getAbsolutePath();首先是查找stub lib文件:/com/sun/jna/darwin-aarch64/libjnidispatch.jnilib, 默認情況下這個lib文件是在jna.jar包中的,所以需要調用extractFromResourcePath方法將jar包中的lib文件拷貝到臨時文件中,然后調用System.load方法將其加載。
第二部分就是調用com.sun.jna.NativeLibrary中的loadLibrary方法來加載JAVA代碼中要加載的lib。
在loadLibrary的時候有一些搜索路徑的規則如下:
所有的搜索邏輯都放在NativeLibrary的方法loadLibrary中實現的,方法體太長了,這里就不一一列舉了,感興趣的朋友可以自行去探索。
本地方法中的結構體參數
如果本地方法傳入的參數是基本類型的話,在JNA中定義該native方法就用基本類型即可。
但是有時候,本地方法本身的參數是一個結構體類型,這種情況下我們該如何進行處理呢?
以Windows中的kernel32 library為例,這個lib中有一個GetSystemTime方法,傳入的是一個time結構體。
我們通過繼承Structure來定義參數的結構體:
@FieldOrder({ "wYear", "wMonth", "wDayOfWeek", "wDay", "wHour", "wMinute", "wSecond", "wMilliseconds" }) public static class SYSTEMTIME extends Structure {public short wYear;public short wMonth;public short wDayOfWeek;public short wDay;public short wHour;public short wMinute;public short wSecond;public short wMilliseconds; }然后定義一個Kernel32的interface:
public interface Kernel32 extends StdCallLibrary { Kernel32 INSTANCE = (Kernel32)Native.load("kernel32", Kernel32.class); Kernel32 SYNC_INSTANCE = (Kernel32)Native.synchronizedLibrary(INSTANCE);void GetSystemTime(SYSTEMTIME result); }最后這樣調用:
Kernel32 lib = Kernel32.INSTANCE; SYSTEMTIME time = new SYSTEMTIME(); lib.GetSystemTime(time);System.out.println("Today's integer value is " + time.wDay);總結
以上就是JNA的基本使用,有關JNA根據深入的使用,敬請期待后續的文章。
本文的代碼:https://github.com/ddean2009/learn-java-base-9-to-20.git
本文已收錄于 http://www.flydean.com/02-jna-overview/
最通俗的解讀,最深刻的干貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!
歡迎關注我的公眾號:「程序那些事」,懂技術,更懂你!
總結
以上是生活随笔為你收集整理的java高级用法之:调用本地方法的利器JNA的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: netty系列之:netty中各不同种类
- 下一篇: 网络协议之:socket协议详解之Soc