JNA 教程

前言

只要你用過了 JNA (java native access) , 那你可能就再也不想用 JNI 了

實際上, JNA 搞定了 JNI 中最麻煩的數據類型映射, 可以讓我們進行高效的開發, 不用再去寫各種的轉換接口.

  • char*
  • string
  • 數組
  • 結構體

上面的數據類型它都支持

可能有人會問 JNA 能完全代替 JNI 么? 不能, JNA只能實現Java訪問C函數,如果你想實現C語言調用 Java 代碼, 你還是需要使用 JNI 技術。

但其實在很大程度上已經夠用了, 因為在很多應用領域都是由 C++算法工程師提供庫 (.so/.dll) , java 工程師只需要負責調用就可以了

JNA 有什么用? 吹一波?

就一個 簡化 jni 開發

想想當年寫 jni 的時候, 下面這樣的代碼要寫幾百行, 你就知道我有多痛苦了

1570852575274

當然最痛苦的還不是這個

在開發中, 作為一個JAVA 程序員你會遇到各種各樣奇葩的問題

還包含一部分你無法理解的C++問題

  • 類型轉換
  • linux windows多環境編譯
  • 內存泄漏
  • 異常處理
  • 各種找不到原因的報錯
  • debug 困難
  • ...

而如果使用 JNA, 你可能只需要這樣:

  • 你不需要通過 javah 生成頭文件, 不需要給它寫實現

  • 不需要在 windows/linux 環境各自編譯成 .dll/.so 來調用真正的函數

  • 只需要聲明一個接口, 其他的事情讓 JNA 做好就行

public class HelloWorld {
    public interface CLibrary extends Library {
        CLibrary INSTANCE = 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");
    }
}
// 該源碼來自 https://github.com/java-native-access/jna/blob/master/www/GettingStarted.md

哪些項目使用了 JNA

  • Apache Cassandra:大型NoSQL數據存儲
  • JetBrains的IntelliJ IDEA
  • NetBeans IDE

...

每天都在用的 IDEA 都用了 JNA =.= 你還怕什么?

數據類型映射

了解一下 C -> java 的數據類型映射, 幫助你更好的完成之后的練習

Native Type Size Java Type Common Windows Types
char 8-bit integer byte BYTE, TCHAR
short 16-bit integer short WORD
wchar_t 16/32-bit character char TCHAR
int 32-bit integer int DWORD
int boolean value boolean BOOL
long 32/64-bit integer NativeLong LONG
long long 64-bit integer long __int64
float 32-bit FP float
double 64-bit FP double
char* C string String LPCSTR
void* pointer Pointer LPVOID, HANDLE, LPXXX

from JNA - Default Type Mappings

你可能會發現表中沒有 native 的 boolean 類型, 默認上 boolean = true 會被默認映射成 -1(int), 在 C 中打印出來是 255. 所以我一般在定義接口時, 會避免使用 bool . 如果想自定義 boolean 映射, 可以參考 JNA maps Java boolean to -1 integer?

代碼示例

本代碼示例基于 64位 win10 + vs 2019

如果遇到 dll 依賴問題( UnsatisfiedLinkError ), 請下載 micro soft vc 運行庫 或者使用 Dependency walker 查看 dll 依賴缺失情況

可以 clone 一下我的項目:

java 部分: https://github.com/giraffe-tree/jna-func

c++ 部分(vs 項目): https://github.com/giraffe-tree/jna-c

max

先來個簡單的例子

先將下面的依賴加入你的 java maven 項目

<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna</artifactId>
    <version>5.3.1</version>
</dependency>
// c++  需要在 vs 中編譯成 dll
int max(int num1, int num2) {
    return num1 > num2 ? num1 : num2;
}
// java
public interface JnaLibrary extends Library {
     // JNA 為 dll 名稱
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    int max(int a, int b);
}

