JNI調用C++dll動態庫如何轉換struct結構體為java實體類
需求:使用java對接第三方c++程序,調用c++方法
一、JNI和JNA簡介
JNI(Java Native Interface)是一種技術,通過這種技術可以做到以下兩點:
- Java程序中的函數可以調用Native語言寫的函數,Native一般指的是C/C++編寫的函數。
- Native程序中的函數可以調用Java層的函數,也就是在C/C++程序中可以調用Java的函數。
我們都知道承載Java世界的虛擬機是用Native語言寫的,而虛擬機又運行在具體平台上,所以虛擬機本身無法做到平台無關。然而,有了JNI技術,可以對Java層屏蔽具體的虛擬機實現上的差異了。這樣,就能實現Java本身的平台無關特性。其實Java一直在使用JNI技術,只是我們平時較少用到罷了。
JNI的使用並不簡單,如果已有一個編譯好的.dll/.so文件,如果使用JNI技術調用,我們首先需要使用C語言另外寫一個.dll/.so共享庫,使用SUN規定的數據結構替代C語言的數據結構,調用已有的 dll/so中公布的函 數。然后再在Java中載入這個庫dll/so,然后編寫Java native函數作為鏈接庫中函數的代理。經過這些繁瑣的步驟才能在Java中調用本地代碼。
JNA(Java Native Access)是建立在JNI技術基礎之上的一個Java類庫,它使我們可以方便地使用java直接訪問動態鏈接庫中的函數。我們不需要重寫我們的動態鏈接庫文件,而是有直接調用的API,大大簡化了我們的工作量。但是JNA一般只適用於較為簡單的C/C++庫,如果接口、數據結構復雜的話就不推薦。而且JNA也只提供了C/C++對Java的接口轉化。
二、Java和C類型對照表
下表是JNA官網 (https://jna.java.net/javadoc/overview-summary.html) 給出的基本類型的C語言和Java類型對照表:
C Type | Representation | Java Type |
---|---|---|
char | 8-bit integer | byte |
wchar_t | platform-dependent | char |
short | 16-bit integer | short |
int | 32-bit integer | int |
int | boolean flag | boolean |
enum | enumeration type | int(usually) |
long long, __int64 | 64-bit integer | long |
float | 32-bit floating point | float |
double | 64-bit floating point | double |
pointer (e.g. void*) | platform-dependent (32- or 64-bit pointer to memory) | BufferPointer |
pointer (e.g. void*) array | 32- or 64-bit pointer to memory (argument/return)contiguous memory (struct member) | \[] (array of primitive type) |
long | platform-dependent (32- or 64-bit integer) | NativeLong |
const char* | NUL-terminated array (native encoding or jna.encoding) | String |
const wchar_t* | NUL-terminated array (unicode) | WString |
char** | NULL-terminated array of C strings | String[] |
wchar** | NULL-terminated array of wide C strings | String[] |
void** | NULL-terminated array of pointers | Pointer[] |
struct* struct | pointer to struct (argument or return) (or explicitly) struct by value (member of struct) (or explicitly) | Structure |
union | same as Structure | Union |
struct[] | array of structs, contiguous in memory | Structure[] |
void (*FP)() | function pointer (Java or native) | Callback |
pointer (*) | same as Pointer | PointerType |
other | integer type | IntegerType |
other | custom mapping, depends on definition | NativeMapped |
這張對照表一般已經能夠滿足跨平台、跨語言調用的數據類型轉換需求,因為如果我們要做跨語言調用,應當盡量使用基本和簡單的數據類型,而不要過多使用復雜結構體傳遞數據,因為C語言的結構體中,每個成員會執行對齊操作與前一個成員保持字節對齊,也就是說,成員的書寫順序會影響結構體占用的空間大小,因此會在Java端定義結構體時會造成不小的麻煩。比如,如果跨語言調用的函數中參數包含stat這個結構體:我們都知道,stat這個結構體是用來描述linux文件系統的文件元數據的基本結構,但麻煩的是,這個結構體的成員定義次序在不同的機型上並不相同,如果我們在Java端重寫這個結構體,會產生兼容性問題。
三、結構體定義
有時候我們需要在Java端訪問某個C/C++結構體中的成員,我們就需要在Java端復寫這個結構體,在復寫的時候需要注意兩點:
- 需要在結構體定義中定義2個內部類ByReference和ByValue,來實現指針類型接口和值類型接口
- 重寫getFieldOrder()來告訴C/C++的成員取值次序
下面我們通過一個栗子來看一下在Java只不過怎么模擬定義一個C/C++結構體:
C/C++代碼
typedef struct A {
B* b;
void* args;
int len;
};
typedef struct B {
int obj_type;
};
Java代碼
/* 結構體A定義 */
public static A extends Structure {
//構造函數定義
public A() {
super();
}
public A(Pointer _a) {
super(_a);
}
//結構體成員定義
public B.ByReference b;
PointerByReference args;
int len;
//添加2個內部類,分別實現指針類型接口、值類型接口
public static class ByReference extends A implements Structure.ByReference {}
public static class ByValue extends A implements Structure.ByValue{}
//定義取值次序,需要與C/C++中對齊,不然會出現NoSuchFieldError
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[]{"b", "args", "len"});
}
}
/* 結構體B定義 */
public static B extends Structure {
public B() {
super();
}
public B(Pointer _b) {
super(_b);
}
int obj_type;
public static class ByReference extends B implements Structure.ByReference {}
public static class ByValue extends B implements Structure.ByValue{}
@Override
protected List getFieldOrder() {
return Arrays.asList(new String[]{"obj_type"});
}
}
四、結構體傳遞
如果需要在Java端訪問某個結構體的成員,需要使用ByReference(指針、引用)或是ByValue(拷貝參數);如果只是起到數據傳遞,不關心具體內部結構,可以使用PointerByReference和Pointer。
test_myFun(struct A a)
test_myFun(A.ByValue a)
test_myFun(struct A *a)
test_myFun(A.ByReference a)
test_myFun(struct A **a)
test_myFun(A.ByReference[] a)
test_myFun(A a)
test_myFun(Pointer a)
test_myFun(A *a)
test_myFun(PointerByReference a)
test_myFun(A **a)
test_myFun(PointerByReference[] a)
五、回調函數
我們有時候會在C/C++中頂一個一個帶回調函數的函數,例如:
typedef bool (*test_cb)(const char *name);
int test_myFun(test_cb cb, const char *name, uint32_t flag);
如果我們需要在Java端調用test_myFun函數,則我們需要在Java端定義一個與test_cb相同的回調接口:
public interface testCallback extends Callback {
//invoke對應test_cb,注意參數順序需要保持一致
boolean invoke(String name);
}
定義該回調接口的實現:
public class testCallbackImpl implements testCallback {
@Override
public int invoke(String name) {
System.out.printf("Invoke Callback " + name + " successfully!");
return true;
}
}
test_myFun函數Java端定義:
int testMyFun(Callback cb, String name, int flag);
調用實例:
int rc = testMyFun(new testCallbackImpl(), "helloworld", 1);
參考文檔: