JNA--(Java跨語言調(diào)用)訪問Java外部接口

文章開始之前首先介紹下.dll/.so文件参淫,我們知道用c/c++編寫的程序如果用于Windows平臺(tái)則編譯為xxx.dll(dynamic link library)文件,Linux平臺(tái)則編譯為libxxx.so(shared object)文件愧杯。

官網(wǎng)
jna--- javadoc
里面介紹了C Type 對(duì)應(yīng)到 Java Type 數(shù)據(jù)類型

image.png

1. JNA簡(jiǎn)單介紹

先說JNI(Java Native Interface)吧涎才,有過不同語言間通信經(jīng)歷的一般都知道,它允許Java代碼和其他語言(尤其C/C++)寫的代碼進(jìn)行交互力九,只要遵守調(diào)用約定即可耍铜。首先看下JNI調(diào)用C/C++的過程,注意寫程序時(shí)自下而上畏邢,調(diào)用時(shí)自上而下。

image

可 見步驟非常的多检吆,很麻煩舒萎,使用JNI調(diào)用.dll/.so共享庫都能體會(huì)到這個(gè)痛苦的過程。如果已有一個(gè)編譯好的.dll/.so文件,如果使用JNI技 術(shù)調(diào)用臂寝,我們首先需要使用C語言另外寫一個(gè).dll/.so共享庫章鲤,使用SUN規(guī)定的數(shù)據(jù)結(jié)構(gòu)替代C語言的數(shù)據(jù)結(jié)構(gòu),調(diào)用已有的 dll/so中公布的函 數(shù)咆贬。然后再在Java中載入這個(gè)庫dll/so败徊,最后編寫Java native函數(shù)作為鏈接庫中函數(shù)的代理。經(jīng)過這些繁瑣的步驟才能在Java中調(diào)用 本地代碼掏缎。因此皱蹦,很少有Java程序員愿意編寫調(diào)用dll/.so庫中原生函數(shù)的java程序。這也使Java語言在客戶端上乏善可陳眷蜈,可以說JNI是 Java的一大弱點(diǎn)沪哺!

那么JNA是什么呢?

JNA(Java Native Access)是一個(gè)開源的Java框架酌儒,是Sun公司推出的一種調(diào)用本地方法的技術(shù)辜妓,是建立在經(jīng)典的JNI基礎(chǔ)之上的一個(gè)框架。之所以說它是JNI的替 代者忌怎,是因?yàn)镴NA大大簡(jiǎn)化了調(diào)用本地方法的過程籍滴,使用很方便,基本上不需要脫離Java環(huán)境就可以完成榴啸。

如果要和上圖做個(gè)比較孽惰,那么JNA調(diào)用C/C++的過程大致如下:

image

可以看到步驟減少了很多,最重要的是我們不需要重寫我們的動(dòng)態(tài)鏈接庫文件插掂,而是有直接調(diào)用的API灰瞻,大大簡(jiǎn)化了我們的工作量。

JNA只需要我們寫Java代碼而不用寫JNI或本地代碼辅甥。功能相對(duì)于Windows的Platform/Invoke和Python的ctypes酝润。

2. JNA技術(shù)原理

JNA使用一個(gè)小型的JNI庫插樁程序來動(dòng)態(tài)調(diào)用本地代碼。開發(fā)者使用Java接口描述目標(biāo)本地庫的功能和結(jié)構(gòu)璃弄,這使得它很容易利用本機(jī)平臺(tái)的功能要销,而不會(huì)產(chǎn)生多平臺(tái)配置和生成JNI代碼的高開銷。這樣的性能夏块、準(zhǔn)確性和易用性顯然受到很大的重視疏咐。

此外,JNA包括一個(gè)已與許多本地函數(shù)映射的平臺(tái)庫脐供,以及一組簡(jiǎn)化本地訪問的公用接口浑塞。

注意:

JNA是建立在JNI技術(shù)基礎(chǔ)之上的一個(gè)Java類庫,它使您可以方便地使用java直接訪問動(dòng)態(tài)鏈接庫中的函數(shù)政己。

原來使用JNI酌壕,你必須手工用C寫一個(gè)動(dòng)態(tài)鏈接庫,在C語言中映射Java的數(shù)據(jù)類型。

JNA中卵牍,它提供了一個(gè)動(dòng)態(tài)的C語言編寫的轉(zhuǎn)發(fā)器果港,可以自動(dòng)實(shí)現(xiàn)Java和C的數(shù)據(jù)類型映射,你不再需要編寫C動(dòng)態(tài)鏈接庫糊昙。

也許這也意味著辛掠,使用JNA技術(shù)比使用JNI技術(shù)調(diào)用動(dòng)態(tài)鏈接庫會(huì)有些微的性能損失。但總體影響不大释牺,因?yàn)镴NA也避免了JNI的一些平臺(tái)配置的開銷萝衩。

3. JNA簡(jiǎn)單使用

JNA的項(xiàng)目已遷移至Github,目前最新版本是4.1.0船侧,已有打包好的jar文件可供下載欠气。

JNA把一個(gè).dll/.so文件看做是一個(gè)Java接口,下面以一個(gè)簡(jiǎn)單的實(shí)例來說明怎么使用镜撩。

當(dāng)然要從最經(jīng)典的HelloWorld開始预柒,我們調(diào)用C的printf函數(shù)打印出“HelloWorld”(官方的例子),前提是已將jar包加入你的classpath袁梗。



package com.sun.jna.examples;

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

運(yùn)行程序宜鸯,如果沒有帶參數(shù)則只打印出“Hello, World”,如果帶了參數(shù)遮怜,則會(huì)打印出所有的參數(shù)淋袖。

很簡(jiǎn)單,不需要寫一行C代碼锯梁,就可以直接在Java中調(diào)用外部動(dòng)態(tài)鏈接庫中的函數(shù)即碗!

下面來解釋下這個(gè)程序。

(1)需要定義一個(gè)接口陌凳,繼承自LibraryStdCallLibrary

默認(rèn)的是繼承Library剥懒,如果動(dòng)態(tài)鏈接庫里的函數(shù)是以stdcall方式輸出的,那么就繼承StdCallLibrary合敦,比如眾所周知的kernel32庫初橘。比如上例中的接口定義:

public interface CLibrary extends Library {

}

(2)接口內(nèi)部定義

接口內(nèi)部需要一個(gè)公共靜態(tài)常量:INSTANCE,通過這個(gè)常量充岛,就可以獲得這個(gè)接口的實(shí)例保檐,從而使用接口的方法,也就是調(diào)用外部dll/so的函數(shù)崔梗。

該常量通過Native.loadLibrary()這個(gè)API函數(shù)獲得夜只,該函數(shù)有2個(gè)參數(shù):

  • 第 一個(gè)參數(shù)是動(dòng)態(tài)鏈接庫dll/so的名稱,但不帶.dll或.so這樣的后綴蒜魄,這符合JNI的規(guī)范扔亥,因?yàn)閹Я撕缶Y名就不可以跨操作系統(tǒng)平臺(tái)了爪膊。搜索動(dòng)態(tài)鏈 接庫路徑的順序是:先從當(dāng)前類的當(dāng)前文件夾找,如果沒有找到砸王,再在工程當(dāng)前文件夾下面找win32/win64文件夾,找到后搜索對(duì)應(yīng)的dll文件峦阁,如果 找不到再到WINDOWS下面去搜索谦铃,再找不到就會(huì)拋異常了。比如上例中printf函數(shù)在Windows平臺(tái)下所在的dll庫名稱是msvcrt榔昔,而在 其它平臺(tái)如Linux下的so庫名稱是c驹闰。
  • 第二個(gè)參數(shù)是本接口的Class類型。JNA通過這個(gè)Class類型撒会,根據(jù)指定的.dll/.so文件嘹朗,動(dòng)態(tài)創(chuàng)建接口的實(shí)例。該實(shí)例由JNA通過反射自動(dòng)生成诵肛。
CLibrary INSTANCE = (CLibrary)
            Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"),
                               CLibrary.class);

接口中只需要定義你要用到的函數(shù)或者公共變量屹培,不需要的可以不定義爸邢,如上例只定義printf函數(shù):

<pre class="java" style="margin-top: 0px; margin-bottom: 0px; color: rgb(75, 75, 75); font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">void printf(String format, Object... args);</pre>

注意參數(shù)和返回值的類型欧啤,應(yīng)該和鏈接庫中的函數(shù)類型保持一致。

(3)調(diào)用鏈接庫中的函數(shù)

定義好接口后绽慈,就可以使用接口中的函數(shù)即相應(yīng)dll/so中的函數(shù)了薛训,前面說過調(diào)用方法就是通過接口中的實(shí)例進(jìn)行調(diào)用媒吗,非常簡(jiǎn)單,如上例中:

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使用的簡(jiǎn)單例子乙埃,可能有人認(rèn)為這個(gè)例子太簡(jiǎn)單了闸英,因?yàn)槭褂玫氖窍到y(tǒng)自帶的動(dòng)態(tài)鏈接庫,應(yīng)該還給出一個(gè)自己實(shí)現(xiàn)的庫函數(shù)例子介袜。其實(shí)我覺得這個(gè)完全沒有必要甫何,這也是JNA的方便之處,不像JNI使用用戶自定義庫時(shí)還得定義一大堆配置信息米酬,對(duì)于JNA來說沛豌,使用用戶自定義庫與使用系統(tǒng)自帶的庫是完全一樣的方法,不需要額外配置什么信息赃额。比如我在Windows下建立一個(gè)動(dòng)態(tài)庫程序:

