JNA 基礎篇<二> 結構體

1.JNA模擬結構體java

原生函數定義:函數

struct{  
  ipaddress              struAddress;//結構體中包含結構體 
  device_register_cb     fnCB;//回調函數
  void                   *pUser;  
  BYTE                   byRes[32];
}CmsListenParam, *CmsListenParam;
struct{
  char    szIP[128];
  WORD    wPort;
  BYTE    byRes[2];
}ipaddress, *ipaddress;

java使用下面的方式模擬結構體:.net

public static class CmsListenParam extends Structure{
    public IpAddress  struAddress;
    public DeviceRegisterCB   fnCB;
    public Pointer              pUser;
    public byte[]               byRes=new byte[32]; 
}
public static class IpAddress extends Structure{
	public byte[]    szIP=new byte[128];
	public short     wPort;
	public byte[]    byRes=new byte[2];
}

       JNA中,定義一個類型繼承Structure 類,用這個類來模擬C 語言的結構體。必須注意,Structure 子類中的公共字段的順序,必須與C 語言中的結構的順序一致。不然會報錯!由於,Java 調用動態連接庫中的C 函數,實際上就是一段內存做爲函數的參數傳遞給C函數。同時,C 語言的結構體是一個嚴格的規範,它定義了內存的次序。所以,JNA 中模擬的結構體的變量順序絕對不能錯。指針

      當數據類型相同時,若交換位置,那麼程序不會報錯,可是數據會傳遞到錯誤的參數中。code

     Structure 類表明了一個原生結構體。當Structure 對象做爲一個函數的參數或者返回值傳遞時,它表明結構體指針。當它被用在另外一個結構體內部做爲一個字段時,它表明結構體自己。另外,Structure 類有兩個內部接口Structure.ByReference 和Structure.ByValue。這兩個接口僅僅是標記,若是一個類實Structure.ByReference 接口,就表示這個類表明結構體指針。若是一個類實現Structure.ByValue 接口,就表示這個類表明結構體自己。用這兩個接口的實現類,能夠明肯定義咱們的Structure 實例表示的是結構體的指針仍是結構體自己。上面的例子中,因爲Structure 實例做爲函數的參數使用,所以是結構體指針。對象

    因此可直接使用param對象做爲參數傳遞blog

CmsListenParam param=new CmsListenParam();

      也可使用繼承

jCmsListenParam param=new CmsListenParam.ByReference();

明確指出param對象是結構體指針而不是結構體自己。接口

2.JNA模擬Unionip

原生代碼定義:

struct{
  WORD            wType;
  BYTE            byRes[4];
  union{
           struct{
               DWORD                 alarmNo;
               BYTE                  byRes[128];
           }alarmRet;
           struct{
               DWORD                 motNo;
               BYTE                  byRes[128];
           }motionRet;
           struct{
               DWORD                 chanNo;
               BYTE                  ruleID;
           }vcaRet;
           struct{
               BYTE                  roomIndex;
               DWORD                 segmentNo;
               WORD                  segmetSize;
           }inquestRet;
  }seniorRet;
}Search_Event_Ret,*Search_Event_Ret;

java使用JNA模擬Union

public static class NET_DVR_SEARCH_EVENT_RET extends Structure {
	public short wType;
	public byte[] byRes = new byte[4];
	public static class SeniorRet extends Union {
		public static class AlarmRet extends Structure 
		{
			public int alarmNo;
			public byte[] byRes = new byte[128];
		}
		public static class MotionParam extends Structure 
		{
			public int motRet;
			public byte[] byRes = new byte[128];
	        }
		public static class VcaRet extends Structure
		{
			public int chanNo;
			public byte ruleID;
	        }
		public static class InquestRet extends Structure
		{
			public byte roomIndex;
			public int  segmentNo;
			public short segmetSize;
	        }	
	}
}

3.JNA模擬C回調函數

這裏只簡單介紹如何模擬回調函數,具體細節會在下一章節再詳細介紹

C回調函數定義

typedef BOOL (CALLBACK *device_register_cb)(
  LONG     iUserID,
  DWORD    dwDataType,
  void     *pOutBuffer,//輸出參數緩衝區
  DWORD    dwOutLen,//輸出參數大小
  void     *pInBuffer,//輸入參數緩衝區
  DWORD    dwInLen,//輸入參數大小
  void     *pUser
);

該回調函數的含義是,根據dwDataType的不一樣,pOutBuffer將指定不一樣的結構體對象,輸出不一樣的結果。pInBuffer用戶向回調函數中寫數據。

Java模擬回調函數實現:

public static interface DeviceRegisterCB extends StdCallCallback{
     public boolean invoke(NativeLong iUserID,int  dwDataType,
              DevRegInfo pOutBuffer,int dwOutLen,
              ServerInfo pInBuffer,int dwInLen,Pointer pUser);
}

在C中 void *pOutBuffer表示能夠指向任何類型,在運行時動態肯定。所以在這裏使用具體類型DevRegInfo pOutBuffer模擬void *pOutBuffer。

定義DeviceRegisterCB實現類:

public static interface DeviceRegisterCBImpl extends StdCallCallback{ 
     public boolean invoke(NativeLong iUserID,int dwDataType, 
                 DevRegInfo pOutBuffer,int dwOutLen,
                 ServerInfo pInBuffer,int dwInLen,Pointer pUser){ 
         System.out.println("數據類型:"+dwDataType); //...編寫具體的業務 
         return true; 
     }  
}

到此爲止,整個NativeLong startListen(CmsListenParam listenPara)方法涉及的結構體,回調函數已經定義完成,下面將參數傳遞到java方法調用原生函數:

CmsListenParam param=new CmsListenParam();
byte [] ip =stringToByteArray("127.0.0.1",128);//字符串轉化爲byte,這裏長度對應結構體中參數的長度
System.arraycopy(pAlarmIP, 0, param.struIPAdress.szIP, 0, ip.length);
param.struIPAdress.wPort =7200;
param.fnMsgCb=new DeviceRegisterCBImpl();
param.pUserData=Pointer.NULL;
param.byProtocolType=1;
param.write();	//爲什麼調用該方法?	
CmsServer.instance.startListen(param);

 

public static byte[] stringToByteArray(String str,int len){
	byte b[]=new byte[len];
	for(int i=0;i<str.length();i++){
		b[i]=Byte.parseByte((int)str.charAt(i)+"");
	}
	return b;
}

爲什麼在將參數傳遞到方法時,要先調用param.write()?

      Java 調用原生函數時,會把傳遞給原生函數的Java 數據固定在內存中,這樣原生函數才能夠訪問這些Java 數據。對於沒有固定住的Java 對象,GC 能夠刪除它,也能夠移動它在內存中的位置,以使堆上的內存連續。若是原生函數訪問沒有被固定住的Java 對象,就會致使調用失敗。Structure 類的write()方法會把結構體的全部字段固定住,使原生函數能夠訪問。

歡迎指出本文有誤的地方,轉載請註明原文出處https://my.oschina.net/7001/blog/672427

相關文章
相關標籤/搜索