JNA—模擬C語言結構體
前言
前幾天寫《JNA--JNI終結者》一文介紹JNA框架。寫完之後才發現,忘了寫比較有難度的C語言Struct的模擬了。
今天就補上這篇文章,介紹Struct。
不寫怎樣模擬C語言結構體,就不能算是真正解決了呼叫動態連結庫的問題。
C語言的結構體用得實在是太廣泛了。
首先說明一點,本文中大量把模擬Struct的類寫作為介面的內部類。
這不是JNA規定的,而是一個程式設計習慣。
因為這些結構體(Structure類的子類),一般沒有重用的價值,因此寫成內部類比較方便。自然,你也可以把結構體寫成一般的類。
例3使用JNA呼叫使用Struct的C函式
C語言開發
繼續使用例2中的那個VSC++
增加一個結構和使用該結構的函式。
標頭檔案增加如下:
#define MYLIBAPIextern"C"__declspec( dllexport )
struct UserStruct{
long id;
wchar_t*name;
int age;
};
MYLIBAPI void sayUser(UserStruct* pUserStruct);
JNA程式
對應的Java程式中,在例2的介面
/*
* 定義一個類,模擬C語言的結構
* */
publicstaticclass UserStruct extends Structure{
public NativeLong
public WString name;
publicintage;
}
publicvoid sayUser(UserStruct.ByReference struct);
Java中的呼叫程式碼:
UserStruct userStruct=new UserStruct ();
userStruct.id=new NativeLong(100);
userStruct.age=30;
userStruct.name=new WString("沈東良");
TestDll1.INSTANCE.sayUser(userStruct);
Struct說明
現在,我們就在Java中實現了對
這裡,我們繼承了Structure類,用這個類來模擬C語言的結構。
必須注意,Structure子類中的公共欄位的順序,必須與C語言中的結構的順序一致。否則會報錯!
因為,Java呼叫dll中的C函式,實際上就是一段記憶體作為函式的引數傳遞給dll。
Dll以為這個引數就是C語言傳過來的引數。
同時,C語言的結構是一個嚴格的規範,它定義了記憶體的次序。因此,JNA中模擬的結構的變數順序絕對不能錯。
如,一個Struct有2個int變數。Int a, int b
如果JNA中的次序和C中的次序相反,那麼不會報錯,但是得到的結果是相反的!
例4使用JNA呼叫使用巢狀Struct陣列的C函式
如果C語言中的結構體是複雜的巢狀的結構體,該怎麼辦呢?
繼續在上面例3的基礎上擴充。
C語言開發
標頭檔案增加如下:
struct CompanyStruct{
long id;
wchar_t*name;
UserStructusers[100];
int count;
};
MYLIBAPI void sayCompany(CompanyStruct* pCompanyStruct);
原始檔:
void sayCompany(CompanyStruct* pCompanyStruct){
std::wcout.imbue(std::locale("chs"));
std::wcout<<L"ID:"<<pCompanyStruct->id<<std::endl;
std::wcout<<L"公司名稱:"<<pCompanyStruct->name<<std::endl;
std::wcout<<L"員工總數:"<<pCompanyStruct->count<<std::endl;
for(int i=0;i<pCompanyStruct->count;i++){
sayUser(&pCompanyStruct->users[i]);
}
}
JNA程式
Java程式中,在原來的介面上加上如下程式碼:
publicstaticclass CompanyStruct extends Structure{
public NativeLong id;
public WStringname;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
publicintcount;
}
publicvoid sayCompany(CompanyStruct pCompanyStruct);
對原來的UserStruct類進行改寫:
/*
* 定義一個類,模擬C語言的結構
* */
publicstaticclass UserStruct extends Structure{
publicstaticclass ByReference extends UserStruct implements Structure.ByReference { }
publicstaticclass ByValue extends UserStruct implements Structure.ByValue
{ }
public NativeLong id;
public WString name;
publicintage;
}
呼叫JNA程式:
CompanyStruct companyStruct=new CompanyStruct();
companyStruct.id=new NativeLong(1);
companyStruct.name=new WString("Google");
companyStruct.count=9;
UserStruct.ByValue userStructValue=new UserStruct.ByValue();
userStructValue.id=new NativeLong(100);
userStructValue.age=30;
userStructValue.name=new WString("沈東良");
for(int i=0;i<companyStruct.count;i++){
companyStruct.users[i]=userStructValue;
}
TestDll1.INSTANCE.sayCompany(companyStruct);
說明
可以看到,程式正確輸出了。
讀者也許會有一些疑問。
1,為什麼我們要給UserStruct這個結構新增2個內部類呢?
看Structure類的API說明,我們知道,這個類內部有2個介面:
|
|
|
|
這2個內部介面是標記,內部什麼都沒有。
在執行時,JNA的執行框架會使用反射檢視你是否實現了這2個介面,然後進行特定的處理。
(這種技術在java標註Annotation之前很流行。現在可以使用執行時Annotation實現同樣的效果。
JNA專案據說1999年就啟動了,使用這樣的老技術不足為奇。只是很奇怪,為什麼國內都沒怎麼聽說過。我也是最近偶然在國外的網站上發現它的。一試之下,愛不釋手,令我又對Java的桌面應用信心百倍!
)
介面,那麼JNA認為你的Struct是一個指標。指向C語言的結構體。
介面,那麼JNA認為你的Struct是值型別,就是C語言的結構體。
因此,在例3中,我沒有實現這2個介面。
2,C語言中,結構體內部必須進行陣列定義。Java中最好也這樣做。
C語言的結構體是一段連續的記憶體,記憶體的大小是編譯時確定的。
因此,陣列必須定義。否則編譯不會成功。
對應的Java類中,我們也應該在類定義時為陣列定義。儘管實際上在使用時再賦值也可以。
但是,強烈不建議你這樣做。
如,上面
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
定義100個元素的陣列,如果你不再類內部定義。
而在使用時定義,如果你沒有正確賦值,沒有定義為100個元素,就會出錯。
從表面上看,CompanyStruct類佔用的記憶體是:
NativeLongid
WStringname;
public UserStruct.ByValue[] users=new UserStruct.ByValue[100];
publicintcount;
這4個元素佔用的記憶體的總和。
由於Java的陣列是一個物件,users中實際儲存的也應該是一個引用,也就是指標,32bit。
那麼CompanyStruct類佔用的記憶體就比對應的C結構體:
struct CompanyStruct{
long id;
wchar_t*name;
UserStructusers[100];
int count;
};
小很多。記憶體少用很多。
我在例3的說明中曾經說過:
“
Java呼叫dll中的C函式,實際上就是一段記憶體作為函式的引數傳遞給dll。
Dll以為這個引數就是C語言傳過來的引數。
”
那麼,JNA怎麼還能正確呼叫C函式呢。
事實上,在呼叫C函式時,JNA會把UserStruct類的例項的所有資料全部*100倍。(我的例子裡陣列是100個,你的例子當然可以有不一樣的數值)
這樣,Java中的結構體的記憶體量就和C語言的CompanyStruct結構體佔據相同大小和結構的記憶體,從而正確呼叫C函式。
例5使用JNA呼叫使用巢狀Struct的指標的陣列的C函式
現在給大家看看最複雜的Struct的例子。
Struct中巢狀的是一個結構體的指標的陣列。
C語言程式碼
struct CompanyStruct2{
long id;
wchar_t*name;
UserStruct*users[100];