JNA的使用

項目地址:http://jna.java.net/
API:http://jna.java.net/javadoc/overview-summary.html


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

https://maven.java.net/content/repositories/releases/net/java/dev/jna/jna-platform/4.0.0/jna-platform-4.0.0.jargit

 

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 類型

類型

原生表現

 

 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

 平臺依賴(3264位指針)

 

 <T>[] (基本類型的數組)

 pointer/array

3264位指針(參數/返回值)

鄰接內存(結構體成員)

 

 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

 平臺依賴(3264位整數)

 

 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中調用外部動態連接庫中的函數!

 

下面來解釋下這個程序。

(1)須要定義一個接口,繼承自Library StdCallLibrary

默認的是繼承Library ,若是動態連接庫裏的函數是以stdcall方式輸出的,那麼就繼承StdCallLibrary,好比衆所周知的kernel32庫。好比上例中的接口定義:

public interface CLibrary extends Library {

}


(2)接口內部定義

接口內部須要一個公共靜態常量:INSTANCE,經過這個常量,就能夠得到這個接口的實例,從而使用接口的方法,也就是調用外部dll/so的函數。

該常量經過Native.loadLibrary()這個API函數得到,該函數有2個參數:

  • 第一個參數是動態連接庫dll/so的名稱,但不帶.dll或.so這樣的後綴,這符合JNI的規範,由於帶了後綴名就不能夠跨操做系統平臺了。搜索動態連接庫路徑的順序是:先從當前類的當前文件夾找,若是沒有找到,再在工程當前文件夾下面找win32/win64文件夾,找到後搜索對應的dll文件,若是找不到再到WINDOWS下面去搜索,再找不到就會拋異常了。好比上例中printf函數在Windows平臺下所在的dll庫名稱是msvcrt,而在其它平臺如Linux下的so庫名稱是c。
  • 第二個參數是本接口的Class類型。JNA經過這個Class類型,根據指定的.dll/.so文件,動態建立接口的實例。該實例由JNA經過反射自動生成。
CLibrary INSTANCE = (CLibrary)
            Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
                               CLibrary.class);

接口中只須要定義你要用到的函數或者公共變量,不須要的能夠不定義,如上例只定義printf函數:

void printf(String format, Object... args);

注意參數和返回值的類型,應該和連接庫中的函數類型保持一致。

(3)調用連接庫中的函數

定義好接口後,就可使用接口中的函數即相應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;
}

而後編譯成一個dll文件(好比CDLL.dll),放到當前目錄下,而後編寫JNA程序調用便可:
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);

Java中的代碼
   UserStruct userStruct=new UserStruct ();
   userStruct.id=new NativeLong(100);
   userStruct.age=30;
   userStruct.name=new WString("奧巴馬");
   TestDll1.INSTANCE.sayUser(userStruct);

Structure說明

如今,咱們就在Java 中實現了對C 語言的結構體的模擬。這裏,咱們繼承了Structure 類,用這個類來模擬C 語言的結構體。

必須注意,Structure 子類中的公共字段的順序,必須與C 語言中的結構的順序一致。不然會報錯!由於,Java 調用動態連接庫中的C 函數,實際上就是一段內存做爲函數的參數傳遞給C函數。動態連接庫覺得這個參數就是C 語言傳過來的參數。同時,C 語言的結構體是一個嚴格的規範,它定義了內存的次序。所以,JNA 中模擬的結構體的變量順序絕對不能錯。若是一個Struct 有2 個int 變量。Int a, int b若是JNA 中的次序和C 中的次序相反,那麼不會報錯,可是數據將會被傳遞到錯誤的字段中去。
Structure 類表明了一個原生結構體。當Structure 對象做爲一個函數的參數或者返回
值傳遞時,它表明結構體指針。當它被用在另外一個結構體內部做爲一個字段時,它表明結構
體自己。
另外,Structure 類有兩個內部接口Structure.ByReference 和Structure.ByValue。這兩個接
口僅僅是標記,若是一個類實現Structure.ByReference 接口,就表示這個類表明結構體指針
若是一個類實現Structure.ByValue 接口,就表示這個類表明結構體自己
使用這兩個接口的實現類,能夠明肯定義咱們的Structure 實例表示的是結構體的指針
仍是結構體自己。
上面的例子中,因爲Structure 實例做爲函數的參數使用,所以是結構體指針。因此這

裏直接使用了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;
};


JNA 中能夠這樣模擬:
public static class CompanyStruct extends Structure{
public NativeLong id;
public WString name;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
public int count;
}


這裏,必須給users 字段賦值,不然不會分配100 個UserStruct 結構體的內存,這樣JNA
中的內存大小和原生代碼中結構體的內存大小不一致,調用就會失敗。


例:結構體內部能夠包含結構體對象的指針的數組

struct CompanyStruct2{
long id;
wchar_t* name;
UserStruct* users[100];
int count;
};


