JNA 回調和使用經歷

1.添加依賴

        <dependency>
            <groupId>net.java.dev.jna</groupId>
            <artifactId>jna</artifactId>
            <version>5.2.0</version>
        </dependency>

2.編寫代碼

加載so文件,將so文件放在resources文件夾下,啓動項目配置項添加 VM options:-Djna.library.path = C:\Users\admin\project\src\main\resources\Lib

2.1 創建接口加載該so文件

public interface AlgoSdk extends Library {
    AlgoSdk instance = Native.load("libAiFramework.so", AlgoSdk.class);
    /**
     * 直接回調
     * @param pic
     * @param length
     * @param resultCB
     */
    int FaceFetureDetect_Java(byte[] pic, int length, GetResultCallBack resultCB);

	/**
     * 結構體回調
     * @param pic
     * @param length
     * @param resultCB
     */
	int FaceFetureDetect2_Java(byte[] pic, int length, GetFeautureCallBack resulFaceAlign_JavatCB);
}

2.2 回調函數

2.2.1 直接回調的函數方法
public interface GetResultCallBack extends Callback {
    void SetResultCB(String value);
}
2.2.2 回調結構的函數方法
2.2.2.1 回調函數
public interface GetFeautureCallBack extends Callback {

    void execute(FeatureDataInfo.ByReference dataInfo);

}
2.2.2.2 回調函數中的結構體:
public class FeatureDataInfo extends Structure{
    /**
     * 人臉特徵
     */
    public Pointer face_feature;
	// 特徵長度
    public int feature_len;
	// 信息
    public String msg;
	// 結構體的引用
    public static class ByReference extends FeatureDataInfo implements Structure.ByReference {
    }
	// 結構體對象
    public static class ByValue extends FeatureDataInfo implements Structure.ByValue {
    }
    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("face_feature", "feature_len", "msg");
    }
}
2.2.2.3 這裏一個java和C的數據類型對照表
Java 類型 C 類型 原生表現
boolean int 32位整數(可定製)
byte char 8位整數
char wchar_t 平臺依賴
short short 16位整數
int int 32位整數
long long long, __int64 64位整數
float float 32位浮點數
double double 64位浮點數
Buffer/Pointer pointer 平臺依賴(32或64位指針)
<\T>[] (基本類型的數組) pointer/array 32或64位指針(參數/返回值)鄰接內存(結構體成員)
String char* /0結束的數組 (native encoding or jna.encoding)
WString wchar_t* /0結束的數組(unicode)
String[] char** /0結束的數組的數組
WString[] wchar_t** /0結束的寬字符數組的數組
Structure struct*/struct 指向結構體的指針(參數或返回值) (或者明確指定是結構體指針)結構體(結構體的成員) (或者明確指定是結構體)
Union union 等同於結構體
Structure[] struct[] 結構體的數組,鄰接內存
Callback <\T> (*fp)() Java函數指針或原生函數指針
NativeMapped varies 依賴於定義
NativeLong long 平臺依賴(32或64位整數)
PointerType pointer 和Pointer相同

3.調用示例:

3.1直接調用示例
public JSONObject faceFeatureFunc(OpenApiDto dto) throws Exception {
    long invokeTime = System.currentTimeMillis();
    AtomicReference<JSONObject> result = new AtomicReference<>();
    int num = AlgoSDK.instance.FaceFetureDetect_Java(dto.getFile().getBytes(), dto.getFile().getBytes().length, value -> {
      result.set(JSON.parseObject(value));
    });
    log.debug("#FaceFeature expend:{}", System.currentTimeMillis() - invokeTime);
    JSONObject json = result.get();
    return json ;
  }
3.2結構體回調調用示例
public Map<String,Object> faceFeatureFunc2(OpenApiDto dto) throws Exception {
    long start = System.currentTimeMillis();
    AtomicReference<ImmutableMap> result = new AtomicReference<>();
    int num = AlgoSDK.instance.FaceFetureDetect2_Java(dto.getFile().getBytes(), dto.getFile().getBytes().length, dataInfo -> {
     Pointer pointer = info.face_feature;
    int length = info.feature_len;
    String msg = info.msg;
    float[] features = pointer.getFloatArray(0, length);
      result.set(ImmutableMap.of("msg", msg, "feature", features));
    });
    long end = System.currentTimeMillis();
    log.debug("operate face feature :{}" + (end - start));
    return ImmutableMap json = result.get();
  }

