Search code examples
node-ffi

How to call dll method with struct return?


I want to build an Electron APP which could access Digital Video Recording (DVR) product from Dahua. I use node-ffi to access dhnetsdk.dll provided from Dahua and could call CLIENT_Init method to initialize SDK, but I could not call CLIENT_LoginEx2 method to log into DVR device.

The problem could be C++ pointer, but I'm not sure. The code shows below.

  1. CLIENT_LoginEx2 method defined in dhnetsdk.h
CLIENT_NET_API LLONG CALL_METHOD CLIENT_LoginEx2(
  const char *pchDVRIP, 
  WORD wDVRPort, 
  const char *pchUserName, 
  const char *pchPassword, 
  EM_LOGIN_SPAC_CAP_TYPE emSpecCap, 
  void* pCapParam, 
  LPNET_DEVICEINFO_Ex lpDeviceInfo, 
  int *error = 0
);
  1. Implement CLIENT_LoginEx2 method in index.ts (by node-ffi)
const DVR = ffi.Library('dhnetsdk.dll', {
  // SDK
  CLIENT_Init: [ref.types.bool, ['pointer', ref.types.int64]],
  CLIENT_GetSDKVersion: [ref.types.uint, []],
  // Login
  CLIENT_LoginEx2: [
    ref.types.int64, [
      ref.types.char, 
      ref.types.ushort, 
      ref.types.char, 
      ref.types.char, 
      ref.types.int, 
      'void *', 
      ref.refType(NET_DEVICEINFO_Ex), 
      ref.types.int
    ]
  ],
});
  1. Define NET_DEVICEINFO_Ex data structure in index.ts (the structure reference from dhnetsdk.dll)

index.ts

const NET_DEVICEINFO_Ex = StructType({
  'sSerialNumber': ArrayType(ref.types.byte, DH_DEV_SERIALNO_LEN),
  'nAlarmInPortNum': ref.types.int,
  'nAlarmOutPortNum': ref.types.int,
  'nDiskNum': ref.types.int,
  'nDVRType': ref.types.int,
  'nChanNum': ref.types.int,
  'byLimitLoginTime': ref.types.byte,
  'byLeftLogTimes': ref.types.byte,
  'bReserved': ArrayType(ref.types.byte, 2),
  'nLockLeftTime': ref.types.int,
  'Reserved': ArrayType(ref.types.char, 24),
});

dhnetsdk.h

typedef struct
{
    BYTE                sSerialNumber[DH_SERIALNO_LEN];
    int                 nAlarmInPortNum;
    int                 nAlarmOutPortNum;
    int                 nDiskNum;
    int                 nDVRType;
    int                 nChanNum;
    BYTE                byLimitLoginTime;
    BYTE                byLeftLogTimes;
    BYTE                bReserved[2];
    int                 nLockLeftTime;
    char                Reserved[24];
} NET_DEVICEINFO_Ex, *LPNET_DEVICEINFO_Ex;
  1. Actually call
let lpDeviceInfo = ref.alloc(NET_DEVICEINFO_Ex);
let error = 0;
console.log(DVR.CLIENT_Init(disConnectCallback, 0));
console.log(DVR.CLIENT_LoginEx2('192.168.1.100', 37777, 'admin', 'dh123456', 0, null, lpDeviceInfo, error));
console.log(DVR.CLIENT_GetSDKVersion());

When I ran the code, the result of DVR.CLIENT_Init & DVR.CLIENT_GetSDKVersion returned as below screenshot, I could confirm that sdk works.

Screenshot 1

When I called DVR.CLIENT_LoginEx2 then the Electron app crashed and returned as below screenshot.

DevTools was disconnected from the page. Once page is reloaded, DevTools will automatically reconnect.

Screenshot 2

I thought it might cause by wrong data structure define, but I really do not know how to trace where be wrong. Please give the advice, really appreciate.

UPDATE May 3

The SDK manual wrote a snippet to demonstrate how to call CLIENT_LoginEx2 as below

LLONG lLoginHandle = CLIENT_LoginEx2(szDevIp, nPort, szUserName, szPasswd,
EM_LOGIN_SPEC_CAP_TCP, NULL, &stDevInfo, &nError);

It shows the last two arguments need to pass pointer, the argument &stDevInfo is a pointer to struct of NET_DEVICEINFO_Ex data structure, and the argument &nError is a pointer to return error code from method callback.

I guess it might be the crash cause but not sure, anyone could help me clarify what issue is, thanks a lot.

