# JNA

back2专题

# 添加依赖

对于maven工程直接在pom.xml中添加如下代码:

<!-- https://mvnrepository.com/artifact/net.java.dev.jna/jna
可以不用,只需要第二个依赖就好
		<dependency>
		    <groupId>net.java.dev.jna</groupId>
		    <artifactId>jna</artifactId>
		</dependency>
https://mvnrepository.com/artifact/net.java.dev.jna/jna-platform -->
<dependency>
    <groupId>net.java.dev.jna</groupId>
    <artifactId>jna-platform</artifactId>
</dependency>
1
2
3
4
5
6
7
8
9
10
11

非maven管理的项目手工添加jar包:
jna-4.5.2.jarjna-platform-4.5.2.jar(我这是截至2018-09-05时最新版本)

# 根据dll中接口函数开发相应代码

public interface IDeviceServiceDll extends Library {

//IDeviceServiceDll deviceService = (IDeviceServiceDll)Native.loadLibrary((Platform.isWindows() ? "msvcrt" : "c"), IDeviceServiceDll.class);
IDeviceServiceDll deviceService = (IDeviceServiceDll)Native.loadLibrary("XNetSDK", IDeviceServiceDll.class);
//IDeviceServiceDll deviceService = (IDeviceServiceDll)Native.loadLibrary("XNetSDK3", IDeviceServiceDll.class);
1
2
3
4
5

初始化接口库,dll文件要放在java.library.path中的路径下,如jdk安装目录的bin下,或者当前项目的根目录下,引用时直接写dll文件的名字(不带后缀)。

# 开发接口

