1. 程式人生 > >深入解析JNA—模擬C語言結構體(2)

深入解析JNA—模擬C語言結構體(2)

原文 http://blog.csdn.net/shendl/archive/2008/12/26/3599849.aspx

2 C 語言中,結構體內部必須進行陣列定義。 Java 中最好也這樣做。

C 語言的結構體是一段連續的記憶體,記憶體的大小是編譯時確定的。

因此,陣列必須定義。否則編譯不會成功。

對應的 Java 類中,我們也應該在類定義時為陣列定義。儘管實際上在使用時再賦值也可以。

但是,強烈不建議你這樣做。

如,上面

public UserStruct.ByValue[] users = new UserStruct.ByValue[100];

定義 100 個元素的陣列,如果你不再類內部定義。

而在使用時定義,如果你沒有正確賦值,沒有定義為 100 個元素,就會出錯。

從表面上看, CompanyStruct 佔用的記憶體是:

NativeLong id

WString name ;

public UserStruct.ByValue[] users = new UserStruct.ByValue[100];

public int count ;

4 個元素佔用的記憶體的總和。

由於 Java 的陣列是一個物件, users 中實際儲存的也應該是一個引用,也就是指標,

32bit

那麼 CompanyStruct 類佔用的記憶體就比對應的 C 結構體:

struct CompanyStruct{

long id;

wchar_t * name;

UserStruct users[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];

// UserStruct users[100];

int count;

};

MYLIBAPI void sayCompany2(CompanyStruct2* pCompanyStruct);

這裡,把剛才使用的 UserStruct 陣列換成 UserStruct 指標的陣列。

JNA 程式碼

public static class CompanyStruct2 extends Structure{

public static class ByReference extends CompanyStruct2 implements Structure.ByReference { }

public NativeLong id ;

public WString name ;

public UserStruct.ByReference[] users = new UserStruct.ByReference[100];

public int count ;

}

public void 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 物件:

因為是結構體的指標的陣列,所以,我們使用了

public UserStruct.ByReference[] users=new UserStruct.ByReference[100];

來對應 C 語言中的

UserStruct* users[100];

但是,有問題,如果去除

pUserStruct.write();

這一行程式碼,就會報錯。

如果去除 pUserStruct.write();

但是使用 TestDll1.INSTANCE.sayUser(pUserStruct);

也不會有問題。

這是怎麼回事?

原來,錯誤的原因就是記憶體鎖定!

java 記憶體鎖定機制和 JNI JNA 呼叫

我們知道, java 的記憶體是 GC 管理的。它會自動管理 JVM 使用的堆記憶體。刪除不再使用的記憶體,並把 Java 物件使用的記憶體自動移動,以防止記憶體碎片增多。

因此,雖然 Java 的引用實際上就是指標,但還是和指標不同。 Java 中不能直接使用指標。因為物件在記憶體中的地址都不是固定的。說不準什麼時候 GC 就把它給移位了。

如果使用 JNI JNA 等技術呼叫 C 函式。那麼就會有問題。因此, C 語言使用的是指標。如果想要獲取 C 函式的返回值。那麼我們必須提供一塊記憶體給 C 語言訪問。而 C 語言是不知道你 Java 的引用的。它只能訪問固定的記憶體地址。

如果 GC Java 物件移來移去,那麼 C 函式就沒辦法和 Java 互動了。

因此,在使用 JNI JNA 時,都會把 Java 物件鎖住。 GC 不再管理。不刪除,也不移動位置。由此出現的記憶體碎片,也不管了!

這種技術的術語是 PIN .NET 也有同樣的概念。

上面 TestDll1.INSTANCE.sayUser(pUserStruct); 這個呼叫是 JNA 呼叫。這個操作就把 pUserStruct 這個 Java 物件鎖住了。

4 中,巢狀的是結構體,因此也會直接把 CompanyStruct 類的例項鎖住。

但是例 5 中,巢狀的是結構體的指標的陣列。 CompanyStruct 2 類的例項 companyStruct2 在執行

TestDll1.INSTANCE.sayCompany2(companyStruct2);

時是被鎖住了,可以 companyStruct2 users 指標陣列 的成員:

pUserStruct 都沒有被鎖住。

怎麼辦呢?

難道每一個 UserStruct.ByReference 的例項都先呼叫一遍不需要的

TestDll1.INSTANCE.sayUser(pUserStruct); 方法?

沒事! JNA 開發人員早已想到了:

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:

這些 write 方法,會把 Java 的記憶體 Pin 住。 就是 C 語言可以使用。 GC 不再管理它們。

現在只要呼叫

pUserStruct.write();

java 模擬結構體例項給 Pin 住就可以了。

題外話, C# 定義了語法,可以使用關鍵字 pin 鎖住 .NET 物件。

結論:

結構體是 C 語言模擬 OOP 開發中經常使用的一種資料組織形式。搞定了結構體 Struct ,我們就可以放心大膽、輕輕鬆鬆地把 C 程式隨便拿來用了!