JNA全稱Java Native Access,是一個創建在經典的JNI技術之上的Java開源框架(https://github.com/twall/jna)。JNA提供一組Java工具類用於在運行期動態訪問系統本地庫(native library:如Window的dll)而不須要編寫任何Native/JNI代碼。開發人員只要在一個java接口中描述目標native library的函數與結構,JNA將自動實現Java接口到native function的映射。html
JNA包:java
https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna/4.0.0/jna-4.0.0.jarlinux
JNA在線幫助文檔:http://twall.github.io/jna/4.0/javadoc/github
JNA入門示例:https://github.com/twall/jna/blob/master/www/GettingStarted.md編程
1,dll和so是C函數的集合和容器,這與Java中的接口概念吻合,因此JNA把dll文件和so文件當作一個個接口。在JNA中定義一個接口就是至關於了定義一個DLL/SO文件的描述文件,該接口表明了動態連接庫中發佈的全部函數。並且,對於程序不須要的函數,能夠不在接口中聲明。數組
2,JNA定義的接口通常繼承com.sun.jna.Library接口,若是dll文件中的函數是以stdcall方式輸出函數,那麼,該接口就應該繼承com.sun.jna.win32.StdCallLibrary接口。安全
3,Jna難點:編程語言之間的數據類型不一致。數據結構
Java和C的數據類型對照表架構
Java 類型 |
C 類型 |
原生表現 |
|
boolean |
int |
32位整數(可定製) |
|
byte |
char |
8位整數 |
|
char |
wchar_t |
平臺依賴 |
|
short |
short |
16位整數 |
|
int |
int |
32位整數 |
|
long |
long long, __int64 |
64位整數 |
|
float |
float |
32位浮點數 |
|
double |
double |
64位浮點數 |
|
Buffer/Pointer |
pointer |
平臺依賴(32或64位指針) |
|
<T>[] (基本類型的數組) |
pointer/array |
32或64位指針(參數/返回值) 鄰接內存(結構體成員) |
|
String |
char* |
/0結束的數組 (native encoding or jna.encoding) |
|
WString |
wchar_t* |
/0結束的數組(unicode) |
|
String[] |
char** |
/0結束的數組的數組 |
|
WString[] |
wchar_t** |
/0結束的寬字符數組的數組 |
|
Structure |
struct*/struct |
指向結構體的指針(參數或返回值) (或者明確指定是結構體指針)結構體(結構體的成員) (或者明確指定是結構體) |
|
Union |
union |
等同於結構體 |
|
Structure[] |
struct[] |
結構體的數組,鄰接內存 |
|
Callback |
<T> (*fp)() |
Java函數指針或原生函數指針 |
|
NativeMapped |
varies |
依賴於定義 |
|
NativeLong |
long |
平臺依賴(32或64位整數) |
|
PointerType |
pointer |
和Pointer相同 |
通用入門案例:
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
/** Simple example of JNA interface mapping and usage. */
public class HelloWorld {
// This is the standard, stable way of mapping, which supports extensive
// customization and mapping of Java to native types.
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((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]);
}
}
}
運行程序,若是沒有帶參數則只打印出「Hello, World」,若是帶了參數,則會打印出全部的參數。
很簡單,不須要寫一行C代碼,就能夠直接在Java中調用外部動態連接庫中的函數!
下面來解釋下這個程序。
Library
或StdCallLibrary
默認的是繼承
Library
,若是動態連接庫裏的函數是以stdcall方式輸出的,那麼就繼承StdCallLibrary
,好比衆所周知的kernel32庫。好比上例中的接口定義:
public interface CLibrary extends Library {
}
接口內部須要一個公共靜態常量:INSTANCE,
經過這個常量,就能夠得到這個接口的實例,從而使用接口的方法,也就是調用外部dll/so的函數。
該常量經過Native.loadLibrary()這個API函數得到,該函數有2個參數:
CLibrary INSTANCE = (CLibrary)
Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
CLibrary.class);
接口中只須要定義你要用到的函數或者公共變量,不須要的能夠不定義,如上例只定義printf函數:
void printf(String format, Object... args);
注意參數和返回值的類型,應該和連接庫中的函數類型保持一致。
定義好接口後,就可使用接口中的函數即相應dll/so中的函數了,前面說過調用方法就是經過接口中的實例進行調用,很是簡單,如上例中:
CLibrary.INSTANCE.printf("Hello, World\n");
for (int i=0;i < args.length;i++) {
CLibrary.INSTANCE.printf("Argument %d: %s\n", i, args[i]);
}
這就是JNA使用的簡單例子,可能有人認爲這個例子太簡單了,由於使用的是系統自帶的動態連接庫,應該還給出一個本身實現的庫函數例子。其實我以爲這個徹底沒有必要,這也是JNA的方便之處,不像JNI使用用戶自定義庫時還得定義一大堆配置信息,對於JNA來講,使用用戶自定義庫與使用系統自帶的庫是徹底同樣的方法,不須要額外配置什麼信息。好比我在Windows下創建一個動態庫程序:
#include "stdafx.h"
extern "C"_declspec(dllexport) int add(int a, int b);
int add(int a, int b) {
return a + b;
}
public class DllTest {
public interface CLibrary extends Library {
CLibrary INSTANCE = (CLibrary)Native.loadLibrary("CDLL", CLibrary.class);
int add(int a, int b);
}
public static void main(String[] args) {
int sum = CLibrary.INSTANCE.add(3, 6);
System.out.println(sum);
}
}
import com.sun.jna.Library;
import com.sun.jna.Native;
import com.sun.jna.Platform;
interface HelloInter extends Library{
int toupper(int ch);
double pow(double x,double y);
void printf(String format,Object... args);
}
public class HelloWorld {
public static void main(String [] args){
HelloInter INSTANCE =
(HelloInter)Native.loadLibrary(
Platform.isWindows()?"msvcrt":"c",
HelloInter.class);
INSTANCE.printf("Hello, Worldn");
String [] strs = new String[]{"芙蓉","如花","鳳姐"};
for (int i=0;i < strs.length;i++) {
INSTANCE.printf("人物 %d: %sn", i, strs[i]);
}
System.out.println("pow(2d,3d)=="+INSTANCE.pow(2d, 3d));
System.out.println("toupper('a')=="+(char)INSTANCE.toupper((int)'a'));
}
}
pow(2d,3d)==8.0
toupper('a')==A
Hello, World
人物 0: 芙蓉
人物 1: 如花
人物 2: 鳳姐
說明:
HelloInter接口中定義的3個函數全是C語言函數庫中的函數,其定義格式以下:
int toupper(int ch)
double pow( double x, double y )
int printf(const char* format, ...)
C語言函數庫中有不少個函數,可是咱們只用到了這3個函數,因此其餘的函數不須要聲明在接口中。
JNA模擬結構體
例:使用 JNA調用使用 Struct的 C函數
假設咱們如今有這樣一個C 語言結構體
struct UserStruct{
long id;
wchar_t* name;
int age;
};
使用上述結構體的函數
#define MYLIBAPI extern "C" __declspec( dllexport )
MYLIBAPI void sayUser(UserStruct* pUserStruct);
對應的Java 程序中,在例1 的接口中添加下列代碼:
public static class UserStruct extends Structure{
public NativeLong id;
public WString name;
public int age;
public static class ByReference extends UserStruct
implements Structure.ByReference { }
public static class ByValue extends UserStruct implements
Structure.ByValue
{ }
}
public void sayUser(UserStruct.ByReference struct);
UserStruct userStruct=new UserStruct ();
userStruct.id=new NativeLong(100);
userStruct.age=30;
userStruct.name=new WString("奧巴馬");
TestDll1.INSTANCE.sayUser(userStruct);
如今,咱們就在Java 中實現了對C 語言的結構體的模擬。這裏,咱們繼承了Structure 類,用這個類來模擬C 語言的結構體。
必須注意,Structure 子類中的公共字段的順序,必須與C 語言中的結構的順序一致。不然會報錯!由於,Java 調用動態連接庫中的C 函數,實際上就是一段內存做爲函數的參數傳遞給C函數。動態連接庫覺得這個參數就是C 語言傳過來的參數。同時,C 語言的結構體是一個嚴格的規範,它定義了內存的次序。所以,JNA 中模擬的結構體的變量順序絕對不能錯。若是一個Struct 有2 個int 變量。Int a, int b若是JNA 中的次序和C 中的次序相反,那麼不會報錯,可是數據將會被傳遞到錯誤的字段中去。裏直接使用了UserStruct userStruct=new UserStruct ();
也可使用UserStruct userStruct=new UserStruct.ByReference ();
明確指出userStruct 對象是結構體指針而不是結構體自己。
JNA模擬複雜結構體
C 語言最主要的數據類型就是結構體。結構體能夠內部能夠嵌套結構體,這使它能夠模
擬任何類型的對象。
JNA 也能夠模擬這類複雜的結構體。
struct CompanyStruct{
long id;
wchar_t* name;
UserStruct users[100];
int count;
};
public static class CompanyStruct extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
public int count;
}
例:結構體內部能夠包含結構體對象的指針的數組
struct CompanyStruct2{
long id;
wchar_t* name;
UserStruct* users[100];
int count;
};
public static class CompanyStruct2 extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByReference[] users=new
UserStruct.ByReference[100];
public int count;
}
CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();
companyStruct2.id=new NativeLong(2);
companyStruct2.name=new WString("Yahoo");
companyStruct2.count=10;
UserStruct.ByReference pUserStruct=new
UserStruct.ByReference();
pUserStruct.id=new NativeLong(90);
pUserStruct.age=99;
pUserStruct.name=new WString("楊致遠");
// pUserStruct.write();
for(int i=0;i<companyStruct2.count;i++){
companyStruct2.users[i]=pUserStruct;
}
TestDll1.INSTANCE.sayCompany2(companyStruct2);
執行測試代碼,報錯了。這是怎麼回事?
typedef struct { WORD wYear; WORD wMonth; WORD wDayOfWeek; WORD wDay; WORD wHour; WORD wMinute; WORD wSecond; WORD wMilliseconds; } SYSTEMTIME, *LPSYSTEMTIME; VOID GetLocalTime(LPSYSTEMTIME lpst);
Listing 2. Kernel32.java // Kernel32.java import com.sun.jna.*; import com.sun.jna.win32.*; public interface Kernel32 extends StdCallLibrary { 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; } void GetLocalTime (SYSTEMTIME result); }
Listing 3. LocalTime.java // LocalTime.java import com.sun.jna.*; public class LocalTime { public static void main (String [] args) { Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class); Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME (); lib.GetLocalTime (time); System.out.println ("Year is "+time.wYear); System.out.println ("Month is "+time.wMonth); System.out.println ("Day of Week is "+time.wDayOfWeek); System.out.println ("Day is "+time.wDay); System.out.println ("Hour is "+time.wHour); System.out.println ("Minute is "+time.wMinute); System.out.println ("Second is "+time.wSecond); System.out.println ("Milliseconds are "+time.wMilliseconds); } }
public class LockWorkStation { public static void main(String[] args) { User32 user32 = (User32) Native.loadLibrary("user32", User32.class); user32.LockWorkStation(); } }
class HANDLE extends PointerType { private boolean immutable; public HANDLE() { } public HANDLE(Pointer p) { setPointer(p); immutable = true; } public Object fromNative(Object nativeValue, FromNativeContext context) { Object o = super.fromNative(nativeValue, context); if (INVALID_HANDLE_VALUE.equals(o)) return INVALID_HANDLE_VALUE; return o; } public void setPointer(Pointer p) { if (immutable) throw new UnsupportedOperationException("immutable reference"); super.setPointer(p); } }
class POINT extends Structure { public int x, y; public POINT() { } public POINT(int x, int y) { this.x = x; this.y = y; } }
JNA也提供了一種保護機制.好比防止JNA出現異常不會致使JVM異常退出,默認是開啓這個功能的,開啓方式爲 System.setProperty(「jna.protected」,」true」); 記得要在JNA加載dll文件以前調用,而後try {...} catch(Throwable e)異常,不過你也不要指望太高,不要覺得加上這個就萬事大吉,出現」非法內存訪問」的時候仍是會一籌莫展。JNA也提供了一種保護機制.好比防止JNA 出現異常不會致使JVM異常退出,默認是開啓這個功能的,開啓方式爲 System.setProperty(「jna.protected」,」true」); 記得要在JNA加載dll文件以前調用,而後try {...} catch(Throwable e)異常,不過你也不要指望太高,不要覺得加上這個就萬事大吉,出現」非法內存訪問」的時候仍是會一籌莫展。
參考文章:
深刻淺出JNA—快速調用原生函數:http://www.doc88.com/p-31975835542.html
JNA—JNI終結者:http://blog.csdn.net/shendl/article/details/3589676
JNA的使用:http://xbgd.iteye.com/blog/1044864
深刻理解JNA—模擬C語言結構體:http://blog.csdn.net/shendl/article/details/3599849