Head First JNA

問題描述

虛擬化項目,需要用到Java調用原生代碼的技術,我們使用的是開源庫JNA(Java Native Access)。

Native(C/C++)代碼,編譯生成動態鏈接庫Dynamic-link library

Windows下常見的.dll文件。這是我們項目中用到的動態鏈接庫。

clipboard.png

而在unix環境下,爲.so文件。這是百度地圖的動態鏈接庫。

clipboard.png

與動態鏈接庫配套的,會有相應的頭文件,來聲明動態鏈接庫中對外暴露的方法。

clipboard.png

百度地圖是直接封裝好,給了.so,但是不給頭文件,直接把寫好的jar包給你,直接調用就行。

clipboard.png

之前也是用過百度地圖的SDK,現在自己手寫代碼調用動態鏈接庫才明白,原來之前用的都是別人封裝好的,如今自己參照頭文件手寫,感覺理解還是深刻了不少。

入門

待解決的問題

我們使用JNA,主要是去調用動態鏈接庫中已經實現的方法,所以要解決的問題就是:如何在Java代碼中調用動態鏈接庫的方法?

打開頭文件,這個方法要求傳輸的數據是指針,而Java是沒有指針的,另一個問題:Java數據類型與C/C++的數據類型如何映射?

clipboard.png

方法映射

打開JNA的官方README,點擊Getting Started

直接看代碼,裏面的sample,入門足夠了。

clipboard.png

  1. 定義接口,繼承Library
  2. 定義該接口的一個實例,加載動態鏈接庫。
  3. 在接口中聲明方法,該方法需要與原生代碼方法聲明一致。
  4. 然後該方法就映射到了原生的方法,我們只需調用該接口中的方法,即可調用到原生的對應方法。
// 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.load((Platform.isWindows() ? "msvcrt" : "c"),
                            CLibrary.class);

    void printf(String format, Object... args);
}

類型映射

默認類型映射

這是官方README中給的類型映射。

學習與實踐的區別,看着這個表格感覺挺簡單的,實際用起來真難。

clipboard.png

結構體映射

結構體PSA_HOST

typedef struct {
  char   name[33];
  DWORD  context;
} PSA_HOST;

映射類HostStruct

  1. 編寫類HostStruct,繼承Structure,表示這個一個結構體。
  2. 聲明字段namecontext並且設置訪問屬性爲public
  3. 重寫getFieldOrder方法,表示本類中各字段以何順序映射原生結構體。
/**
 * @author zhangxishuo on 2019-02-16
 * 結構體 PSA_HOST
 */
public class HostStruct extends Structure {

    public byte[] name = new byte[33];

    public int context;

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("name", "context");
    }
}

注意

const char *才能映射爲String類型。

char *只能映射爲byte數組,然後使用Native.toString()方法將byte數組轉換爲String

方法映射

typedef PSA_STATUS (*LPFN_PSA_ShutdownHost)( PSA_LOGON_HANDLE *handle, IN PSA_HOST *psa_host );

參數中需要PSA_HOST結構體的指針。

參考了好多篇文章,最常用的就是下面這種寫法。寫靜態內部類,內部類實現ByReferenceByValue接口,分別表示映射指針,與映射值。

/**
 * @author zhangxishuo on 2019-02-16
 * 結構體 PSA_HOST
 */
public class HostStruct extends Structure {

    /**
     * 結構體指針
     */
    public static class ByReference extends HostStruct implements Structure.ByReference {
    }

    /**
     * 結構體具體的值
     */
    public static class ByValue extends HostStruct implements Structure.ByValue {
    }

    public byte[] name = new byte[33];

    public int context;

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("name", "context");
    }
}

映射

/**
 * 關閉計算機
 * @param pointerByReference 認證pointer
 * @param host               主機指針
 * @return 參見枚舉類PsaStatus
 */
NativeLong _PSA_ShutdownHost(PointerByReference pointerByReference,
                            HostStruct.ByReference host);

複雜類型

打起精神,重點來了!

開發過程中,有這樣一種複雜的數據結構,嵌套關係比較複雜。

typedef struct {
  union {
    DWORD flags;
    struct {
      DWORD rev:23;
      DWORD copy_status:3;
      DWORD timeout:1;
      DWORD disconnect:1;
      DWORD rev1:1;
      DWORD os_logoned:1;
      DWORD logoned:1;
      DWORD online:1;
    };
  };
} PSA_HOST_STATUS_FLAGS;

clipboard.png

知識不用就忘,誰還記得C語言裏的聯合是啥?

Too young

struct {
  DWORD rev:23;
  DWORD copy_status:3;
  DWORD timeout:1;
  DWORD disconnect:1;
  DWORD rev1:1;
  DWORD os_logoned:1;
  DWORD logoned:1;
  DWORD online:1;
};

DWORD就是int,先映射裏面的結構體。

把這些屬性一寫,然後再重寫getFieldOrder方法。

/**
 * @author zhangxishuo on 2019-02-26
 * 計算機狀態結構體
 */
public class HostStatusStruct extends Structure {

    /**
     * 結構體指針
     */
    public static class ByReference extends HostStatusStruct implements Structure.ByReference {
    }

    /**
     * 結構體具體的值
     */
    public static class ByValue extends HostStatusStruct implements Structure.ByValue {
    }

    public int rev;

    public int copy_status;

    public int timeout;

    public int disconnect;

    public int rev1;

    public int os_logoned;

    public int logoned;

    public int online;

