Nodejs调用Dll模块的方法


Posted in NodeJs onSeptember 17, 2018

公司项目采用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位或者64位,如果两者不一致,会导致调用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模块的方法,希望对大家有所帮助,如果大家有任何疑问请给我留言,小编会及时回复大家的。在此也非常感谢大家对三水点靠木网站的支持!

NodeJs 相关文章推荐
nodejs读取memcache示例分享
Jan 02 NodeJs
Nodejs全栈框架StrongLoop推荐
Nov 09 NodeJs
轻松创建nodejs服务器(10):处理POST请求
Dec 18 NodeJs
Nodejs如何复制文件
Mar 09 NodeJs
Nodejs进阶:基于express+multer的文件上传实例
Nov 21 NodeJs
nodejs学习笔记之路由
Mar 27 NodeJs
详解Nodejs 通过 fs.createWriteStream 保存文件
Oct 10 NodeJs
浅谈NodeJs之数据库异常处理
Oct 25 NodeJs
nodejs使用http模块发送get与post请求的方法示例
Jan 08 NodeJs
Nodejs模块载入运行原理
Feb 23 NodeJs
nodejs 如何手动实现服务器
Aug 20 NodeJs
Nodejs Express 通过log4js写日志到Logstash(ELK)
Aug 30 NodeJs
nodejs中express入门和基础知识点学习
Sep 13 #NodeJs
NodeJS 实现多语言的示例代码
Sep 11 #NodeJs
nodejs高大上的部署方式(PM2)
Sep 11 #NodeJs
Nodejs使用Mongodb存储与提供后端CRD服务详解
Sep 04 #NodeJs
Nodejs Express 通过log4js写日志到Logstash(ELK)
Aug 30 #NodeJs
NodeJS如何实现同步的方法示例
Aug 24 #NodeJs
Nodejs中的JWT和Session的使用
Aug 21 #NodeJs
You might like
php实现的百度搜索某地天气的小偷代码
2014/04/23 PHP
php调用nginx的mod_zip模块打包ZIP文件
2014/06/11 PHP
PHP使用GIFEncoder类生成gif动态滚动字幕
2014/07/01 PHP
php数组指针操作详解
2017/02/14 PHP
解决laravel id非自增 模型取回为0 的问题
2019/10/11 PHP
HTML中的setCapture和releaseCapture使用介绍
2012/03/21 Javascript
jquery-syntax动态语法着色示例代码
2014/05/14 Javascript
JavaScript利用正则表达式去除日期中的“-”
2014/07/01 Javascript
点击A元素触发B元素的事件在IE8下会识别成A元素
2014/09/04 Javascript
javascript实现依次输入input自动定焦
2014/12/23 Javascript
Javascript监视变量变化的方法
2015/06/09 Javascript
JavaScript检查子字符串是否在字符串中的方法
2016/02/03 Javascript
浅谈Vuex的状态管理(全家桶)
2017/11/04 Javascript
VUE 使用中踩过的坑
2018/02/08 Javascript
JavaScript设计模式之代理模式实例分析
2019/01/16 Javascript
JS求1到任意数之间的所有质数的方法详解
2019/05/20 Javascript
微信小程序template模板与component组件的区别和使用详解
2019/05/22 Javascript
vue router 跳转时打开新页面的示例方法
2019/07/28 Javascript
浅谈vue 多个变量同时赋相同值互相影响
2020/08/05 Javascript
python图片验证码生成代码
2016/07/02 Python
python中强大的format函数实例详解
2018/12/05 Python
Python学习笔记之图片人脸检测识别实例教程
2019/03/06 Python
Python中如何导入类示例详解
2019/04/17 Python
python二维码操作:对QRCode和MyQR入门详解
2019/06/24 Python
简单了解python filter、map、reduce的区别
2020/01/14 Python
PyCharm2020最新激活码+激活码补丁(亲测最新版PyCharm2020.2激活成功)
2020/11/25 Python
Python 虚拟环境工作原理解析
2020/12/24 Python
python中altair可视化库实例用法
2021/01/26 Python
HTML5 3D旋转相册的实现示例
2019/12/03 HTML / CSS
欧洲顶级的童装奢侈品购物网站:Bambini Fashion(面向全球)
2018/04/24 全球购物
高中生的自我鉴定范文
2014/01/24 职场文书
销售主管竞聘书
2014/03/31 职场文书
计算机专业自荐信
2014/05/24 职场文书
小学运动会班级口号
2014/06/09 职场文书
学校艾滋病宣传活动总结
2015/05/09 职场文书
JS如何实现基于websocket的多端桥接平台
2021/05/14 Javascript