UPDATE May 3

It might works after some efforts, I re-defined ffi.Library as below The point is 'char*' need passed by ref.refType('char') or 'string' in short; 'void*' need passed by ref.refType('void') or 'pointer' in short; 'int*' need passed by ref.refType('int') or 'int*' in short.

const NetDeviceInfoEx = StructType({
  'sSerialNumber': ArrayType(ref.types.byte, DH_DEV_SERIALNO_LEN),
  'nAlarmInPortNum': ref.types.int,
  'nAlarmOutPortNum': ref.types.int,
  'nDiskNum': ref.types.int,
  'nDVRType': ref.types.int,
  'nChanNum': ref.types.int,
  'byLimitLoginTime': ref.types.byte,
  'byLeftLogTimes': ref.types.byte,
  'bReserved': ArrayType(ref.types.byte, 2),
  'nLockLeftTime': ref.types.int,
  'Reserved': ArrayType(ref.types.char, 24),
});
const NetDeviceInfoExPtr = ref.refType(NetDeviceInfoEx);

CLIENT_LoginEx2: [
  ref.types.int64, [
    'string', 
    ref.types.ushort, 
    'string', 
    'string', 
    ref.types.int, 
    'pointer', 
    NetDeviceInfoExPtr, // or ref.refType('NetDeviceInfoEx')
    'int*'
  ]
],

Actually called DLL by below code Please notice that we need define output parameters variables to fetch message from DLL returned.

// Output Parameters
var lpDeviceInfo = ref.alloc(NetDeviceInfoEx);
var error = ref.alloc('int');

var v = DVR.CLIENT_LoginEx2('192.168.1.100', 37777, 'admin', 'dh123456', EM_LOGIN_SPAC_CAP_TYPE.EM_LOGIN_SPEC_CAP_TCP.value, null, lpDeviceInfo, error);
console.log(v);
console.log(lpDeviceInfo.deref());

Because I don't have DVR device to test the code, could not confirm that it works or not. I'll test it after entering the office tomorrow.


Solution

  • The last update in my question post, I figure out the correct way to define ffi.Library.

    I make mistakes cause by misunderstand of C language. The point is 'char*' need passed by ref.refType('char') or 'string' in short; 'void*' need passed by ref.refType('void') or 'pointer' in short; 'int*' need passed by ref.refType('int') or 'int*' in short.

    So I've rewrote the code as below and it works finally.

    const NetDeviceInfoEx = StructType({
      'sSerialNumber': ArrayType(ref.types.byte, DH_DEV_SERIALNO_LEN),
      'nAlarmInPortNum': ref.types.int,
      'nAlarmOutPortNum': ref.types.int,
      'nDiskNum': ref.types.int,
      'nDVRType': ref.types.int,
      'nChanNum': ref.types.int,
      'byLimitLoginTime': ref.types.byte,
      'byLeftLogTimes': ref.types.byte,
      'bReserved': ArrayType(ref.types.byte, 2),
      'nLockLeftTime': ref.types.int,
      'Reserved': ArrayType(ref.types.char, 24),
    });
    const NetDeviceInfoExPtr = ref.refType(NetDeviceInfoEx);
    
      /**
       * LLONG CLIENT_LoginEx2(
       *   const char *pchDVRIP,
       *   WORD wDVRPort,
       *   const char *pchUserName,
       *   const char *pchPassword,
       *   EM_LOGIN_SPAC_CAP_TYPE emSpecCap,
       *   void* pCapParam,
       *   LPNET_DEVICEINFO_Ex lpDeviceInfo,
       *   int *error
       * );
       */
      CLIENT_LoginEx2: [ref.types.int64, ['string', ref.types.ushort, 'string', 'string', ref.types.int, 'pointer', NetDeviceInfoExPtr, 'int*']],
    

    Actually called DLL by below code. Please notice that we need define output parameters variables to fetch message from DLL returned.

    // Output Parameters
    var lpDeviceInfo = ref.alloc(NetDeviceInfoEx);
    var error = ref.alloc('int');
    
    var v = DVR.CLIENT_LoginEx2('192.168.1.100', 37777, 'admin', 'dh123456', EM_LOGIN_SPAC_CAP_TYPE.EM_LOGIN_SPEC_CAP_TCP.value, null, lpDeviceInfo, error);
    console.log(v); // it will return device ID by DLL defined
    console.log(lpDeviceInfo.deref()); // it will return NetDeviceInfoEx struct data