    @Override
    protected List<String> getFieldOrder() {
        return Arrays.asList("rev", "copy_status", "timeout", "disconnect", "rev1", "os_logoned", "logoned", "online");
    }
}
union {
  DWORD flags;
  struct {
    DWORD rev:23;
    DWORD copy_status:3;
    DWORD timeout:1;
    DWORD disconnect:1;
    DWORD rev1:1;
    DWORD os_logoned:1;
    DWORD logoned:1;
    DWORD online:1;
  };
};

然後再映射聯合,編寫一個類繼承Union,該類即映射到聯合。

/**
 * 聯合
 */
public static class UNION extends Union {
    public int flags;
    public HostStatusStruct hostStatusStruct;
}

最後映射最外層的PSA_HOST_STATUS_FLAGS

/**
 * @author zhangxishuo on 2019-02-26
 * 結構體 PSA_HOST_STATUS_FLAGS
 */
public class HostStatusFlagsStruct extends Structure {

    /**
     * 聯合
     */
    public static class UNION extends Union {
        public int flags;
        public HostStatusStruct hostStatusStruct;
    }

    /**
     * 結構體指針
     */
    public static class ByReference extends HostStatusFlagsStruct implements Structure.ByReference {
    }

    /**
     * 結構體具體的值
     */
    public static class ByValue extends HostStatusFlagsStruct implements Structure.ByValue {
    }

    public UNION union;

    @Override
    protected List<String> getFieldOrder() {
        return Collections.singletonList("union");
    }
}

看上去好像沒什麼毛病,一切都這麼簡單嗎?當然不是。

-1073741824

一調用,就炸了。

clipboard.png

API文檔的聲明,flags應該是沒什麼具體含義的,而結構體中應該是我們想要獲取的信息。

那爲什麼flags有數,而結構體中卻沒有值呢?

聯合

struct TEST_STRUCT {
    int a,
    int b
};

clipboard.png

union TEST_UNION {
    int a,
    int b
};

clipboard.png

聲明映射類型

重寫read方法,當讀取數據時,設置聯合的類型爲結構體類型。

@Override
public void read() {
    super.read();
    union.setType(HostStatusStruct.class);
    union.read();
}

怪事

clipboard.png

當時這個問題把我愁壞了,捯飭了一整天才學明白。

數字

一直是這個數字:-1073741824,這個數字是不是有什麼問題?

-1073741824轉換爲32機的二進制表示:

1100 0000 0000 0000 0000 0000 0000 0000

1073741824230次方。

問題

問題還是出在這個上:

struct {
  DWORD rev:23;
  DWORD copy_status:3;
  DWORD timeout:1;
  DWORD disconnect:1;
  DWORD rev1:1;
  DWORD os_logoned:1;
  DWORD logoned:1;
  DWORD online:1;
};

雖然DWORD就映射爲int,但這裏不是簡單的映射:

rev:23表示rev32位。

copy_status:3表示copy_status3位。

timeout:1表示timeout1位。

內存是倒着存的,所以數據應該是這樣。

clipboard.png

映射

原理知道了,那怎麼映射呢?怎麼映射一個一位的內容呢?

clipboard.png

StackOverflow上一老哥給出瞭解決方案,先寫個int把所有的都映射過來,然後我想要第幾位再從裏面扒。


/**
 * @author zhangxishuo on 2019-02-26
 * 計算機狀態結構體
 */
public class HostStatusStruct extends Structure {

    /**
     * 結構體指針
     */
    public static class ByReference extends HostStatusStruct implements Structure.ByReference {
    }

    /**
     * 結構體具體的值
     */
    public static class ByValue extends HostStatusStruct implements Structure.ByValue {
    }

    public int value;

    public int getRev() {
        return value & 0x3FFFFF;
    }

    public int getCopyStatus() {
        return (value >> 23) & 0x3;
    }

    public int getTimeout() {
        return (value >> 26) & 0x1;
    }

    public int getDisconnect() {
        return (value >> 27) & 0x1;
    }

    public int getRev1() {
        return (value >> 28) & 0x1;
    }

    public int getOsLogoned() {
        return (value >> 29) & 0x1;
    }

    public int getLogoned() {
        return (value >> 30) & 0x1;
    }

    public int getOnline() {
        return (value >> 31) & 0x1;
    }

    @Override
    protected List<String> getFieldOrder() {
        return Collections.singletonList("value");
    }
}

至此,功能完成。

總結

又過去了忙碌的一週,很高興我們的新項目已經完成大半。

感謝潘佳琦與李宜衡在本項目中的支持,第一次用Angular,好多地方我也不懂,我先學着,然後設計一套架構付諸實踐,潘佳琦與李宜衡也都能遵從我制定的規範。

起初,我也提出了許多錯誤的規範,但當我用着用着發現原來那套不行的時候,及時改正,修改架構再重新設計,潘佳琦與李宜衡也在前臺經歷了大約三次的代碼重構。

clipboard.png

前臺架構變更多次,感覺最後的設計還讓人滿意,也能讓他人快速理解這種設計理念。

爭取下一個項目,不使用ng-alain,自己從頭到尾搭建一個項目骨架。

最後表揚一下潘佳琦,上週基本我有一半的時間都在上課,我能做的就是前一天晚上把任務建好,然後寫一些基礎代碼或示例代碼,然後給潘佳琦講,再讓他去寫。

潘佳琦效率還是很高的,我記得週一的時候建了一堆任務,我想怎麼着也得寫兩天吧,當我上課回來,發現“噹噹噹”,潘佳琦都給寫完了,代碼也十分的規範。

對小組員的開發效率在心中也有了一個重新的定位。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章