4.JNA以結構體數組爲參數進行調用必須使用:

  • 1代碼 C++
////// C++  
// student 結構體定義  
typedef struct    
{  
    int age;  
    char name[20];  
}Student;  
  • 2代碼 java
// 修改java對象的屬性值  
JNAAPI bool changeObjs(Student stu[],int size)  
{  
    for(int i=0;i<size;i++)  
    {  
        stu[i].age*=10;  
        strcpy(stu[i].name,"wokettas");  
    }  
    return true;  
}  
/////// Java  
// JNA調用方法  
Student[] stus=new Students[2];  
Student s1=new Student();  
Student s2=new Student();  
s1.age=1;  
s1.name=Arrays.copyOf("k1".getBytes(), 20);  
s2.age=2;  
s2.name=Arrays.copyOf("k2".getBytes(),20);  
stus[0]=s1; //數組賦值  
stus[1]=s2;  
Gui.gui.changeObjs(stus, stus.length);
  • 3 運行報錯:

    Structure array elements must use contiguous memory (bad backing address at Structure array index 1)

    結構體數組必須使用連續的內存區域, 上例s1,s2都是new出來的新對象,不可能連續; 也就是說傳統方式的初始化數組不能解決, 查詢JNA api發現裏面提供了:

  • 4 toArray
    public Structure[] toArray(int size)
    Returns a view of this structure’s memory as an array of structures. Note that this Structure must have a public, no-arg constructor. If the structure is currently using a Memory backing, the memory will be resized to fit the entire array.

  • 5 修改後的代碼:

Java代碼

// 測試對象數組  
Student student=new Student();  
Student[] stus=(Student[]) student.toArray(2); //分配大小爲2的結構體數組  
stus[0].age=1;  
stus[0].name=Arrays.copyOf("k1".getBytes(), 20);  
stus[1].age=2;  
stus[1].name=Arrays.copyOf("k2".getBytes(),20);  
Gui.gui.changeObjs(stus, stus.length);

5.如果傳遞參數的是結構體數組,並且裏面含有char*之類的,就必須用指針Pointer(com.sun.jna)

-1 比如:

  • 回調方法:
    ​​在這裏插入圖片描述

  • 結構體數組
    在這裏插入圖片描述

  • 2 那麼java 結構體:
    在這裏插入圖片描述

  • 3 封裝參數:

    byte[] b = dto.getFile().getBytes();
    ImageDataInfo  imageDataInfo = new ImageDataInfo();
    ImageDataInfo[] array = (ImageDataInfo[])imageDataInfo.toArray(1);
    Pointer p2 = new Memory(b.length+1);
    p2.write(0,b,0,b.length);
    array[0].imageData=p2;
    array[0].len =b.length;
jna Pointer
  • read 是把指針指向的內存空間中的數據讀取到對應的數組當中
  • write 是把數組中的數據寫入到指針指向的內存空間當中

6.內存問題

  • 1.剛開始使用jna的時候用到了JNA,運行項目是沒有問題的,但是跑多了程序就直接崩潰,想來想去,還是沒有思路,作爲Java開發,很少有自己手動釋放內存的習慣,因爲JVM已經替我們做了,但是利用JNA掉用的時候涉及到了C,C不會自動釋放內存,需要手動釋放內存。剛開始使用的時候還以爲所有的jna使用的內存都是堆內申請的內存,並且和其他的對象一樣都屬於沒有引用的就會被GC回收掉
    Java進程的內存包括Java NonHeap空間、Java Heap空間和Native Heap空間。JNA中的Memory對象是從Native Heap中分配空間。但java的GC是針對Java Heap空間設計的,當Java Heap空間不足時會觸發GC,但Native Heap空間不夠卻不會觸發GC。
    所以,當Java Heap佔用空間不大時,並不會GC掉Memory對象,也就不會執行finalize()方法從而釋放分配的Native Heap空間