#include "stdafx.h"

extern "C"_declspec(dllexport) int add(int a, int b);

int add(int a, int b) {
    return a + b;
}

然后編譯成一個(gè)dll文件(比如CDLL.dll)加派,放到當(dāng)前目錄下,然后編寫JNA程序調(diào)用即可:

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

4. JNA技術(shù)難點(diǎn)

有過跨語言跳芳、跨平臺(tái)開發(fā)的程序員都知道芍锦,跨平臺(tái)、語言調(diào)用的難點(diǎn)飞盆,就是不同語言之間數(shù)據(jù)類型不一致造成的問題娄琉。絕大部分跨平臺(tái)調(diào)用的失敗次乓,都是這個(gè)問題造成的。關(guān)于這一點(diǎn)孽水,不論何種語言票腰,何種技術(shù)方案,都無法解決這個(gè)問題女气。JNA也不例外杏慰。

上面說到接口中使用的函數(shù)必須與鏈接庫中的函數(shù)原型保持一致,這是JNA甚至所有跨平臺(tái)調(diào)用的難點(diǎn)炼鞠,因?yàn)镃/C++的類型與Java的類型是不一樣的缘滥,你必須轉(zhuǎn)換類型讓它們保持一致,比如printf函數(shù)在C中的原型為:

<pre class="cpp" style="margin-top: 0px; margin-bottom: 0px; color: rgb(75, 75, 75); font-size: 13px; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; widows: 2; word-spacing: 0px; -webkit-text-stroke-width: 0px; background-color: rgb(255, 255, 255); text-decoration-style: initial; text-decoration-color: initial;">void printf(const char *format, [argument]);</pre>

你不可能在Java中也這么寫谒主,Java中是沒有char *指針類型的朝扼,因此const char *轉(zhuǎn)到Java下就是String類型了。

這就是類型映射(Type Mappings)霎肯,JNA官方給出的默認(rèn)類型映射表如下:

image

C擎颖,java和操作系統(tǒng)數(shù)據(jù)類型對(duì)應(yīng)表 (相對(duì)比較新)

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 LPTCSTR
void* pointer Pointer LPVOID, HANDLE, LPXXX
pointer Buffer/Pointer 平臺(tái)依賴(32 或64 位指針)
pointer/array <T>[] (基本類型的數(shù)組) 32 或64 位指針(參數(shù)/返回值) 鄰接內(nèi)存(結(jié)構(gòu)體成員)
wchar_t* WString \0 結(jié)束的數(shù)組(unicode)
char** String[] \0 結(jié)束的數(shù)組的數(shù)組
wchar_t** WString[] \0 結(jié)束的寬字符數(shù)組的數(shù)組
struct*/struct Structure 指向結(jié)構(gòu)體的指針(參數(shù)或返回值) (或者明確指定是結(jié)構(gòu)體指針)/結(jié)構(gòu)體(結(jié)構(gòu)體的成員) (或者明確指定是結(jié)構(gòu)體)
union Union 等同于結(jié)構(gòu)體
Structure[] struct[] 結(jié)構(gòu)體的數(shù)組,鄰接內(nèi)存
<T> (*fp)() Callback Java 函數(shù)指針或原生函數(shù)指針
varies NativeMapped 依賴于定義
pointer PointerType 和Pointer 相同

還有很多其它的類型映射观游,需要的請(qǐng)到JNA官網(wǎng)查看肠仪。

另外,JNA還支持類型映射定制备典,比如有的Java中可能找不到對(duì)應(yīng)的類型(在Windows API中可能會(huì)有很多類型异旧,在Java中找不到其對(duì)應(yīng)的類型),JNA中TypeMapper類和相關(guān)的接口就提供了這樣的功能提佣。

5. JNA能完全替代JNI嗎吮蛹?

這可能是大家比較關(guān)心的問題,但是遺憾的是拌屏,JNA是不能完全替代JNI的潮针,因?yàn)橛行┬枨筮€是必須求助于JNI。

使用JNI技術(shù)倚喂,不僅可以實(shí)現(xiàn)Java訪問C函數(shù)每篷,也可以實(shí)現(xiàn)C語言調(diào)用Java代碼。