  • 如以下接口1

返回值为int,入参是一个结构体指针,且该结构体中存在一个回调函数; 该回调函数C++中如下:

typedef int (CALLBACK *PXSDK_MessageCallBack) (XSDK_HANDLE hDevice, int nMsgId, int nParam1, int nParam2, int nParam3, const char *szString, void *pObject, int nSeq, void *pUserData);
1

JAVA中定义回调函数:

先定义回调函数对应的接口

public interface PXSDK_MessageCallBack extends Callback{
public void MessageHandle(int hDevice,int nMsgId,int nParam1,int nParam2,int nParam3,String szString,String pObject,int nSeq,String pUserData);
}
1
2
3

再定义其实现类:

public interface PXSDK_MessageCallBack extends Callback{
    public void MessageHandle(int hDevice,int nMsgId,int nParam1,int nParam2,int nParam3,String szString,String pObject,int nSeq,String pUserData);
}
public class PXSDK_MessageCallBackImpl implements PXSDK_MessageCallBack{
    @Override
    public void MessageHandle(int hDevice, int nMsgId, int nParam1, int nParam2, int nParam3, String szString,
            String pObject, int nSeq, String pUserData) {
        // TODO 自动生成的方法存根
        System.out.println("\n回调:"+hDevice+"\n"+pUserData+"\n"+nMsgId);
    }
        }
1
2
3
4
5
6
7
8
9
10
11

PS:接口继承的 Callback,也可以是StdCallCallback

# 定义该接口入参的结构体

public static class SXSDKInitParam extends Structure {
    public int nLogLevel;
    public PXSDK_MessageCallBack pMsgCallBack;
    public String pUserData;
    public static class ByReference extends SXSDKInitParam implements Structure.ByReference {}
    public static class ByValue  extends SXSDKInitParam implements Structure.ByValue  {}
    @Override
    protected List<String> getFieldOrder() {
        // TODO 自动生成的方法存根
        List<String> a = new ArrayList<String>();
        a.add("nLogLevel");
        a.add("pMsgCallBack");
        a.add("pUserData");
        return a;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# 定义该接口

public int XSDK_Init(SXSDKInitParam.ByReference pParam);
1

ByReferenceByValue,貌似目前没看出区别来,网传一个是针对指针,一个是针对结构体本身。

至此基本定义就结束,现在来测试。

# 调用测试

public static void main(String[] args) {
IDeviceServiceDll.SXSDKInitParam.ByReference initData = new IDeviceServiceDll.SXSDKInitParam.ByReference();
    IDeviceServiceDll.PXSDK_MessageCallBack messageCallBack = new IDeviceServiceDll.PXSDK_MessageCallBackImpl();
    initData.nLogLevel=3;
    initData.pMsgCallBack = messageCallBack;
    initData.pUserData = "什么鬼";
    result = IDeviceServiceDll.deviceService.XSDK_Init(initData);
    System.out.println("初始化函数返回值:"+result);
}
1
2
3
4
5
6
7
8
9

至此调用成功,返回正常调用结果值。

  • 再如以下接口2
typedef struct SXSDKLoginParam
{
char sDevId[128]; // ip/dns/sn
int  nDevPort;
char sUserName[64];
char sPassword[64];
EDEV_NET_TYPE  nCnnType;//枚举类型,两个枚举量
}SXSDKLoginParam;
1
2
3
4
5
6
7
8

实现如下:

public static class SXSDKLoginParam extends Structure {
    public byte[] sDevId = new byte[128];
    public int nDevPort;
    public byte[] sUserName = new byte[64];
    public byte[] sPassword = new byte[64];
    public int nCnnType;
    public static class ByReference extends SXSDKLoginParam implements Structure.ByReference {}
    public static class ByValue  extends SXSDKLoginParam implements Structure.ByValue  {}
    @Override
    protected List<String> getFieldOrder() {
        // TODO 自动生成的方法存根
        List<String> a = new ArrayList<String>();
        a.add("sDevId");
        a.add("nDevPort");
        a.add("sUserName");
        a.add("sPassword");
        a.add("nCnnType");
        return a;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20

结构体中存在byte时,传值时需注意

IDeviceServiceDll.SXSDKLoginParam.ByReference loginData = new IDeviceServiceDll.SXSDKLoginParam.ByReference();
    int hDevice = 0;
    loginData.sDevId = Arrays.copyOf(device.getDeviceIp().getBytes(), 128);
    loginData.nDevPort = Integer.valueOf(device.getDevicePort());
    loginData.sUserName = Arrays.copyOf(device.getDeviceUserName().getBytes(), 64);
    loginData.sPassword = Arrays.copyOf(device.getDevicePassword().getBytes(), 64);
    loginData.nCnnType = EDEV_NET_TYPE.EDEV_CNN_TYPE_IP_DNS.getType();
    hDevice = IDeviceServiceDll.deviceService.XSDK_DevLogin(loginData, loginTimeout, 0);
1
2
3
4
5
6
7
8

否则的话,传值会错乱,java传结构体给c,是把申请的连续内存整个传过去,c就依次找,
第一个值,c会在第一块128个字节的内存里找,到了第二个参数,内存位置就不对了,后面的参数值就会错的更离谱,但是基本上每次的值都一样,无论你传的是什么值。

# 类型对照表

Native Type Java Type Native Representation
char byte 8-bit integer
wchar_t char 16/32-bit character
short short 16-bit integer
int int 32-bit integer
int boolean 32-bit integer (customizable)
long, __int64 long 64-bit integer
long long long 64-bit integer
float float 32-bit FP
double double 64-bit FP
pointer Buffer/Pointer
pointer array[](array of primitive type)
char* String
wchar_t* WString
char** String[]
wchar_t** WString[]
void* Pointer
void ** PointerByReference
int& IntByReference
int* IntByReference
struct Structure
(*fp)() Callback
varies NativeMapped
long NativeLong
pointer PointerType

# 支持常见的数据类型的映射

Java类型 C 类型 原生表现
String char* /0结束的数组 (native encoding or jna.encoding)
WString wchar_t* /0结束的数组(unicode)
String[] char** /0结束的数组的数组
WString[] wchar_t** /0结束的宽字符数组的数组
Structure struct*、struct 指向结构体的指针 (参数或返回值) (或者明确指定是结构体指针)结构体(结构体的成员) (或者明确指定是结构体)
Union union 等同于结构体
Structure[] struct[] 结构体的数组,邻接内存
Callback <T>(*fp)() Java函数指针或原生函数指针
NativeMapped varies 依赖于定义
NativeLong long 平台依赖(32或64位整数)
PointerType pointer 和 Pointer相同

# 和操作系统数据类型的对应表

Java 类型 C 类型 原生表现
boolean int 32位整数 (可定制)
byte char 8位整数
char wchar_t 平台依赖
short short 16位整数
int int 32位整数
long long long, __int64 64位整数
float float 32位浮点数
double double 64位浮点数
Buffer Pointer pointer平台依赖(32或 64位指针)
<T>[](基本类型的数组) pointer、array 32或 64位指针(参数/返回值)邻接内存(结构体成员)

BYTE,byte就是int值就可,例如:(byte)1;

# 出现结构体字段不匹配

看是否拼写不一致、再看java中结构体类中的变量是不是public。

public static class SXMediaRealPlayReq extends Structure{
    public int nChannel;
    public int nStreamType;
    public int nRequestType;
    public PXSDK_MediaCallBack pMediaCallback;
    public Pointer pUserData;
    public static class ByReference extends SXMediaRealPlayReq implements Structure.ByReference {}
    public static class ByValue  extends SXMediaRealPlayReq implements Structure.ByValue  {}

    @Override
    protected List<String> getFieldOrder() {
        // TODO 自动生成的方法存根
        List<String> a = new ArrayList<String>();
        a.add("nChannel");
        a.add("nStreamType");
        a.add("nRequestType");
        a.add("pMediaCallback");
        a.add("pUserData");
        return a;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

# c语言返回的结构体指针如何转换

以下是通用方法

public static <T extends Structure> T p2Structure(Pointer pParam, Class<T> clazz) throws InstantiationException, IllegalAccessException {
    T result = clazz.newInstance();
    result.write();//先写入内存
    Pointer p = result.getPointer();//获取java结构体指针
    p.write(0, pParam.getByteArray(0, result.size()), 0, result.size());//写入获取到的数据
    result.read();
    return result;
}
1
2
3
4
5
6
7
8

其中:pParam为c语言返回的结构体指针,T为java的结构体。

# 指针

原生代码中的指针,可以使用Pointer类型,或者PointerType类型及它们的子类型来模拟。
Pointer代表原生代码中的指针。其属性peer就是原生代码中指针的地址。
我们不可以直接创建Pointer对象,但可以用它表示原生函数中的任何指针。
Pointer类有2个子类:Function,Memory
Function类代表原生函数的指针,可以通过invoke(Class,Object[],Map)这一系列的方法调用原生函数。
Memory类代表的是堆中的一段内存,它也是我们可以创建的Pointer子类。
创建一个Memory类的实例,就是在原生代码的内存区中分配一块指定大小的内存。这块内存会在GC释放这个Java对象时被释放。
Memory类在指针模拟中会被经常用到。

PointerType类代表的是一个类型安全的指针。
ByReference类是PointerType类的子类。
ByReference类代表指向堆内存的指针。
ByReference类非常简单。

ByteByReference, DoubleByReference,FloatByReference, IntByReference, LongByReference, NativeLongByReference,PointerByReference, ShortByReference, W32API.HANDLEByReference,X11.AtomByReference, X11.WindowByReference
1

ByteByReference等类故名思议,就是指向原生代码中的字节数据的指针。
PointerByReference类表示指向指针的指针。
在JNA中模拟指针,最常用到的就是Pointer类和PointerByReference类。Pointer类代表指向任何东西的指针,PointerByReference类表示指向指针的指针。Pointer类更加通用,事实上PointerByReference类内部也持有Pointer类的实例。
PointerByReference类可以嵌套使用,它所指向的指针,本身可能也是指向指针的指针。

# 结构体内部可以包含结构体对象的指针的数组

struct CompanyStruct{
    long id;
wchar_t*  name;
UserStruct* users[100];
int count;
};
1
2
3
4
5
6

模拟如下:

public static class CompanyStruct{
public NativeLong id;

        public WString  name;

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

        public int count;
}
1
2
3
4
5
6
7
8
9

# 总结

JNA打破了Java和原生代码原本泾渭分明的界限,实现了Java和原生代码的强强联合,在各自擅长的领域分工合作,快速解决问题。
Java可以方便地利用原生代码的优势:执行速度快,可以直接操作硬件,机器码不容易被破解等。 原生代码可以通过回调Java函数,利用Java的优势:开发效率高,自动内存管理,跨平台,类库丰富,网络功能强大,支持多种脚本语言等。
JNA为Java开发者打开了一扇通向广袤的原生代码世界的大门。

# 实例

# 热成像示例

/**
 * 设备网络地址信息
 */
public static class NET_INET_ADDR extends Structure {

    public byte[] szHostIP = new byte[CONST_MAXLENGTH_IP];//IP地址(点分符形式)
    public int nPORT;//端口号
    public int nIPProtoVer;//IP协议版本(1:IPv4协议,2:IPv6协议)
    @Override
    protected List<String> getFieldOrder() {
        // TODO 自动生成的方法存根
        List<String> a = new ArrayList<String>();
        a.add("szHostIP");
        a.add("nPORT");
        a.add("nIPProtoVer");
        return a;
    }
}

/**
   * 设备信息
   */
  public static class NET_DEVICE_INFO extends Structure {


      public NET_INET_ADDR struInetAddr = new NET_INET_ADDR();
      public byte[] szUserID = new byte[CONST_MAXLENGTH_USERID];//登陆设备的用户ID
      public byte[] szPassword = new byte[CONST_MAXLENGTH_PASSWORD];//登陆设备的密码
      public byte[] szDeviceID = new byte[CONST_MAXLENGTH_DEVICEID];//设备ID
      public byte[] szDeviceName = new byte[CONST_MAXLENGTH_DEVICENAME];//设备名称
      public int nDeviceType;//设备类型
@Override
protected List<String> getFieldOrder() {
   // TODO 自动生成的方法存根
   List<String> a = new ArrayList<String>();
   a.add("struInetAddr");
   a.add("szUserID");
   a.add("szPassword");
   a.add("szDeviceID");
   a.add("szDeviceName");
   a.add("nDeviceType");
   return a;
}
  }

/**
   *   设备基本信息结构体
   * @author huting@four-faith.com
   *
   * @date 2019年3月27日
   * @time 下午2:02:27
   */
  public static class NET_DEVICE_INFOEX extends Structure {                                                    //设备类型

      public NET_DEVICE_INFO struDeviceInfo;
      public int nRouterMappingEnableFlag;//路由器映射标志
      public byte[] szRouterAddr = new byte[CONST_MAXLENGTH_IP];//路由器地址,IP地址或域名
      public int nRouterMappingControlPort;//控制映射端口
      public int nRouterMappingTCPAVPort;//TCP音视频映射端口
      public int nRouterMappingRTSPPort;//RTSP映射端口
      public int nRouterMappingRTPPort;//RTP映射端口
      public int nRouterMappingRTCPPort;//RTCP映射端口
@Override
protected List<String> getFieldOrder() {
   // TODO 自动生成的方法存根
   List<String> a = new ArrayList<String>();
   a.add("struDeviceInfo");
   a.add("nRouterMappingEnableFlag");
   a.add("szRouterAddr");
   a.add("nRouterMappingControlPort");
   a.add("nRouterMappingTCPAVPort");
   a.add("nRouterMappingRTSPPort");
   a.add("nRouterMappingRTPPort");
   a.add("nRouterMappingRTCPPort");
   return a;
}
  }
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77

回调函数

package com.ffCamera.service.cdll;

import com.ffCamera.service.SNNetSdk.NET_ALARM_INFO;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
import com.sun.jna.win32.StdCallLibrary.StdCallCallback;

/**
 *     SNNetSdk_捷安视讯热感报警信息回调类
 * @author huting@four-faith.com
 *
 * @date 2019年2月19日
 * @time 下午5:09:46
 */
public interface AlarmCallBack extends StdCallCallback {
    /**
     * 报警回调
     *
     * @param lCMSHandle CMS Alarm Handle
     * @param lpAlarmInfo 报警信息
     * @param lpUserData 用户指针
     */
    public void invoke(NativeLong lCMSHandle, NET_ALARM_INFO lpAlarmInfo, Pointer lpUserData);

}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25

实现

package com.ffCamera.service.cdll;

import java.text.ParseException;
import java.text.SimpleDateFormat;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import com.ffCamera.data.AlarmMajorType;
import com.ffCamera.data.SecurityAlarmMinorType;
import com.ffCamera.data.TableNameEnum;
import com.ffCamera.entity.TsBiVideo;
import com.ffCamera.entity.TsDataVideo;
import com.ffCamera.service.ITsBiVideoService;
import com.ffCamera.service.ITsDataVideoService;
import com.ffCamera.service.SNNetSdk.NET_ALARM_INFO;
import com.ffCamera.util.CommonUtils;
import com.ffCamera.util.UuidUtil;
import com.sun.jna.NativeLong;
import com.sun.jna.Pointer;
/**
 *     SNNetSdk_捷安视讯热感报警信息回调实现类
 * @author huting@four-faith.com
 *
 * @date 2019年2月19日
 * @time 下午5:09:00
 */
public class AlarmCallbackImpl implements AlarmCallBack {
   public static Logger log = LoggerFactory.getLogger(AlarmCallbackImpl.class);
   private ITsBiVideoService tsBiVideoService;
   private ITsDataVideoService tsDataVideoService;
   public AlarmCallbackImpl(ITsBiVideoService  tsBiVideoService,ITsDataVideoService tsDataVideoService) {
      // TODO 自动生成的构造函数存根
      this.tsBiVideoService=tsBiVideoService;
      this.tsDataVideoService=tsDataVideoService;
   }
   public AlarmCallbackImpl() {
      // TODO 自动生成的构造函数存根
   }

   @Override
   public void invoke(NativeLong lCMSHandle, NET_ALARM_INFO lpAlarmInfo, Pointer lpUserData) {
      // TODO 自动生成的方法存根
      if(lpAlarmInfo.nAlarmFlag==1) {
         SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
         String gatherTime = lpAlarmInfo.struAlarmTime.nYear+"-"+
               (lpAlarmInfo.struAlarmTime.nMonth>9?lpAlarmInfo.struAlarmTime.nMonth:("0"+lpAlarmInfo.struAlarmTime.nMonth))+"-"+
               (lpAlarmInfo.struAlarmTime.nDay>9?lpAlarmInfo.struAlarmTime.nDay:("0"+lpAlarmInfo.struAlarmTime.nDay))+" "+
               (lpAlarmInfo.struAlarmTime.nHour>9?lpAlarmInfo.struAlarmTime.nHour:("0"+lpAlarmInfo.struAlarmTime.nHour))+":"+
               (lpAlarmInfo.struAlarmTime.nMinute>9?lpAlarmInfo.struAlarmTime.nMinute:("0"+lpAlarmInfo.struAlarmTime.nMinute))+":"+
               (lpAlarmInfo.struAlarmTime.nSecond>9?lpAlarmInfo.struAlarmTime.nSecond:("0"+lpAlarmInfo.struAlarmTime.nSecond));
         try {
            TsDataVideo tsDataVideo = new TsDataVideo();
            if(tsBiVideoService==null) {
               return;
            }
            //根据设备ID获取视频设备信息
            TsBiVideo tsBiVideo = this.tsBiVideoService.queryByDeviceNo(CommonUtils.getCopyByte(lpAlarmInfo.szDeviceId));
            TsDataVideo tmp = null;
            do {
               String uuid = UuidUtil.getId();
               tsDataVideo.setId(uuid);
               tmp = this.tsDataVideoService.queryById(uuid);
            }while(tmp!=null && !CommonUtils.isEmptyOrWhitespaceOnly(tmp.getId()));
            tsDataVideo.setProjectno(tsBiVideo.getProjectno());//项目编号
            tsDataVideo.setOffcomno(tsBiVideo.getOffcomno());//单位编号
            tsDataVideo.setName(tsBiVideo.getName());//报警设备名称
            tsDataVideo.setNo(tsBiVideo.getNo());//设备唯一编号
            tsDataVideo.setGathertime(sdf.parse(gatherTime));//报警信息采集时间
            tsDataVideo.setAlarm(AlarmMajorType.getMessage(lpAlarmInfo.nMajorType)+"-"+SecurityAlarmMinorType.getMessage(lpAlarmInfo.nMinorType));//报警类型
            this.tsDataVideoService.saveVideoAlarmInfo(tsDataVideo,TableNameEnum.TS_DATA_VIDEO.getIndex());
            //删除相关报警信息,再新增最新报警信息
            this.tsDataVideoService.deleteVideoLastByNo(tsBiVideo.getNo(), tsBiVideo.getProjectno());
            this.tsDataVideoService.saveVideoAlarmInfo(tsDataVideo,TableNameEnum.TS_DATA_VIDEO_LAST.getIndex());
         } catch (ParseException e) {
            // TODO 自动生成的 catch 块
            e.printStackTrace();
            log.error(e.getMessage());
         }
         log.info("报警标志:"+lpAlarmInfo.nAlarmFlag);
         log.info("报警类型:主-"+lpAlarmInfo.nMajorType+",子-"+lpAlarmInfo.nMinorType);
         log.info("报警时间:"+
               lpAlarmInfo.struAlarmTime.nYear+"-"+
               lpAlarmInfo.struAlarmTime.nMonth+"-"+
               lpAlarmInfo.struAlarmTime.nDay+" "+
               lpAlarmInfo.struAlarmTime.nHour+":"+
               lpAlarmInfo.struAlarmTime.nMinute+":"+
               lpAlarmInfo.struAlarmTime.nSecond);
         log.info("报警设备ID:"+CommonUtils.getCopyByte(lpAlarmInfo.szDeviceId));
         log.info("报警设备地址:"+CommonUtils.getCopyByte(lpAlarmInfo.szDeviceIp));
         log.info("报警源ID:"+CommonUtils.getCopyByte(lpAlarmInfo.szSourceId)+",报警源名称:"+CommonUtils.getCopyByte(lpAlarmInfo.szSourceName));
      }
   }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94

# 海康云台控制

BOOL NET_DVR_Init();
/**
 * 初始化SDK,调用其他SDK函数的前提。
 * TRUE表示成功,FALSE表示失败。
 * 接口返回失败请调用NET_DVR_GetLastError获取错误码,通过错误码判断出错原因。
 * @return
 */
boolean  NET_DVR_Init();

LONG NET_DVR_Login_V30(
  char                       *sDVRIP,
  WORD                       wDVRPort,
  char                       *sUserName,
  char                       *sPassword,
  LPNET_DVR_DEVICEINFO_V30   lpDeviceInfo
);
struct{
  BYTE     sSerialNumber[SERIALNO_LEN];
  BYTE     byAlarmInPortNum;
  BYTE     byAlarmOutPortNum;
  BYTE     byDiskNum;
  BYTE     byDVRType;
  BYTE     byChanNum;
  BYTE     byStartChan;
  BYTE     byAudioChanNum;
  BYTE     byIPChanNum;
  BYTE     byZeroChanNum;
  BYTE     byMainProto;
  BYTE     bySubProto;
  BYTE     bySupport;
  BYTE     bySupport1;
  BYTE     bySupport2;
  WORD     wDevType;
  BYTE     bySupport3;
  BYTE     byMultiStreamProto;
  BYTE     byStartDChan;
  BYTE     byStartDTalkChan;
  BYTE     byHighDChanNum;
  BYTE     bySupport4;
  BYTE     byLanguageType;
  BYTE     byVoiceInChanNum;
  BYTE     byStartVoiceInChanNo;
  BYTE     byRes3[2];
  BYTE     byMirrorChanNum;
  WORD     wStartMirrorChanNo;
  BYTE     byRes2[2];
}NET_DVR_DEVICEINFO_V30,*LPNET_DVR_DEVICEINFO_V30;

```java
int  NET_DVR_Login_V30(String sDVRIP, short wDVRPort, String sUserName, String sPassword, NET_DVR_DEVICEINFO_V30 lpDeviceInfo);
/**
 * NET_DVR_Login_V30()参数结构
 */
public static class NET_DVR_DEVICEINFO_V30 extends Structure
{
    public  byte[] sSerialNumber = new byte[HcNetSdkDllConstants.SERIALNO_LEN]; //序列号
    public  byte byAlarmInPortNum;                                            //报警输入个数
    public  byte byAlarmOutPortNum;                                               //报警输出个数
    public  byte byDiskNum;                                                     //硬盘个数
    public  byte byDVRType;                                                     //设备类型, 1:DVR 2:ATM DVR 3:DVS ......
    public  byte byChanNum;                                                     //模拟通道个数
    public  byte byStartChan;                                                //起始通道号,例如DVS-1,DVR - 1
    public  byte byAudioChanNum;                                                //语音通道数
    public  byte byIPChanNum;                                              //最大数字通道个数
    public  byte[] byRes1 = new byte[24];                                  //保留

    @Override
    protected List<String> getFieldOrder() {
        // TODO 自动生成的方法存根
        List<String> a = new ArrayList<String>();
        a.add("sSerialNumber");
        a.add("byAlarmInPortNum");
        a.add("byAlarmOutPortNum");
        a.add("byDiskNum");
        a.add("byDVRType");
        a.add("byChanNum");
        a.add("byStartChan");
        a.add("byAudioChanNum");
        a.add("byIPChanNum");
        a.add("byRes1");
        return a;
    }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
BOOL NET_DVR_PTZCruise_Other(
  LONG     lUserID,
  LONG     lChannel,
  DWORD    dwPTZCruiseCmd,
  BYTE     byCruiseRoute,
  BYTE     byCruisePoint,
  WORD     wInput
);
1
2
3
4
5
6
7
8
/**
 * 云台巡航操作。(不需要开启预览)
 * @param lUserID NET_DVR_Login_V40等登录接口的返回值
 * @param lChannel 通道号
 * @param dwPTZCruiseCmd 操作云台巡航命令,见PtzCruiseCmdEnum
 * @param byCruiseRoute 巡航路径,最多支持32条路径(序号从1开始)
 * @param byCruisePoint 巡航点,最多支持32个点(序号从1开始)
 * @param wInput 不同巡航命令时的值不同,预置点(最大300)、时间(最大255)、速度(最大40)
 * @return
 */
boolean  NET_DVR_PTZCruise_Other(int lUserID,int lChannel,int dwPTZCruiseCmd,byte byCruiseRoute, byte byCruisePoint, short wInput);
1
2
3
4
5
6
7
8
9
10
11