Memory中的finalize()方法:

 /** Properly dispose of native memory when this object is GC'd. */
    @Override
    protected void finalize() {
        dispose();
    }
 
    /** Free the native memory and set peer to zero */
    protected synchronized void dispose() {
        try {
            free(peer);
        } finally {
            peer = 0;
            allocatedMemory.remove(this);
        }
    }
 
 protected static void free(long p) {
        // free(0) is a no-op, so avoid the overhead of the call
        if (p != 0) {
            Native.free(p);
        }
    }

其中,Native.free()方法如下:

    /**
     * Call the real native free
     * @param ptr native address to be freed; a value of zero has no effect,
     * passing an already-freed pointer will cause pain.
     */
    public static native void free(long ptr);

Pointer類中的方法:

 	/** Read the native peer value.  Use with caution. */
    public static long nativeValue(Pointer p) {
        return p == null ? 0 : p.peer;
    }
 
    /** Set the native peer value.  Use with caution. */
    public static void nativeValue(Pointer p, long value) {
        p.peer = value;
    }

由上面的源碼可知,當Memory被GC掉時,會自動去釋放分配的直接內存(前提是要執行GC)。爲了避免過多的使用Memory分配直接內存而導致直接內存空間不足,可以手動釋放掉Memory分配的內存,方法如下:

Pointer p = new Memory(1024 * 1024);
long peer = Pointer.nativeValue(p);
Native.free(peer);//手動釋放內存
Pointer.nativeValue(p, 0);//避免

如果不調用最後一行代碼

Pointer.nativeValue(p, 0);
  • 2.jna內存釋放問題,在網上很多帖子都說的是java端需要clear()釋放jna結構體的內存(內存地址內容清空),這個是防止內存泄漏的有效手段。但是在我實際開發中,我發現一個問題,就是我clear()釋放了內存後,C的邏輯 還需要這個內存裏面的內容進行下一步的操作,C再取內存中的數據的時候就會報錯(C報錯就直接崩潰),所以我們java端就不在釋放內存,就讓C自己去釋放內存,最後整個系統的內存還是很穩定沒有出現內存泄漏。最後查詢相關資料,jna的使用的內存是java和C共用的內存,並且不在堆內(如果是堆內就會存在java使用完成就會被回收,C獲取內存地址的數據就直接報錯崩潰),所以jna使用的內存可以在C和java任何一端回收。

7.JNA C和java注意事項

  • 1.如果是結構體回調參數,C端必須重新向JVM聲明新的內存空間,把每個參數重新賦值,意爲原參數空間地址有引用,不然會出現java端獲取參數時亂碼/空的情況。
  • 2.結構體數組回調或者返回會出現這個問題,其他單個結構體和直接參數返回暫時沒有此情況。
  • 3.如果會用到回調函數結合結構體數組嵌套結構體數組或者單個結構體實體的情況,在嵌套的結構體數組或者實體裏面的元素,如果返回的不是基本類型(int,boolean,byte…),最好使用Pointer的格式來獲取元素的內容,否則會出現亂碼或者獲取不到的情況。
  • 4.Java的對象的參數傳遞問題。jna是java調用C的函數/方法,所以就會存在內存空間指向問題,在C端是指針指向內存空間,java在調用jna的時候我們基本也用的是Pointer類來獲取C端返回的元素的數據,你就會表象的看着java其實也是指針在指向內存空間,其實java是值傳遞,不要被JNA的表象所迷惑。

8.總結出現的問題

  • 1.調用jna的時候獲取內存地址的值到java本地緩存的時候不要直接用 “對象=對象” 的java賦值方式,因爲jna對象內存的數據是和C共用的,所以直接用等於來賦值,就會引用同一個內存地址的數據,如果C修改了數據,那麼這個時候java的內存對象也會是修改後的數據。最糟糕的情況:C回收了一段已經傳給了java的內存數據,那麼java再次使用這一段內存數據就會報錯readexception,甚至由於調用了so包而崩潰。所以爲了避免出現這種情況,就會把對象中所有的數據都通過基本類型或者new一個新的引用類型通過申請新的堆內內存空間的方式取出來,纔不會出現C修改或者回收了此對象導致java再次處理數據混亂出現問題(如果是高併發問題更難找,復現問題都是隨機,還有及其性能纔會出現)。
  • 2.回調函數只能初始化一次,如果每次調用再初始化,就會出現C回調的時候每次的邏輯都是最新的,然後出現邏輯錯亂,併發串路的情況,所以回調函數只需要初始化一次。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章