問題描述
虛擬化項目,需要用到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 {
char name[33];
DWORD context;
} 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
機的二進制表示:
1100 0000 0000 0000 0000 0000 0000 0000
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
,自己從頭到尾搭建一個項目骨架。
最後表揚一下潘佳琦,上週基本我有一半的時間都在上課,我能做的就是前一天晚上把任務建好,然後寫一些基礎代碼或示例代碼,然後給潘佳琦講,再讓他去寫。
潘佳琦效率還是很高的,我記得週一的時候建了一堆任務,我想怎麼着也得寫兩天吧,當我上課回來,發現“噹噹噹”,潘佳琦都給寫完了,代碼也十分的規範。
對小組員的開發效率在心中也有了一個重新的定位。