Head First JNA
問題描述
虛擬化專案,需要用到 Java
呼叫原生程式碼的技術,我們使用的是開源庫 JNA
( Java Native Access
)。
Native
( C/C++
)程式碼,編譯生成動態連結庫 Dynamic-link library
。
在 Windows
下常見的 .dll
檔案。這是我們專案中用到的動態連結庫。
而在 unix
環境下,為 .so
檔案。這是百度地圖的動態連結庫。
與動態連結庫配套的,會有相應的標頭檔案,來宣告動態連結庫中對外暴露的方法。
百度地圖是直接封裝好,給了 .so
,但是不給標頭檔案,直接把寫好的 jar
包給你,直接呼叫就行。
之前也是用過百度地圖的 SDK
,現在自己手寫程式碼呼叫動態連結庫才明白,原來之前用的都是別人封裝好的,如今自己參照標頭檔案手寫,感覺理解還是深刻了不少。
入門
待解決的問題
我們使用 JNA
,主要是去呼叫動態連結庫中已經實現的方法,所以要解決的問題就是:如何在 Java
程式碼中呼叫動態連結庫的方法?
開啟標頭檔案,這個方法要求傳輸的資料是指標,而 Java
是沒有指標的,另一個問題: Java
資料型別與 C/C++
的資料型別如何對映?
方法對映
開啟 JNA
的官方 README
,點選 Getting Started
。
直接看程式碼,裡面的 sample
,入門足夠了。
Library
// 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
中給的型別對映。
學習與實踐的區別,看著這個表格感覺挺簡單的,實際用起來真難。
結構體對映
結構體 PSA_HOST
:
typedef struct { charname[33]; DWORDcontext; } PSA_HOST;
對映類 HostStruct
:
- 編寫類
HostStruct
,繼承Structure
,表示這個一個結構體。 - 宣告欄位
name
與context
, 並且設定訪問屬性為public
。 - 重寫
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
結構體的指標。
參考了好多篇文章,最常用的就是下面這種寫法。寫靜態內部類,內部類實現 ByReference
與 ByValue
介面,分別表示對映指標,與對映值。
/** * @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;
知識不用就忘,誰還記得 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
一呼叫,就炸了。
看 API
文件的宣告, flags
應該是沒什麼具體含義的,而結構體中應該是我們想要獲取的資訊。
那為什麼 flags
有數,而結構體中卻沒有值呢?
聯合
struct TEST_STRUCT { int a, int b };
union TEST_UNION { int a, int b };
宣告對映型別
重寫 read
方法,當讀取資料時,設定聯合的型別為結構體型別。
@Override public void read() { super.read(); union.setType(HostStatusStruct.class); union.read(); }
怪事
當時這個問題把我愁壞了,捯飭了一整天才學明白。
數字
一直是這個數字: -1073741824
,這個數字是不是有什麼問題?
把 -1073741824
轉換為 32
機的二進位制表示:
1073741824
是 2
的 30
次方。
問題
問題還是出在這個上:
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
表示 rev
佔 32
位。
copy_status:3
表示 copy_status
佔 3
位。
timeout:1
表示 timeout
佔 1
位。
記憶體是倒著存的,所以資料應該是這樣。
對映
原理知道了,那怎麼對映呢?怎麼對映一個一位的內容呢?
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
,好多地方我也不懂,我先學著,然後設計一套架構付諸實踐,潘佳琦與李宜衡也都能遵從我制定的規範。
起初,我也提出了許多錯誤的規範,但當我用著用著發現原來那套不行的時候,及時改正,修改架構再重新設計,潘佳琦與李宜衡也在前臺經歷了大約三次的程式碼重構。
前臺架構變更多次,感覺最後的設計還讓人滿意,也能讓他人快速理解這種設計理念。
爭取下一個專案,不使用 ng-alain
,自己從頭到尾搭建一個專案骨架。
最後表揚一下潘佳琦,上週基本我有一半的時間都在上課,我能做的就是前一天晚上把任務建好,然後寫一些基礎程式碼或示例程式碼,然後給潘佳琦講,再讓他去寫。
潘佳琦效率還是很高的,我記得週一的時候建了一堆任務,我想怎麼著也得寫兩天吧,當我上課回來,發現“噹噹噹”,潘佳琦都給寫完了,程式碼也十分的規範。
對小組員的開發效率在心中也有了一個重新的定位。