七,来点高级的

写hello world,对于新手来说很有必要,但是对于希望能玩点更高级的来说,感觉就差那么点意思了,所以我们来写一个海康摄像头抓拍的插件吧(其实眼尖的人,看到我之前建的项目名称,大致能想到了)

这篇文章,会学到这么几个知识点:

1,设置第三方头文件、静态库文件

2,传参,javascript和c++代码之间如何相互传参

3,返回值

4,异常

 

按官方文档,异常有两种,一种是启用C++异常,一种是禁用C++异常。node-addon-api定义了一个 Napi:Error 类,用于处理异常。

  • 启用C++异常

Napi:Error将从 std::exception 继承,这个时候 可以在代码里try-catch,举例如下:

// 抛出异常方式
throw Napi::Error::New(env, "Example exception");

// 捕获异常方式
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Value result;
try {
    result = jsFunctionThatThrows({ arg1, arg2 });
} catch (const Error& e) {
    cerr << "Caught JavaScript exception: " + e.what();
}
  • 不启用C++异常

那么Napi:Error不会继承 std::exception,用法如下:

// 抛出异常(抛出后,可以在js代码里捕获)
Napi::Env env = ...
Napi::Error::New(env, "Example exception").ThrowAsJavaScriptException();

// 异常的传播
Napi::Env env = ...
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Value result = jsFunctionThatThrows({ arg1, arg2 });
if (env.IsExceptionPending()) {
    Error e = env.GetAndClearPendingException();
    return e.Value();
}

// 处理异常,但不传播
Napi::Env env = ...
Napi::Function jsFunctionThatThrows = someObj.As<Napi::Function>();
Napi::Value result = jsFunctionThatThrows({ arg1, arg2 });
if (env.IsExceptionPending()) {
    Napi::Error e = env.GetAndClearPendingException();
    cerr << "Caught JavaScript exception: " + e.Message();
}

 

node-gyp rebuild --debug --arch=$arch

注意以上命令,由于一些第三方c++ DLL,比如海康SDK,分x86和x64版本,所以如果大家需要编译为32位版本,除了引入海康32位版本的SDK以外,还需要指定gyp编译为32位版本,所以上述命令中的 "$arch"需要指定为 “ia32”。

但是如果大家安装的nodejs是64位的,而编译的目标平台指定的是32位的,那么在运行过程中,会报一个无效的32位应用程序的错误。

我这里打算编译64位的版本,所以我下载了64位的海康SDK,先看一下我的目录:

我把海康SDK相关文件,放到了红框所示的目录下,bin文件夹里存放的,是dll文件,include是头文件,lib是静态库文件。

然后配置binding.gyp,设置头文件和库文件:

{
    "targets": [
        {
        # 链接目标
        "target_name": "nodecamera",
        "cflags!": [
                "-fno-exceptions"
            ],
            "cflags_cc!": [
                "-fno-exceptions"
            ],
            "sources": [
                "src/index.cpp",
                "src/KSHikCameraWrapper.cpp"
            ],
        # 头文件搜索路径
        "include_dirs": [
                "<!@(node -p \"require('node-addon-api').include\")",
                "sdk/hik/include"
            ],
        # 预编译宏
        'defines': [ 
        # 启用C++异常
        'NAPI_CPP_EXCEPTIONS'
        # 禁用C++异常 NAPI_DISABLE_CPP_EXCEPTIONS
            ],
            # 微软编译器附加设定
            'msvs_settings': {
                'VCCLCompilerTool': {
                    'ExceptionHandling': 1,
                    # 如果启用了C++异常,对于微软编译器来说,需要设定异常模型,下面两种方式效果是一样的
                    # 'AdditionalOptions': ['/EHsc']
                }
            },
        # 静态库
        'libraries': [
                "../sdk/hik/lib/HCNetSDK.lib"
            ]
        },

        {
            # 拷贝文件
            "target_name": "action_after_build",
            "type": "none",
            "dependencies": [ "nodecamera" ],
            "copies": [{
                    "destination": "<(module_root_dir)/build/debug",
                    "files": [
                        "<(module_root_dir)/sdk/hik/bin/**.*"
                        ]
                    }]
        }
    ]
}

注意,我的文章是windows下的nodejs c++插件开发,所以我并没有在gyp里设置一些条件来根据操作系统编译。这里有一个小问题,就是“拷贝文件”这块,我发现这样配置,并不能把包括文件夹及其子项的内容都拷贝走,哪位大神如果知道怎么搞的话,可以留言告诉我一下,先谢谢啦。

那么我们开始吧^_^

有过海康SDK开发经验的同学都知道,SDK需要全局一次性的初始化以及最后的清理工作,所以我打算将这部分代码放到全局函数中来实现:

