文章開始之前首先介紹下.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ù)類型
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í)自上而下。
可 見步驟非常的多检吆,很麻煩舒萎,使用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++的過程大致如下:
可以看到步驟減少了很多,最重要的是我們不需要重寫我們的動(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è)接口陌凳,繼承自Library
或StdCallLibrary
默認(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)類型映射表如下:
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:
6. 參考文獻(xiàn)
(2)C++DLL編程詳解