深入解析JNA—模拟C语言结构体
原帖:http://blog.csdn.net/shendl/article/details/3599849
?
深入解析JNA—模擬C語言結(jié)構(gòu)體
前言
前幾天寫《JNA--JNI終結(jié)者》一文介紹JNA框架。寫完之后才發(fā)現(xiàn),忘了寫比較有難度的C語言Struct的模擬了。
今天就補(bǔ)上這篇文章,介紹Struct。
不寫怎樣模擬C語言結(jié)構(gòu)體,就不能算是真正解決了調(diào)用動(dòng)態(tài)鏈接庫的問題。
C語言的結(jié)構(gòu)體用得實(shí)在是太廣泛了。
?
首先說明一點(diǎn),本文中大量把模擬Struct的類寫作為接口的內(nèi)部類。
這不是JNA規(guī)定的,而是一個(gè)編程習(xí)慣。
因?yàn)檫@些結(jié)構(gòu)體(Structure類的子類),一般沒有重用的價(jià)值,因此寫成內(nèi)部類比較方便。自然,你也可以把結(jié)構(gòu)體寫成一般的類。
?
例3?? 使用JNA調(diào)用使用Struct的C函數(shù)
C語言開發(fā)
繼續(xù)使用例2中的那個(gè)VSC++的dll項(xiàng)目。
增加一個(gè)結(jié)構(gòu)和使用該結(jié)構(gòu)的函數(shù)。
頭文件增加如下:
?
#define MYLIBAPI extern "C" __declspec( dllexport ) struct UserStruct{long id;wchar_t* name;int age;};MYLIBAPI void sayUser(UserStruct* pUserStruct);JNA程序
對應(yīng)的Java程序中,在例2的? 接口
?
/** 定義一個(gè)類,模擬C語言的結(jié)構(gòu)* */publicstaticclass UserStruct extends Structure{public NativeLong id;public WString name;publicintage;}publicvoid sayUser(UserStruct.ByReference struct);Java中的調(diào)用代碼:UserStruct userStruct=new UserStruct ();userStruct.id=new NativeLong(100);userStruct.age=30;userStruct.name=new WString("沈東良");TestDll1.INSTANCE.sayUser(userStruct);Struct說明
??? 現(xiàn)在,我們就在Java中實(shí)現(xiàn)了對C語言的結(jié)構(gòu)的模擬。
這里,我們繼承了Structure類,用這個(gè)類來模擬C語言的結(jié)構(gòu)。
?
必須注意,Structure子類中的公共字段的順序,必須與C語言中的結(jié)構(gòu)的順序一致。否則會(huì)報(bào)錯(cuò)!
?
因?yàn)?#xff0c;Java調(diào)用dll中的C函數(shù),實(shí)際上就是一段內(nèi)存作為函數(shù)的參數(shù)傳遞給dll。
Dll以為這個(gè)參數(shù)就是C語言傳過來的參數(shù)。
同時(shí),C語言的結(jié)構(gòu)是一個(gè)嚴(yán)格的規(guī)范,它定義了內(nèi)存的次序。因此,JNA中模擬的結(jié)構(gòu)的變量順序絕對不能錯(cuò)。
如,一個(gè)Struct有2個(gè)int變量。? Int a, int b?
如果JNA中的次序和C中的次序相反,那么不會(huì)報(bào)錯(cuò),但是得到的結(jié)果是相反的!
?
?例4?? 使用JNA調(diào)用使用嵌套Struct數(shù)組的C函數(shù)
如果C語言中的結(jié)構(gòu)體是復(fù)雜的嵌套的結(jié)構(gòu)體,該怎么辦呢?
繼續(xù)在上面例3的基礎(chǔ)上擴(kuò)充。
?
C語言開發(fā)
頭文件增加如下:
struct CompanyStruct{long id;wchar_t* name;UserStruct users[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"員工總數(shù):"<<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 WString name;public UserStruct.ByValue[] users=new UserStruct.ByValue[100];publicintcount; }public void sayCompany(CompanyStruct pCompanyStruct);對原來的UserStruct類進(jìn)行改寫:
/** 定義一個(gè)類,模擬C語言的結(jié)構(gòu)* */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;}調(diào)用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);說明
可以看到,程序正確輸出了。
??? 讀者也許會(huì)有一些疑問。?
1,為什么我們要給UserStruct 這個(gè)結(jié)構(gòu)添加2個(gè)內(nèi)部類呢?
看Structure類的API說明,我們知道,這個(gè)類內(nèi)部有2個(gè)接口:
| static?interface | Structure.ByReference |
| static?interface | Structure.ByValue |
這2個(gè)內(nèi)部接口是標(biāo)記,內(nèi)部什么都沒有。
在運(yùn)行時(shí),JNA的執(zhí)行框架會(huì)使用反射查看你是否實(shí)現(xiàn)了這2個(gè)接口,然后進(jìn)行特定的處理。
(這種技術(shù)在java標(biāo)注Annotation之前很流行?,F(xiàn)在可以使用運(yùn)行時(shí)Annotation實(shí)現(xiàn)同樣的效果。
JNA項(xiàng)目據(jù)說1999年就啟動(dòng)了,使用這樣的老技術(shù)不足為奇。只是很奇怪,為什么國內(nèi)都沒怎么聽說過。我也是最近偶然在國外的網(wǎng)站上發(fā)現(xiàn)它的。一試之下,愛不釋手,令我又對Java的桌面應(yīng)用信心百倍!
)
?
?
如果你的Struct實(shí)現(xiàn)Structure.ByReference接口,那么JNA認(rèn)為你的Struct是一個(gè)指針。指向C語言的結(jié)構(gòu)體。
如果你的Struct實(shí)現(xiàn)Structure.ByValue接口,那么JNA認(rèn)為你的Struct是值類型,就是C語言的結(jié)構(gòu)體。
如果你不實(shí)現(xiàn)這2個(gè)接口,那么就相當(dāng)于你實(shí)現(xiàn)了Structure.ByReference接口。
因此,在例3中,我沒有實(shí)現(xiàn)這2個(gè)接口。
2,C語言中,結(jié)構(gòu)體內(nèi)部必須進(jìn)行數(shù)組定義。Java中最好也這樣做。
C語言的結(jié)構(gòu)體是一段連續(xù)的內(nèi)存,內(nèi)存的大小是編譯時(shí)確定的。
因此,數(shù)組必須定義。否則編譯不會(huì)成功。
對應(yīng)的Java類中,我們也應(yīng)該在類定義時(shí)為數(shù)組定義。盡管實(shí)際上在使用時(shí)再賦值也可以。
但是,強(qiáng)烈不建議你這樣做。
如,上面
public UserStruct.ByValue[]users=new UserStruct.ByValue[100];
定義100個(gè)元素的數(shù)組,如果你不再類內(nèi)部定義。
而在使用時(shí)定義,如果你沒有正確賦值,沒有定義為100個(gè)元素,就會(huì)出錯(cuò)。
?
從表面上看,CompanyStruct類占用的內(nèi)存是:
NativeLong?id
WString? name;
????????????????????
?????????? public UserStruct.ByValue[]users=new UserStruct.ByValue[100];
?????????? publicintcount;
這4個(gè)元素占用的內(nèi)存的總和。
?
由于Java的數(shù)組是一個(gè)對象,users中實(shí)際保存的也應(yīng)該是一個(gè)引用,也就是指針,32bit。
?
那么CompanyStruct類占用的內(nèi)存就比對應(yīng)的C結(jié)構(gòu)體:
struct CompanyStruct{
??? long id;
?? wchar_t*?name;
?? UserStruct?? users[100];
?? int count;
};
?
小很多。內(nèi)存少用很多。
我在例3的說明中曾經(jīng)說過:
“
Java調(diào)用dll中的C函數(shù),實(shí)際上就是一段內(nèi)存作為函數(shù)的參數(shù)傳遞給dll。
Dll以為這個(gè)參數(shù)就是C語言傳過來的參數(shù)。
”
那么,JNA怎么還能正確調(diào)用C函數(shù)呢。
?
事實(shí)上,在調(diào)用C函數(shù)時(shí),JNA會(huì)把UserStruct類的實(shí)例的所有數(shù)據(jù)全部*100倍。(我的例子里數(shù)組是100個(gè),你的例子當(dāng)然可以有不一樣的數(shù)值)
這樣,Java中的結(jié)構(gòu)體的內(nèi)存量就和C語言的CompanyStruct結(jié)構(gòu)體占據(jù)相同大小和結(jié)構(gòu)的內(nèi)存,從而正確調(diào)用C函數(shù)。
?
例5?? 使用JNA調(diào)用使用嵌套Struct的指針的數(shù)組的C函數(shù)
現(xiàn)在給大家看看最復(fù)雜的Struct的例子。
Struct中嵌套的是一個(gè)結(jié)構(gòu)體的指針的數(shù)組。
?
C語言代碼
struct CompanyStruct2{
??? long id;
?? wchar_t*?name;
? UserStruct*? users[100];
? // UserStruct??users[100];
?? int count;
?
};
MYLIBAPI void sayCompany2(CompanyStruct2* pCompanyStruct);
?
這里,把剛才使用的UserStruct數(shù)組換成UserStruct指針的數(shù)組。
JNA代碼
publicstaticclass CompanyStruct2 extends Structure{publicstaticclass ByReference extends CompanyStruct2 implements Structure.ByReference { }public NativeLong id;public WString name;public UserStruct.ByReference[] users=new UserStruct.ByReference[100];publicintcount;}publicvoid sayCompany2(CompanyStruct2.ByReference pCompanyStruct);測試代碼:
CompanyStruct2.ByReference companyStruct2=new CompanyStruct2.ByReference();companyStruct2.id=new NativeLong(2);companyStruct2.name=new WString("Yahoo");companyStruct2.count=10; UserStruct.ByReference pUserStruct=new UserStruct.ByReference();pUserStruct.id=new NativeLong(90);pUserStruct.age=99;pUserStruct.name=new WString("良少");pUserStruct.write();// TestDll1.INSTANCE.sayUser(pUserStruct);for(int i=0;i<companyStruct2.count;i++){companyStruct2.users[i]=pUserStruct;} TestDll1.INSTANCE.sayCompany2(companyStruct2);程序說明----Pin鎖住Java對象:
因?yàn)槭墙Y(jié)構(gòu)體的指針的數(shù)組,所以,我們使用了
public UserStruct.ByReference[] users=new UserStruct.ByReference[100];
來對應(yīng)C語言中的
UserStruct*? users[100];
?
但是,有問題,如果去除
pUserStruct.write();
這一行代碼,就會(huì)報(bào)錯(cuò)。
如果去除pUserStruct.write();
但是使用??? TestDll1.INSTANCE.sayUser(pUserStruct);
也不會(huì)有問題。
?
這是怎么回事?
?
??? 原來,錯(cuò)誤的原因就是內(nèi)存鎖定!
?
java內(nèi)存鎖定機(jī)制和JNI,JNA調(diào)用
我們知道,java的內(nèi)存是GC管理的。它會(huì)自動(dòng)管理JVM使用的堆內(nèi)存。刪除不再使用的內(nèi)存,并把Java對象使用的內(nèi)存自動(dòng)移動(dòng),以防止內(nèi)存碎片增多。
因此,雖然Java的引用實(shí)際上就是指針,但還是和指針不同。Java中不能直接使用指針。因?yàn)閷ο笤趦?nèi)存中的地址都不是固定的。說不準(zhǔn)什么時(shí)候GC就把它給移位了。
?
如果使用JNI,JNA等技術(shù)調(diào)用C函數(shù)。那么就會(huì)有問題。因此,C語言使用的是指針。如果想要獲取C函數(shù)的返回值。那么我們必須提供一塊內(nèi)存給C語言訪問。而C語言是不知道你Java的引用的。它只能訪問固定的內(nèi)存地址。
如果GC把Java對象移來移去,那么C函數(shù)就沒辦法和Java交互了。
?
因此,在使用JNI和JNA時(shí),都會(huì)把Java對象鎖住。GC不再管理。不刪除,也不移動(dòng)位置。由此出現(xiàn)的內(nèi)存碎片,也不管了!
這種技術(shù)的術(shù)語是PIN。? .NET也有同樣的概念。
?
?
上面TestDll1.INSTANCE.sayUser(pUserStruct);這個(gè)調(diào)用是JNA調(diào)用。這個(gè)操作就把pUserStruct這個(gè)Java對象鎖住了。
?
例4中,嵌套的是結(jié)構(gòu)體,因此也會(huì)直接把CompanyStruct類的實(shí)例鎖住。
?
但是例5中,嵌套的是結(jié)構(gòu)體的指針的數(shù)組。? CompanyStruct2類的實(shí)例companyStruct2在執(zhí)行
TestDll1.INSTANCE.sayCompany2(companyStruct2);
時(shí)是被鎖住了,可以companyStruct2的users指針數(shù)組的成員:
pUserStruct?都沒有被鎖住。
?
怎么辦呢??
難道每一個(gè)UserStruct.ByReference 的實(shí)例都先調(diào)用一遍不需要的
TestDll1.INSTANCE.sayUser(pUserStruct);? 方法?
?
沒事!JNA開發(fā)人員早已想到了:
Structure? 類中有方法:
write
public void write()Writes the fields of the struct to native memory
writeField
public void writeField(String?name)Write the given field to native memory. The current value in the Java field will be translated into native memory.
Throws:
IllegalArgumentException - if no field exists with the given name
?
??? 這些write方法,會(huì)把Java的內(nèi)存Pin住。?? 就是C語言可以使用。GC不再管理它們。
?
現(xiàn)在只要調(diào)用
pUserStruct.write();
把java模擬結(jié)構(gòu)體實(shí)例給Pin住就可以了。
?
題外話,C#定義了語法,可以使用關(guān)鍵字 pin 鎖住.NET對象。
?
結(jié)論:
結(jié)構(gòu)體是C語言模擬OOP開發(fā)中經(jīng)常使用的一種數(shù)據(jù)組織形式。搞定了結(jié)構(gòu)體Struct,我們就可以放心大膽、輕輕松松地把C程序隨便拿來用了!
總結(jié)
以上是生活随笔為你收集整理的深入解析JNA—模拟C语言结构体的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java实现对无符号整数的支持
- 下一篇: java接收c语言的结构体