JNA 中能夠這樣模擬:
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);
執行測試代碼,報錯了。這是怎麼回事?
考察JNI 技術,咱們發現Java 調用原生函數時,會把傳遞給原生函數的Java 數據固定
在內存中,這樣原生函數才能夠訪問這些Java 數據。對於沒有固定住的Java 對象,GC 能夠
刪除它,也能夠移動它在內存中的位置,以使堆上的內存連續。若是原生函數訪問沒有被固
定住的Java 對象,就會致使調用失敗。
固定住哪些java 對象,是JVM 根據原生函數調用自動判斷的。而上面的CompanyStruct2
結構體中的一個字段是UserStruct 對象指針的數組,所以,JVM 在執行時只是固定住了
CompanyStruct2 對象的內存,而沒有固定住users 字段引用的UserStruct 數組。所以,形成
了錯誤。
咱們須要把users 字段引用的UserStruct 數組的全部成員也所有固定住,禁止GC 移動
或者刪除。
若是咱們執行了pUserStruct.write();這段代碼,那麼就能夠成功執行上述代碼。
Structure 類的write()方法會把結構體的全部字段固定住,使原生函數能夠訪問。


案例一:獲取本地時間(Get local time)

若是你在Java Native Access 首頁 看過「JNA如何入門」,你就會知道一個很簡單的關於調用Windows 平臺下的API函數:GetSystemTime() 的JNA示例。這個不完整的例子只是展現了JNA的基本特色。(在例子的基礎上,我作了一個更完整的基於Windows的例子來介紹JNA)我在Windows平臺下完善了這個例子來介紹JNA。

第一例子基於Windows GetLocalTime() API函數返回本地當前的時間和日期。和GetSystemTime()不一樣的是,返回的時間/日期是協調通用時間(UTC)格式的,GetLocalTime()返回的時間/日期信息的格式是根據當前時區來表示。

在一個Java程序中使用JNA調用GetLocalTime,你須要知道這個函數所在的Windows平臺下的動態連接庫(DLL)的名稱(和可能所在的地理區域)。咱們發現GetLocalTime()和GetSystemTime在同一個DLL文件中:kernel32.dll。你還須要知道GetLocalTime()在C語言環境中的申明。申明以下Listing 1:

Listing 1. GetLocalTime在C語言中的申明

typedef struct
{
   WORD wYear;
   WORD wMonth;
   WORD wDayOfWeek;
   WORD wDay;
   WORD wHour;
   WORD wMinute;
   WORD wSecond;
   WORD wMilliseconds;
}
SYSTEMTIME, *LPSYSTEMTIME;

VOID GetLocalTime(LPSYSTEMTIME lpst);

這個基於C語言的申明代表傳到這個函數的參數數目和類型。在這個例子中,只有一個參數---一個指向Windows SYSTEMTIME結構體的指針。並且,每一個結構體成員的類型是16bit長度的無符號整型。根據這些信息,你可以建立一個徹底描述GetLocalTime()函數的接口,如Listing 2中所示:

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);
}




Kernel32 接口(The Kernel32 interface)

由於JNA使用經過一個接口來訪問某個庫中的函數,Listing 2表示了一個描述GetLocalTime()的接口。根據約定,我把接口命名爲Kernel32是由於GetLocalTime()在Windows的kernel32.dll庫。

這個接口必須繼承com.sun..jna.Library接口。由於Windows API函數遵循stdcall調用協議(stdcall calling convention),爲Windows API申明的接口也必須繼承com.sun.jna.win32. StdCallLibrary接口。所以這個接口共繼承了Library 和 com.sun.jna.win32.StdCall兩個接口。

在前面,你已經知道了GetLocalTime() 須要一個指向SYSTEMTIME結構體的指針做爲它惟一的參數。由於Java不支持指針,JNA是經過申明一個com.sun.jna.Structure的子類來代替的。根據java文檔中抽象類的概念,在參數環境中,Structure至關於C語言的struct*。

在SYSTEMTIME類中的字段和C結構體中的相對應的屬性字段的順序是一一對應的。保證字段順序的一致性是很是重要的。例如,我發現交換wYear和wMonth會致使wYear和wMonth值互換。

每一個字段在java中是short integer類型的。按照JNA首頁上 「默認類型映射」章節給出的提示,這個short integer分配類型是正確。然而,咱們應該知道一個重要的區別:Windows平臺下的WORD類型等同於C語言環境中的16-bit的無符號的short integer,而java中short integer是16-bit有符號的short integer。

一個類型映射的問題

經過比較一個API 函數返回的整型值,你會發現Windows/C 語言的無符號整型和Java語言的有符號整型的JNA類型映射是有問題的。在比較的過程當中,若是你不細心,那麼錯誤的執行過程可能致使決定性狀況。致使這種後果是由於忘記任何數值的符號位的肯定是根據:在無符號整型的狀況下會被解釋爲正號,而在有符號整型的進制中被理解爲負號的。

