1. 程式人生 > >JNA—模擬C語言結構體

JNA—模擬C語言結構體



前言

前幾天寫《JNA--JNI終結者》一文介紹JNA框架。寫完之後才發現,忘了寫比較有難度的C語言Struct的模擬了。

今天就補上這篇文章,介紹Struct

不寫怎樣模擬C語言結構體,就不能算是真正解決了呼叫動態連結庫的問題。

C語言的結構體用得實在是太廣泛了。

首先說明一點,本文中大量把模擬Struct的類寫作為介面的內部類。

這不是JNA規定的,而是一個程式設計習慣。

因為這些結構體(Structure類的子類),一般沒有重用的價值,因此寫成內部類比較方便。自然,你也可以把結構體寫成一般的類。

3使用JNA呼叫使用StructC函式

C語言開發

繼續使用例2中的那個VSC++

dll專案。

增加一個結構和使用該結構的函式。

標頭檔案增加如下:

#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

id;

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中實現了對

C語言的結構的模擬。

這裡,我們繼承了Structure類,用這個類來模擬C語言的結構。

必須注意,Structure子類中的公共欄位的順序,必須與C語言中的結構的順序一致。否則會報錯!

因為,Java呼叫dll中的C函式,實際上就是一段記憶體作為函式的引數傳遞給dll

Dll以為這個引數就是C語言傳過來的引數。

同時,C語言的結構是一個嚴格的規範,它定義了記憶體的次序。因此,JNA中模擬的結構的變數順序絕對不能錯。

如,一個Struct2int變數。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個介面:

static interface


          Tagging interface to indicate the address of an instance of the Structure type is to be used within a
Structure definition rather than nesting the full Structure contents.

static interface


          Tagging interface to indicate the value of an instance of the
Structure type is to be used in function invocations rather than its address.

2個內部介面是標記,內部什麼都沒有。

在執行時,JNA的執行框架會使用反射檢視你是否實現了這2個介面,然後進行特定的處理。

(這種技術在java標註Annotation之前很流行。現在可以使用執行時Annotation實現同樣的效果。

JNA專案據說1999年就啟動了,使用這樣的老技術不足為奇。只是很奇怪,為什麼國內都沒怎麼聽說過。我也是最近偶然在國外的網站上發現它的。一試之下,愛不釋手,令我又對Java的桌面應用信心百倍!

介面,那麼JNA認為你的Struct是一個指標。指向C語言的結構體。

介面,那麼JNA認為你的Struct是值型別,就是C語言的結構體。

因此,在例3中,我沒有實現這2個介面。

2C語言中,結構體內部必須進行陣列定義。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];