將編譯好的 JNA.dll 放入 resources 文件夾下的 win32-x86-64 目錄中 (我使用的是 64位 windows), JNA 會自動到 win32-x86-64 中去找 JNA.dll

運行 main 函數即可

public static void main(String[] args) {
    int max = JnaLibrary.INSTANCE.max(100, 200);
    // out: 200
    System.out.println(max);
}

primitive array

 // java
public interface JnaLibrary extends Library {
     // JNA 為 dll 名稱
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    void testArray(short[] vals, int len);
}
// c++
void testArray(uint16_t* vals, int len) {
    for (int j = 0; j < len; j++) {
        printf("vals[%d]: %d \n", j, vals[j]);
    }
}
JnaLibrary.INSTANCE.testArray(new short[]{1, 2, 3, 4}, 4);
// out:
vals[0]: 1 
vals[1]: 2 
vals[2]: 3 
vals[3]: 4 

值傳遞與引用傳遞

通過值傳遞對象的時候需要注意

  1. 對象需要繼承 Structure , 且它的屬性必須為 public
    • Structure fields corresponding to native struct fields must be public. If your structure is to have no fields of its own, it must be declared abstract.
  2. JNA 有時候會判斷錯誤, 導致原本的值傳遞, 變成引用傳遞, 從而報出Invalid memory access 的異常, 這時候最好實現一下 Structure.ByValue 接口
  1. FieldOrder 需要按順序寫, 否則會報出 Invalid memory access
// java
public interface JnaLibrary extends Library {
     // JNA 為 dll 名稱
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    // 實際測試下來 void printUser(User.ByValue user); 也是可以的
    void printUser(User user);
    void printUserRef(User user);

@Structure.FieldOrder({"name", "height", "weight"})
public static class User extends Structure {

public static class UserValue extends User implements Structure.ByValue {

    public UserValue(String name, int height, double weight) {
        super(name, height, weight);
        }
    }

    public User(String name, int height, double weight) {
        this.name = name;
        this.height = height;
        this.weight = weight;
    }

        public String name;
        public int height;
        public double weight;
    }
}
// .h
struct User {
    char* name;
    int height;
    double weight;
};
void printUser(User user);
void printUserRef(User& user);

// cpp
void printUser(User user) {
    printf("printUser user: %s height: %d weight: %.2f \n", user.name, user.height, user.weight);
}
void printUserRef(User& user) {
    printf("printUserRef user: %s height: %d weight: %.2f \n", user.name, user.height, user.weight);
}
JnaLibrary.User.UserValue user1 = new JnaLibrary.User.UserValue("user1", 186, 65.2);
JnaLibrary.INSTANCE.printUserRef(user1);
JnaLibrary.INSTANCE.printUser(user1);
// out:
printUserRef user: user1 height: 186 weight: 65.20 
printUser user: user1 height: 186 weight: 65.20

Pointer

// java
public interface JnaLibrary extends Library {
    // JNA 為 dll 名稱
    JnaLibrary INSTANCE = Native.load("JNA", JnaLibrary.class);
    
    void testStruct(ArrInfo arrInfo);
    
    @Structure.FieldOrder({"vals", "len"})
        public static class ArrInfo extends Structure {
            public Pointer vals;
            public int len;

            public ArrInfo(Pointer vals, int len) {
            this.vals = vals;
            this.len = len;
        }
    }
}
// .h
struct ArrInfo
{
    uint16_t* vals;
    int len;
};
void testStruct(ArrInfo arrInfo);

// cpp
void testStruct(ArrInfo arrInfo) {
    for (int j = 0; j < arrInfo.len; j++) {
        printf("arrInfo[%d]: %d \n", j, arrInfo.vals[j]);
    }
}
// java main test
int len = 3;
int shortSize = Native.getNativeSize(Short.class);
Pointer pointer = new Memory(len * shortSize);
for (int i = 0; i < len; i++) {
    pointer.setShort(shortSize * i, (short) i);
}
JnaLibrary.ArrInfo arrInfo = new JnaLibrary.ArrInfo(pointer, len);
JnaLibrary.INSTANCE.testStruct(arrInfo);
// out
arrInfo[0]: 0 
arrInfo[1]: 1 
arrInfo[2]: 2 