經過Kernel32獲取本地時間(Access the local time with Kernel32)

JNA首頁上的GetSystemTime()示例已經代表必須使用預先申明的接口爲本地庫分配一個實例對象。你能夠經過com.sun.jna.Native類中靜態公用方法loadLibrary(String name, Class interfaceClass)來完成上述的目標。Listing 3 所示:

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);
   }
}




Listing 3 執行Kernel32 lib = (Kernel32) Native.loadLibrary ("kernel32", Kernel32.class);來分配一個Kernel32實例對象而且裝載kernel32.dll。由於kernel32.dll是Windows平臺下標準的dll文件,因此不要指定訪問這個庫的路徑。然而,若是找不到這個dll文件,loadLibrary()會拋出一個UnsatisfiedLinkError異常。

Kernel32.SYSTEMTIME time = new Kernel32.SYSTEMTIME ();建立了一個SYSTEMTIME結構體的示例。初始化後下面是lib.GetLocalTime (time);,這句話使用本地的時間/日期來給這個實例賦值。幾個System.out.println()語句是輸出這些值。

編譯和運行這個應用(Compile and run the application)

這部分很容易。假設jna.jar、Kernel32.java和LocalTime.java是放在當前文件夾中,調用java –cp jna.jar;. LocalTime.java來編譯這個應用的源代碼。若是在Windows平臺下,調用invoke java –cp jna.jar;. LocalTime 來運行這個應用。你能夠獲得相似與Listing 4的輸出結果:

Listing 4. 從LocalTime.java生成的輸出

Year is 2007
Month is 12
Day of Week is 3
Day is 19
Hour is 12
Minute is 35
Second is 13
Milliseconds are 156

案例二:調用本地的
使用JNA的調用本地方法的時候須要自定義數據結構,下面咱們經過調用Windows提供的的鎖定工做站方法來了解一下JNA。

    一、首先查詢Windows API知道鎖定工做站的方法在user32.dll中定義,接下來定義一個接口來繼承JNA的Library.java接口,用做聲明DLL庫文件,這裏咱們就把它命名爲User32:

    public interface User32 extends Library {}

複製代碼
二、查詢user32.dll提供的API得知鎖定工做方法是LockWorkStation,返回類型是boolean型,在User32.java中新增相應的方法:

    boolean LockWorkStation();

複製代碼
這樣咱們的User32.java這個類就定義好了。接下來咱們寫測試程序進行調用。

    三、編寫測試類好比LockWorkStation.java,首先經過JNA的Native類加載對應的dll:

    User32 user32 = (User32) Native.loadLibrary("user32", User32.class);

複製代碼
而後就能夠調用LockWorkStation方法了,完整代碼以下:

 
   public class LockWorkStation {
        public static void main(String[] args) {
          User32 user32 = (User32) Native.loadLibrary("user32", User32.class);
          user32.LockWorkStation();
        }
    }



複製代碼
這裏說明一下loadLibrary方法中第一個參數是須要加載的dll文件名稱,第二個參數的做用是讓JNA使用這個類的加載器去加載DLL文件,加載順序是,先從Users.class類的當前文件夾找,若是沒有找到,再在工程當前文件夾下面找win32/win64文件夾,找到後搜索對應的dll文件,若是找不到再到WINDOWS下面去搜索,再找不到就會拋異常了。以TWAINDSM.dll將文件放到工程的根文件夾能夠按照下面這個格式放:

附件: jnaexplorer.JPG

  上面的User32定義的是dll庫文件,有時會碰到好比HANDLE、POINT、WORD和MSG等數據類型,有些數據類型JNA中沒有提供,須要本身定義,根據做用的不一樣,定義的時候繼承的父類也不同,好比HANDLE定義方法是:

  
 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);
            }
        }


        HANDLE被定義爲類型安全的指針。而POINT用做表示座標,不須要這麼複雜,定義方式爲:
  
 class POINT extends Structure {
            public int x, y;
            public POINT() { }
            public POINT(int x, int y) { this.x = x; this.y = y; }
      }


使用JNA的過程當中也不必定會一路順風,好比會拋出」非法內存訪問」,這時候檢查一下變量是否==null。還有內存對齊的問題,當從內存中獲取圖片信息進行保存的時候,若是內存對齊處理很差,就會拋出很嚴重的異常,致使JVM異常退出,JNA提供了四種內存對齊的方式,分別是:ALIGN_DEFAULT、ALIGN_NONE、ALIGN_GNUC和ALIGN_MSVC。ALIGN_DEFAULT採用平臺默認的對齊方式(推薦);ALIGN_NONE是不採用對齊方式;ALIGN_GNUC爲針對linux/gcc操做系統的對齊方式。ALIGN_MSVC爲針對win32/msvc架構的內存對齊方式。

    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

相關文章
相關標籤/搜索