最近在搞一個小程式,需要用java調用DLL。用到的技術是JNA。
具體的內容在網上一搜都有,但是很多文章內容都差不多,而且都有些問題,也不知道是不是版本的問題,反正代碼拿來一copy各種問題,倒騰了好久,終於弄出點眉目出來,寫來與大家分享下。
首先,c/c++代碼如下:
extern "C" _declspec(dllexport) int add(int first, int second);
實現代碼:
int add(int first, int second) {printf("(c) test jna : %d + %d = %d\n", first, second, first + second);return first + second;}
封裝成DLL,copy到java工程的bin目錄下,
在java中使用時,首先需要匯入jna的包,過程網上都有,就不多說了,然後在代碼中需要添加:
import com.sun.jna.Library;import com.sun.jna.Native;import com.sun.jna.NativeLong;import com.sun.jna.Platform;import com.sun.jna.Pointer;import com.sun.jna.Structure;
調用代碼如下:
public class Dll { public interface TestJnaLib extends Library { TestJnaLib INSTANCE = (TestJnaLib)Native.loadLibrary("DLL.dll", TestJnaLib.class); int add(int first, int second); } public static void main(String[] args) {TestJnaLib.INSTANCE.add(23, 34); }
好,第一個沒有任何問題,通過。
現在試試結構體:
給DLL 工程中添加這些(教程裡用的):
struct UserStruct{int id;char* name;int age;};extern "C" __declspec( dllexport )void sayUser(UserStruct* pUserStruct){printf("%ld %s %d\n",pUserStruct->id,pUserStruct->name,pUserStruct->age);}
按照教程的說法,封裝以及調用規則如下:
public static class UserStruct extends Structure { public int id; public String name; public int age; public static class ByReference extends UserStructimplements Structure.ByReference { } public static class ByValue extends UserStruct implements Structure.ByValue{ }@Overrideprotected List getFieldOrder() {List a = new ArrayList(); a.add("id"); a.add("name"); a.add("age"); return a;} }
注意在教程裡沒有提到getFieldOrder這個函數,所以編譯會報錯,只好實現一下,查了一下,這個list返回的是封裝結構體中的變數名稱,這個一定不能寫反(不管是基本變數還是結構體變數,或者是數組什麼的,只需要添加名稱就可以)。
調用函式宣告:
void sayUser(UserStruct.ByReference struct);
關於:
public static class ByReference extends UserStructimplements Structure.ByReference { } public static class ByValue extends UserStruct implements Structure.ByValue{ }
這兩個需要說一下,教程裡是說顯示的說明是值或引用,如果去掉也不影響,前提是定義變數的時候應該這樣寫:
TestJnaLib.UserStruct userStruct=new TestJnaLib.UserStruct();
我測試過了,這個寫對結果也沒有什麼影響,DLL調用成功,不過還是建議加上,這樣顯得更正確些。
測試代碼:
TestJnaLib.UserStruct.ByReference userStruct=new TestJnaLib.UserStruct.ByReference(); userStruct.id=100; userStruct.age=30; userStruct.name=new String("奧巴馬"); TestJnaLib.INSTANCE.sayUser(userStruct);
看聲明,是傳遞引用,所以顯示的使用了ByReference。
一定注意,在定義變數的時候使用的是UserStruct.ByReference ,因為函式宣告的是ByReference。前後一定要一致。
下面是問題最多的一塊:結構體數組:
定義如下:
struct CompanyStruct{int id;char* name;int count;UserStruct users[10];};extern "C" __declspec( dllexport )void sayCom(CompanyStruct* pCompanyStruct){printf("%ld %s %d\n",pCompanyStruct->id,pCompanyStruct->name,pCompanyStruct->count);for(int i=0;i<10;i++)printf("%ld %s %d\n",pCompanyStruct->users[i].id,pCompanyStruct->users[i].name,pCompanyStruct->users[i].age);}
嵌套結構體:
教程裡是這樣封裝調用的:
public static class CompanyStruct extends Structure{public NativeLong id;public WString name;public UserStruct.ByValue[] users=newUserStruct.ByValue[100];public int count;}
但是會報錯(除了那個getFiledOrder函數外)。
回頭看看,可能是因為版本的問題吧,文章和教程都是08年的,現在JNA更新到了4.0了,可能對某些東西做了改動,所以會報錯。
嘗試著這樣定義:使用原始的方法定義:
public static class CompanyStruct extends Structure{ public int id; public String name; public int count; public UserStruct [] users=new UserStruct[10]; public static class ByReference extends CompanyStructimplements Structure.ByReference { } public static class ByValue extends CompanyStruct implements Structure.ByValue{ }@Overrideprotected List getFieldOrder() {List a = new ArrayList(); a.add("id"); a.add("name"); a.add("count"); a.add("users"); return a;} }
不用byValue了。如果這裡用到了byValue或者byReferfece都會報錯,即使編譯過了,運行也是錯的(其中有什麼invalid memory 訪問等一大堆各種奇葩的問題)。
想想,不管怎樣,在c模式下,數組其實跟指標是“相通”的。
函式宣告:
void sayCom(CompanyStruct.ByReference struct);
調用如下:
TestJnaLib.CompanyStruct.ByReference companyStruct2=new TestJnaLib.CompanyStruct.ByReference(); companyStruct2.id=1000; companyStruct2.name=new String("xueerfei"); companyStruct2.count=10; TestJnaLib.UserStruct pUserStruct=new TestJnaLib.UserStruct(); pUserStruct.id=90; pUserStruct.name=new String("xueerfei"); pUserStruct.age=99; pUserStruct.write(); int offset = 0; for(int i=0;i<companyStruct2.users.length;i++){ companyStruct2.users[i]=pUserStruct; } TestJnaLib.INSTANCE.sayCom(companyStruct2);
因為函數是Reference聲明,所以定義使用ByReference。(注意,如果在java裡函式宣告是一般聲明,那麼定義變數時也是用一般聲明,雖然在DLL中是指標參數,但是調用DLL經測試是成功的,這點我很納悶,可能在轉換的時候java已經自動識別了吧,保險一下,還是顯示的使用聲明吧)
注意這裡:
TestJnaLib.UserStruct pUserStruct=new TestJnaLib.UserStruct();
不用顯示的使用Reference,因為在定義的時候使用的是普通的new,所以這裡也而且必須使用普通的變數定義。
這樣調用就成功了。
其他的說明教程裡都有講的比較詳細,就不多說了。
因為技術也在發展,所以教程裡的某些東西可能因為更新而被修改,所以閱讀的時候還需要自己再實際的測試使用才行。