#include <napi.h>
#include <string>
#include <sstream>
#include "KSHikCameraWrapper.h"

// 初始化海康SDK
Napi::Value InitHikSDK(const Napi::CallbackInfo &info)
{
    try
    {

        if (!CKSHikCameraWrapper::g_bSDKInit)
        {
            if (NET_DVR_Init())
            {
                CKSHikCameraWrapper::g_bSDKInit = true;
            }
            else
            {
                DWORD dwErr = NET_DVR_GetLastError();
                std::ostringstream os;
                os << "return code: " << dwErr;
                Napi::Error::New(info.Env(), os.str()).ThrowAsJavaScriptException();
            }
        }

        return Napi::Boolean::New(info.Env(), true);
    }
    catch (const Napi::Error &e)
    {
        std::ostringstream os;
        os << "init sdk failed, " << e.what();
        Napi::TypeError::New(info.Env(), os.str()).ThrowAsJavaScriptException();
    }

    return Napi::Boolean::New(info.Env(), false);
}

// 注销海康SDK
void CleanupHikSDK(const Napi::CallbackInfo &info)
{
    try
    {
        if (CKSHikCameraWrapper::g_bSDKInit)
        {
            if (NET_DVR_Cleanup())
            {
                CKSHikCameraWrapper::g_bSDKInit = false;
            }
        }
    }
    catch (const Napi::Error &e)
    {
        std::ostringstream os;
        os << "cleanup sdk failed, " << e.what();
        Napi::TypeError::New(info.Env(), os.str()).ThrowAsJavaScriptException();
    }
}

Napi::Object Init(Napi::Env env, Napi::Object exports)
{
    exports.Set(
        Napi::String::New(env, "initHikSDK"),
        Napi::Function::New(env, InitHikSDK));

    exports.Set(
        Napi::String::New(env, "cleanupHikSDK"),
        Napi::Function::New(env, CleanupHikSDK));

    return CKSHikCameraWrapper::Init(env, exports);
}

// 最后调用这个宏,把上面定义的C++函数暴露给JAVASCRIPT
NODE_API_MODULE(NODE_GYP_MODULE_NAME, Init)

 

然后我们编写摄像头的打开、关闭和抓拍,首先是头文件定义:

#pragma once

#include <napi.h>
#include <windows.h>
#include <string>
#include <sstream>
#include "HCNetSDK.h"

class CKSHikCameraWrapper : public Napi::ObjectWrap<CKSHikCameraWrapper>
{
public:
    static Napi::Object Init(Napi::Env env, Napi::Object exports);
    static Napi::Value CreateNewItem(const Napi::CallbackInfo &info);
    static Napi::FunctionReference constructor;

    CKSHikCameraWrapper(const Napi::CallbackInfo &info);
    ~CKSHikCameraWrapper();

public:
    // 打开摄像头
    // 需要传入的参数
    // 摄像头IP,摄像头端口,账号,密码,通道号
    Napi::Value Open(const Napi::CallbackInfo &info);

    // 关闭摄像头
    Napi::Value Close(const Napi::CallbackInfo &info);

    // 抓拍
    // 需要传入的参数
    // 抓拍文件保存路径
    Napi::Value Capture(const Napi::CallbackInfo &info);

public:
    static BOOL g_bSDKInit;

private:
    LONG m_nUserId;
};

在这里,我使用了 Napi::ObjectWrap 包装类,使用这个类,则在 javascript 代码里,需要用 new 的方式初始化一个对象,比较符合c++程序员的习惯,然后在javascript垃圾回收的时候,会自动调用这个类的析构函数。

接下来是实现代码:

#include "KSHikCameraWrapper.h"

BOOL CKSHikCameraWrapper::g_bSDKInit = FALSE;

Napi::Object CKSHikCameraWrapper::Init(Napi::Env env, Napi::Object exports)
{
    Napi::Function func = DefineClass(
        env, "KSHikCameraWrapper",
        {InstanceMethod("open", &CKSHikCameraWrapper::Open),
         InstanceMethod("close", &CKSHikCameraWrapper::Close),
         InstanceMethod("capture", &CKSHikCameraWrapper::Capture),
         StaticMethod<&CKSHikCameraWrapper::CreateNewItem>("CreateNewItem")});

    Napi::FunctionReference *constructor = new Napi::FunctionReference();
    *constructor = Napi::Persistent(func);
    env.SetInstanceData(constructor);
    exports.Set("KSHikCameraWrapper", func);
    return exports;
}

Napi::Value CKSHikCameraWrapper::CreateNewItem(const Napi::CallbackInfo &info)
{
    Napi::FunctionReference *constructor = info.Env().GetInstanceData<Napi::FunctionReference>();
    return constructor->New({Napi::Number::New(info.Env(), 42)});
}