關于 JNA 調試

雖然 JNA 相比于 JNI 好用很多, 但我在使用的過程中還是遇到一些"坑"

這些 bug 常常很難直接找出, JNA 統一都報了一個 Invalid memory access , 導致我們找不到真正錯誤的原因. 這時候就需要調試了

我使用的開發環境是 IDEA + jdk8 + VS2019

步驟

先簡單講下步驟, 具體圖文可以看下面的實戰

  1. 通過 vs 編譯 debug 版本的 dll , 放入 java 項目指定的目錄下 (我這里是resources/win32-x86-64/JNA.dll)
  2. 啟動 java 程序, 停止在你指定的斷點上
  3. jps -l 找到 java 程序的 pid
  4. 在 vs 中 ctrl+alt+p 附加到進程, 選擇剛剛找到的 pid, 點擊附加
    • 其實一般是最上面的那個 java 進程, 就是你剛剛運行起來的那個
    • 點擊 j 可以快速查找
  5. 在 vs 中設置一個斷點
  6. 在 idea 中繼續debug, 它會跳到 vs 的debug 界面中
  7. 在 vs 中點擊繼續就可以接著調試啦

實戰

在 idea 中進入調試

1572006969955

在下圖中, jps -l 找到 java 程序的 pid = 10560

1572006665036

在 vs 中 ctrl+alt+p 附加到進程, 選擇剛剛找到的 pid, 點擊附加

1572006913302

此時斷點不會命中

1572008782675

接著在 idea 中點擊繼續, 然后回到 vs 界面, 我們發現 斷點被觸發了

1572008818053

查看局部變量

1572008861802

yeah, 調試完成 =.=

其他問題

C++ 中 printf 控制臺打印缺失

目前測試下來, 可能存在 java 主線程停止, 但 C++ print 緩存區未被清空/不輸出的情況, 導致控制臺打印內容缺失

在這種情況下, 請延長 java 主線程運行的時間.

在C++調試時, 遇到未加載 jvm.pdb

目前測試下來, 在 vs 中 debug 繼續后會報出一個 未加載 jvm.pdb , 我確實沒有找到這個文件

但還可以繼續調試, 沒啥大問題

不過什么時候來個調試 openjdk 想想應該蠻有趣的 哈哈

Invalid memory access

這個問題怎么說呢, JNA 好多地方都能報出這個錯誤, 我遇到這個錯誤時, 大部分都是我的java 參數 -> C++ 參數的映射問題

包括參數類型寫錯, 順序寫錯等, 仔細檢查映射關系就能解決

dll 兼容性問題

java.lang.UnsatisfiedLinkError: 找不到指定的模塊。

這個錯誤我遇到過的情況分兩種

  1. 我調用的 dll 沒有放進指定目錄
    • 比如我的平臺是 win32-x86-64, 但是沒有放入這個目錄中
    • 這種情況下, jna 會提示找不到指定目錄的資源文件比較好解決
  2. 我調用的 dll 依賴的其他 dll 沒有找到
    • 這個情況, 我在 win10 下編譯, 然后在 windower server 2012 上運行時遇到過
    • 通過解決方案是這樣
      1. 通過 Dependency walker 查看 dll 依賴缺失情況
      2. 補全缺失的 dll
    • 當時我的情況是 缺失了 vc140, 然后我下載 micro soft vc 運行庫 就解決了這個問題

個人網站文章鏈接: https://giraffetree.me/2019/10/28/jna_tutorial/ 歡迎留言~

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,333評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,491評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,263評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,946評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,708評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,409評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,939評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,774評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,209評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,641評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,872評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,650評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容