视频1 视频21 视频41 视频61 视频文章1 视频文章21 视频文章41 视频文章61 推荐1 推荐3 推荐5 推荐7 推荐9 推荐11 推荐13 推荐15 推荐17 推荐19 推荐21 推荐23 推荐25 推荐27 推荐29 推荐31 推荐33 推荐35 推荐37 推荐39 推荐41 推荐43 推荐45 推荐47 推荐49 关键词1 关键词101 关键词201 关键词301 关键词401 关键词501 关键词601 关键词701 关键词801 关键词901 关键词1001 关键词1101 关键词1201 关键词1301 关键词1401 关键词1501 关键词1601 关键词1701 关键词1801 关键词1901 视频扩展1 视频扩展6 视频扩展11 视频扩展16 文章1 文章201 文章401 文章601 文章801 文章1001 资讯1 资讯501 资讯1001 资讯1501 标签1 标签501 标签1001 关键词1 关键词501 关键词1001 关键词1501 专题2001
Nodejs调用Dll模块的方法
2020-11-27 22:07:30 责编:小采
文档


公司项目采用Electron(electronjs.org/ )开发pc应用,会涉及到与底层硬件设备的通信,而sdk封装 基本上都是通过 C++ 动态链接库dll实现的。

有两种方案可供选择:

  • 方案一: 使用node-ffi
  • 方案二: 使用C++编写一个node addon,通过LoadLibrary调用dll
  • 以上两种方案都可以解决dll调用问题,方案选型要个人对C++ 的掌握程度,如果熟悉C++开发,可以直接选择方案二最方便。如果完全不了解C++,那么只能采用方案一。

    由于笔主不太懂C++,最终选择第一种方案。

    二、什么是node-ffi?

    ( www.npmjs.com/package/ffi…

    node-ffi是使用纯JavaScript加载和调用动态库的node addon,它可以用来在不写任何C++代码的情况下调用动态链接库的API 接口。

    ffi究竟干了什么?其实它本质上还是一个编译后的Node addon,node_modules/ffi/build/Release/ffi_bindings.node, ffi_bindings.node就是一个addon ffi充当了nodejs和dll之间的桥梁。

    下面是一个简单的加载dll的demo实例:

    var ffi = require('ffi');
    var libpath = path.join(_dirname, '/test.dll');
    var testLib = ffi.Library(libpath, {
     'start': ['bool', ['bool']]
    });
    testLib.start(true); // true

    三、安装node-ffi

    npm install ffi

    如果本地没有安装编译node addon的环境会报错,如下图所示

    无论是使用ffi,还是直接写node addon,都缺少不了编译node Addon这个步骤,要编译node addon,有两种方法:

    1、node-gyp( www.npmjs.com/package/nod … )。

    npm install node-gyp

    具体安装参考:github.com/nodejs/node…

    总结来说需要以下四点:

    python 2.7-3.0版本之间 (推荐装v2.7,v3.x.x是不支持的)

    NET Framework 4.5.1

    Visual C++编译工具 (在windows中是不需要安装VS,如果自己安装例如VS2015,导致编译报错error MSB4132: The tools version "2.0" is unrecognized. Available tools versions are "4.0".这个问题,说明没有装好编译器,又或者编译器没有被正确地识别, node-gyp的文档建议使用npm config set msvs_version 2015, 但是有些机器即使这样设置了也无效,需要手动设置msvs_version, 应该这样写: node-gyp rebuild --msvs_version=2015。如果因为安装了VS2015导致无法正常编译,可直接恢复到安装VS之前的还原点)
    环境变量配置。(注:python安装位置需要添加到环境变量)

    2、electron-rebuild( www.npmjs.com/package/ele… )

    如果采用electron开发应用程序,electron同样也支持node原生模块,但由于和官方的node 相比使用了不同的 V8 引擎,如果你想编译原生模块,则需要手动设置electron的headers的位置。

    electron-rebuild为多个版本的node和electron提供了一种简单发布预编译二进制原生模块的方法。 它可以重建electron模块,识别当前electron版本,帮你自动完成了下载 headers、编译原生模块等步骤。 一个下载 electron-rebuild 并重新编译的例子:

    npm install --save-dev electron-rebuild
    # 每次运行"npm install"时,也运行这条命令
    ./node_modules/.bin/electron-rebuild
    # 在windows下如果上述命令遇到了问题,尝试这个:
    .\node_modules\.bin\electron-rebuild.cmd

    详情请看 electronjs.org/docs/tutori…

    这里需要注意nodejs版本问题,nodejs平台必须跟dll保持一致,同样是32位或者位,如果两者不一致,会导致调用dll失败。

    成功安装ffi模块之后,就可以开始我们下面的ffi调用dll的实例应用。

    四、应用举例

    在开发需求中,需要调用基于C++编写的TCP数据转发服务的SDK。

    首先我们来看一下dll头文件接口声明的代码如下:

    #ifndef JS_CONNECTION_SDK
    #define JS_CONNECTION_SDK
    #ifdef JS_SDK
    #define C_EXPORT __declspec(dllexport)
    #else
    #define C_EXPORT __declspec(dllimport)
    #endif
    extern "C"
    {
     typedef void(*ReceiveCallback) (int cmd, int seq, const char *data);
     /*设置读取数据回调*/
     C_EXPORT void _cdecl SetReceiveCallback(ReceiveCallback callback);
     /*
     *设置option
     */
     C_EXPORT void _cdecl SetOption(
     const char* appKey, 
     const char* tk,
     int lc, 
     int rm
     );
     /*
     *创建连接
     */
     C_EXPORT bool _cdecl CreateConnection();
     /*发送数据*/
     C_EXPORT bool _cdecl SendData(int cmd, int seq, const char *data, unsigned int len);
     /*释放连接*/
     C_EXPORT void _cdecl ReleaseConnection();
    }
    #endif

    ffi调用dll模块封装,代码如下:

    try {
     const ffi = require('ffi');
     const path = require('path');
     const Buffer = require('buffer').Buffer;
     const libpath = path.join(APP_PATH, '..', '..', '/testSDK.dll');
     
     const sdkLib = ffi.Library(libpath, {
     'CreateConnection': ['bool', []],
     'SendData': ['bool', ['int', 'int', 'string', 'int']],
     'ReleaseConnection': ['void', []],
     'SetOption': ['void', ['string', 'string', 'int', 'int']],
     'SetReceiveCallback': ['void', ['pointer']]
     });
     
     module.exports = {
     createConnection: function(){
     sdkLib.CreateConnection();
     },
     setReceiveCallback(cb) {
     global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
     cb && cb(cmd, seq, data && JSON.parse(data));
     });
     sdkLib.SetReceiveCallback(global.setReceiveCallback);
     },
     sendData: function(cmd, seq, data){
     data = JSON.stringify(data);
     sdkLib.SendData(cmd, seq, data, data.replace(/[^\x00-\xff]/g, '000').length, 0);
     },
     releaseConnection: function(){
     sdkLib.ReleaseConnection();
     },
     setOption: function (option) {
     sdkLib.SetOption(
     option.appKey,
     option.tk,
     option.lc,
     option.rm
     );
     }
     } 
    } catch (error) {
     log.info(error);
    }

    第一步:通过ffi注册dll接口

    const sdkLib = ffi.Library(libpath, {
     'CreateConnection': ['bool', []],
     'SendData': ['bool', ['int', 'int', 'string', 'int']],
     'ReleaseConnection': ['void', []],
     'SetOption': ['void', ['string', 'string', 'int', 'int']],
     'SetReceiveCallback': ['void', ['pointer']]
     }); 

    ffi.Library方法,第一个参数传入dll路径,第二参数JSON对象配置相关接口。

    key对应dll头文件中输出的接口,例如C_EXPORT bool _cdecl CreateConnection();

    value array配置参数类型,array[0]注册接口函数返回值类型,array[1]注册接口函数传入形参类型。

    1、基础参数类型bool, char, short, int, long等。

    2、指针类型,需要引入ref模块,如下:

    var ref = require('ref');
    var intPointer = ref.refType('char');
    var doublePointer = ref.refType('short');
    var charPointer = ref.refType('int');
    var stringPointer = ref.refType('long');
    var boolPointer = ref.refType('bool');

    3、回调函数指针pointer,可以通过ffi.Callback创建,如下:

    global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
     cb && cb(cmd, seq, data && JSON.parse(data));
     });
    sdkLib.SetReceiveCallback(global.setReceiveCallback);

    回调函数参数类型配置与dll接口参数类型配置相同,这里就不多说。

    这里需要注意一点,回调函数可能会被JavaScript垃圾自动回收机制回收,所以我这里是把回调函数挂载到全局对象global上。

    第二步:接口调用

    通过ffi.Library(libpath, {...})注册接口,可以直通过返回的sdkLib对象调用对接的接口。例如:

    var bool = sdkLib.CreateConnection();
    console.log(bool); // true or false;
    var cmd = 0, seq = 0, data = {...};
    var dataStr = JSON.stringify(data);
    // JavaScript中文字符长度在C++中长度计算要*3
    sdkLib.SendData(cmd, seq, data, data.replace(/[^\x00-\xff]/g, '000').length);
    global.setReceiveCallback = ffi.Callback('void', ['int', 'int', 'string'], function(cmd, seq, data){
     cb(cmd, seq, data && JSON.parse(data));
    });
    sdkLib.SetReceiveCallback(global.setReceiveCallback);

    补充:下面看下NodeJS通过ffi调用DLL

    第一步建立一个dll, 提供方法如下

    int WINAPI CAM_Open(char *pIn, char* pOut);

    第二步安装ffi (前提已安装python2.x环境)

    npm install --save ffi

    第三步创建测试文件

    var ffi = require("ffi")
    var DLL = ffi.Library('FaceRecognition.dll', {
     'CAM_Open' : ['int', ['string', 'string']]
    });
    var result = DLL.CAM_Open("", "");
    console.log(result)

    参考资料

    https://github.com/node-ffi/node-ffi

    总结

    以上所述是小编给大家介绍的Nodejs调用Dll模块的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对脚本之家网站的支持!

    下载本文
    显示全文
    专题