CKSHikCameraWrapper::CKSHikCameraWrapper(const Napi::CallbackInfo &info)
    : Napi::ObjectWrap<CKSHikCameraWrapper>(info)
{
    m_nUserId = -1;
}

CKSHikCameraWrapper::~CKSHikCameraWrapper()
{
}

Napi::Value CKSHikCameraWrapper::Open(const Napi::CallbackInfo &info)
{
    if (!CKSHikCameraWrapper::g_bSDKInit)
    {
        Napi::TypeError::New(info.Env(), "sdk is not inited").ThrowAsJavaScriptException();
        return Napi::Boolean::New(info.Env(), false);
    }

    if (info.Length() != 5)
    {
        Napi::TypeError::New(info.Env(), "less args").ThrowAsJavaScriptException();
        return Napi::Boolean::New(info.Env(), false);
    }

    if (-1 != m_nUserId)
    {
        // 已经登录
        return Napi::Boolean::New(info.Env(), true);
    }

    // IP
    std::string strIPAddr = info[0].As<Napi::String>();
    // 端口
    int nPort = info[1].As<Napi::Number>().Int32Value();
    // 账号
    std::string strAcc = info[2].As<Napi::String>();
    // 密码
    std::string strPwd = info[3].As<Napi::String>();

    NET_DVR_DEVICEINFO_V30 dvrinfo = {0};

    m_nUserId = NET_DVR_Login_V30((char *)strIPAddr.c_str(), nPort,
                                  (char *)strAcc.c_str(), (char *)strPwd.c_str(), &dvrinfo);

    if (-1 == m_nUserId)
    {
        // 登录失败,查看一下错误代码
        DWORD dwErr = NET_DVR_GetLastError();
        std::ostringstream os;
        os << "open camera failed, return code: " << dwErr;
        return Napi::String::New(info.Env(), os.str());
    }

    return Napi::Boolean::New(info.Env(), true);
}

Napi::Value CKSHikCameraWrapper::Close(const Napi::CallbackInfo &info)
{
    if (-1 != m_nUserId)
    {
        if (NET_DVR_Logout_V30(m_nUserId))
        {
            m_nUserId = -1;
            return Napi::Boolean::New(info.Env(), true);
        }
        else
        {
            DWORD dwErr = NET_DVR_GetLastError();
            std::ostringstream os;
            os << "close camera failed, return code: " << dwErr;
            return Napi::String::New(info.Env(), os.str());
        }
    }

    return Napi::Boolean::New(info.Env(), true);
}

Napi::Value CKSHikCameraWrapper::Capture(const Napi::CallbackInfo &info)
{
    if (-1 == m_nUserId)
    {
        return Napi::String::New(info.Env(), "please connect camera first");
    }

    if (info.Length() != 1)
    {
        Napi::TypeError::New(info.Env(), "less args").ThrowAsJavaScriptException();
        return Napi::Boolean::New(info.Env(), false);
    }

    std::string strImageFile = info[0].As<Napi::String>();
    NET_DVR_JPEGPARA struJpegPara = {0};
    struJpegPara.wPicSize = 19;
    struJpegPara.wPicQuality = 2;
    if (!NET_DVR_CaptureJPEGPicture(m_nUserId, 0, &struJpegPara, (char *)strImageFile.c_str()))
    {
        DWORD dwErr = NET_DVR_GetLastError();
        std::ostringstream os;
        os << "capture failed, return code: " << dwErr;
        return Napi::String::New(info.Env(), os.str());
    }
    return Napi::Boolean::New(info.Env(), true);
}

最后我们来写一个 js 调用看看效果:

// const addon = require('./build/Release/nodecamera.node');
const addon = require('bindings')('nodecamera.node');
var init = false;

try {
    init = addon.initHikSDK();
    if (true === init) {
        var hik = new addon.KSHikCameraWrapper();
        var camera = hik.open('192.168.20.121', 8000, 'admin', 'admin123456', 1);
        if (true === camera) {
            var result = hik.capture('d:\tmp\test.jpg');
            if (true === result) {
                console.log('抓拍成功');
            } else {
                console.log(result);
            }
            hik.close();
        } else {
            console.log(camera);
        }

        addon.cleanupHikSDK();
    }
} catch (error) {
    console.log(error);
}

很遗憾,我手头上用的一个海康摄像头,不支持 NET_DVR_CaptureJPEGPicture 函数抓拍:

再看看控制台输出:

最后我们查一下海康SDK:

好吧,兴致勃勃的心情一下不是很爽了,不过不要紧,我们至少证明了nodejs可以通过c++插件的方式实现很多扩展功能。

还有很多问题还没研究,不过这已经是一个好的开始了,不是吗?推荐大家去看看nodejs c++ addon 的git文档来继续学习:nodejs c/c++ addon

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值