而JNA只能實(shí)現(xiàn)Java訪問C函數(shù)端圈,作為一個(gè)Java框架焦读,自然不能實(shí)現(xiàn)C語言調(diào)用Java代碼。此時(shí)舱权,你還是需要使用JNI技術(shù)矗晃。

JNI是JNA的基礎(chǔ),是Java和C互操作的技術(shù)基礎(chǔ)宴倍。有時(shí)候张症,你必須回歸到基礎(chǔ)上來仓技。

6 查看dll 文件

depends簡(jiǎn)介

depends是一款可以查看一個(gè)exe文件或dll文件需要依賴哪些dll文件的工具,比如我們生產(chǎn)了一個(gè)exe程序俗他,顯然在我們的開發(fā)環(huán)境下是可以執(zhí)行這個(gè)exe程序的脖捻,但是換一個(gè)環(huán)境還可以執(zhí)行嗎?這就不見得了兆衅。所以我們需要知道這個(gè)exe程序都依賴哪些動(dòng)態(tài)鏈接庫郭变,以保證程序離開了開發(fā)環(huán)境還可以正常運(yùn)行。

下載與安裝

在vs2008之后涯保,這個(gè)軟件就被移除了,所以我們需要在這里單獨(dú)下載它周伦。這個(gè)軟件灰常簡(jiǎn)單夕春,嚴(yán)格意義上說其實(shí)沒有安裝的過程,下載下來之后可以直接運(yùn)行专挪,而且壓縮包中提供了依賴的dll及志。

主要參考:

Java跨語言調(diào)用,使用JNA訪問Java外部接口
JNA (Java 本地訪問)理論概述與入門
dll依賴查看工具-depends

Java 調(diào)用 C/C++ 之 JNA 系列實(shí)戰(zhàn)篇 —— 起步 (一)

系統(tǒng)認(rèn)識(shí)jna:

JNA 基礎(chǔ)篇<一> 初識(shí)JNA

JNA 基礎(chǔ)篇<二> 結(jié)構(gòu)體

JNA中級(jí)篇 回調(diào)函數(shù)詳解

解決JNA動(dòng)態(tài)加載jar中dll問題

6. 參考文獻(xiàn)

(1)JNA—JNI終結(jié)者

(2)C++DLL編程詳解

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末寨腔,一起剝皮案震驚了整個(gè)濱河市速侈,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌迫卢,老刑警劉巖倚搬,帶你破解...
    沈念sama閱讀 218,122評(píng)論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異乾蛤,居然都是意外死亡每界,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門家卖,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眨层,“玉大人,你說我怎么就攤上這事上荡∨坑#” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵酪捡,是天一觀的道長(zhǎng)叁征。 經(jīng)常有香客問我,道長(zhǎng)逛薇,這世上最難降的妖魔是什么航揉? 我笑而不...
    開封第一講書人閱讀 58,636評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮金刁,結(jié)果婚禮上帅涂,老公的妹妹穿的比我還像新娘议薪。我一直安慰自己,他們只是感情好媳友,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評(píng)論 6 392
  • 文/花漫 我一把揭開白布斯议。 她就那樣靜靜地躺著,像睡著了一般醇锚。 火紅的嫁衣襯著肌膚如雪哼御。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,541評(píng)論 1 305
  • 那天焊唬,我揣著相機(jī)與錄音恋昼,去河邊找鬼。 笑死赶促,一個(gè)胖子當(dāng)著我的面吹牛液肌,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播鸥滨,決...
    沈念sama閱讀 40,292評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼嗦哆,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了婿滓?” 一聲冷哼從身側(cè)響起老速,我...
    開封第一講書人閱讀 39,211評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎凸主,沒想到半個(gè)月后橘券,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡卿吐,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評(píng)論 3 336
  • 正文 我和宋清朗相戀三年约郁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片但两。...
    茶點(diǎn)故事閱讀 39,965評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡鬓梅,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出谨湘,到底是詐尸還是另有隱情绽快,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評(píng)論 5 347
  • 正文 年R本政府宣布紧阔,位于F島的核電站坊罢,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏擅耽。R本人自食惡果不足惜活孩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 329
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望乖仇。 院中可真熱鬧憾儒,春花似錦询兴、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至训裆,卻和暖如春眶根,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背边琉。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評(píng)論 1 269
  • 我被黑心中介騙來泰國(guó)打工属百, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人变姨。 一個(gè)月前我還...
    沈念sama閱讀 48,126評(píng)論 3 370
  • 正文 我出身青樓族扰,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親钳恕。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評(píng)論 2 355