JNI調用C++dll動態庫如何轉換struct結構體為java實體類


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

參考文檔:

http://blog.umcloud.com/java-sdk/#comment-879


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM