JNI 技术是双向的,既可以从Java 代码中调用原生函数,也可以从原生函数中直接创建
Java 虚拟机,并调用Java 代码。但是在原生函数中调用java代码要写大量C代码,这对大多数java程序员来说是很头疼的。
使用JNA,我们不用编写C代码就能在原生代码中调用java代码。JNA 可以模拟函数指针,通过函数指针,就可以实现在原生代码中调用Java 函数。
下面直接用代码进行说明:
原生代码定义:
//方法定义
LONG StartListenServer(const Alarm_Listen_Param *param);
//Alarm_Listen_Param结构体
struct{
IpAddress struIPAdress;
MessageCallBack fnMsgCb;
void *pUserData;
BYTE byProtocolType;
BYTE byRes[31];
}Alarm_Listen_Param, *Alarm_Listen_Param;
//ip结构体
struct{
char szIP[128];
WORD wPort;
BYTE byRes[2];
}IpAddress, *IpAddress;
//回调函数声明
typedef BOOL (CALLBACK *MessageCallBack)(
LONG iHandle,
AlarmMessage *pAlarmMsg,
void *pUserData
);
//启动参数结构体
struct{
DWORD alarmType;
void *alarmInfo;
DWORD alarmInfoLen;
void *pXmlBuf;
DWORD xmlBufLen;
BYTE byRes[32];
}AlarmMessage, *AlarmMessage;
//返回值结构体
struct{
DWORD dwSize;
char alarmTime[32];
char deviceID[256];
DWORD alarmType;
DWORD alarmAction;
DWORD videoChannel;
DWORD alarmInChannel;
DWORD diskNumber;
BYTE remark[64];
BYTE retransFlag;
BYTE byRes[63];
}AlarmInfo,*AlarmInfo;
java代码实现:
public interface AlarmServer extends StdCallLibrary{
public static final int UNKNOWN =0;
public static final int ALARM =1;
public static final int REPORT =3;
public final static int MAX_DEVICE_ID_LEN =256;
public final static int MAX_TIME_LEN =32;
public final static int MAX_REMARK_LEN =64;
//创建唯一实例
AlarmServer INSTANCE=(AlarmServer ) Native.loadLibrary("AlarmServer",AlarmServer .class);
//启动参数结构体
public static class Alarm_Listen_Param extends Structure{
public IpAddress struIPAdress;
public MessageCallBack fnMsgCb;//回调函数
public Pointer pUserData;
public byte byProtocolType;
public byte[] byRes=new byte[31];
}
//ip结构体
public static class IpAddress extends Structure{
public byte[] ip=new byte[128];
public short port;
public byte[] byRes=new byte[2];
}
//回调函数参数结构体
public static class AlarmMessage extends Structure{
public int alarmType;//类型
public Pointer alarmInfo;//内容
public int alarmInfoLen;//缓冲区大小
public String xmlBuf;//内容(XML)
public int xmlBufLen;//内容大小
public byte[] byRes=new byte[20];
}
//返回值结构体
public static class AlarmInfo extends Structure{
public int size;
public byte[] alarmTime=new byte[MAX_TIME_LEN];
public byte[] deviceID=new byte[MAX_DEVICE_ID_LEN];
public int alarmType;
public int alarmAction;
public int videoChannel;
public int alarmInChannel;
public int diskNumber;
public byte[] remark=new byte[MAX_REMARK_LEN];
public byte retransFlag;
public byte[] byRes=new byte[63];
}
//回调函数定义
public static interface MessageCallBack extends StdCallCallback{
public boolean invoke(NativeLong iHandle,
AlarmMessage pAlarmMsg,Pointer pUserData);
}
}
回调函数实现类:
//回调函数具体实现类,处理业务逻辑
public class AlarmServerImpl {
public static class MessageCallBackImpl implements MessageCallBack{
public boolean invoke(NativeLong iHandle,
AlarmMessage alarmMsg,Pointer pUserData){
boolean isSuccess=false;
try{
int dwType=alarmMsg.alarmType;
switch(dwType){
case AlarmServer.UNKNOWN:
System.out.println("未知类型");
break;
case AlarmServer.ALARM:
AlarmInfo alarmInfo=new AlarmInfo();
alarmInfo.write();
Pointer p=alarmInfo.getPointer();
p.write(0,alarmMsg.alarmInfo.getByteArray(0,
alarmMsg.alarmInfoLen),0, alarmMsg.alarmInfoLen);
alarmInfo.read();
System.out.println("设备id="+newString(alarmInfo.deviceID).trim());
System.out.println("报警内容="+alarmMsg.xmlBuf);
//...具体业务逻辑
case AlarmServer.REPORT:
//...具体业务逻辑
break;
default:
break;
}
isSuccess=true;
}catch(Exception e){
e.printStackTrace();
}
return isSuccess;
}
}
}
原生函数可以通过函数指针实现函数回调,调用外部函数来执行任务。JNA 可以方便地模拟函数指针,把Java 函数作为函数指针传递给原生函数,实现在原生代码中调用Java 代码。
代码说明:
AlarmInfo alarmInfo=new AlarmInfo();
alarmInfo.write();
Pointer p=alarmInfo.getPointer();
p.write(0,alarmMsg.alarmInfo.getByteArray(0,alarmMsg.alarmInfoLen),0, alarmMsg.alarmInfoLen);
alarmInfo.read();
alarmMsg.alarmInfo在结构体中是指针类型,指向的内容根据dwType不同而不同,由于Pointer指向的是内存块,因此需要将内存中的数据转为byte流写入具体结构体中,才能解析数据。因此,这里对结构体的定义要求必须完全正确,即每个字段的长度,字段的顺序都必须严格对应原生代码中的结构体,否则解析结果就会不正确。