Nodejs如何调用Dll模块

2023-05-16

  • 苏格团队
  • 作者:Tomey

一、为什么需要用node.js调用dll?

公司项目采用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);
复制代码

文章到此结束,写这篇文章目标主要是记录自己通过node调用dll从无到有以的过程以及采坑记录,文章有误的地方,欢迎各位大佬指正~

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Nodejs如何调用Dll模块 的相关文章

  • 创新AI技术引领手机芯片发展方向,麒麟970荣获中国好设计金奖

    11月24日 xff0c 2017年中国好设计颁奖仪式在深圳正式召开 xff0c 奖项一共设置为金奖 银奖 创意奖 入围奖四个级别 而麒麟970作为人工智能领域的代表性产品 xff0c 也是唯一的芯片产品 xff0c 在今年的85个项目中脱
  • 史上最全阿里 Java 面试题总结

    以下为大家整理了阿里巴巴史上最全的 Java 面试题 xff0c 涉及大量 Java 面试知识点和相关试题 JAVA基础 JAVA中的几种基本数据类型是什么 xff0c 各自占用多少字节 String类能被继承吗 xff0c 为什么 Str
  • 图象传输协议——PCoIP

    PCoIP 是一种高性能显示协议 专为交付虚拟桌面而构建 无论最终用户具有什么任务或处于何位置 均可为其提供内容极为丰富的最佳桌面体验 借助 PCoIP 整个计算体验先经过在数据中心进行 压缩 加密和编码 然后再通过标准 IP 网络传输到启
  • VB6 对象库未注册问题

    以下是个人使用VB6出现对象库未注册问题的解决方法 已成功 xff01 一 注册ocx文件 mscomctl ocx文件放进路径C Windows System32 xff08 64是此路径 xff0c 由于本人是64位系统32位未测试 x
  • 源码阅读技巧篇

    转载请注明原创出处 xff0c 谢谢 xff01 说在前面 本人水平有限 xff0c 下面的一些都是本人的思考与理解 xff0c 如果有那里不对 xff0c 希望各位大佬积极指出 xff0c 欢迎在留言区进行评论交流 探讨 主题 为什么要读
  • 黑箱方法-神经网络①

    人工神经网络 人工神经网络的概念 人工神经网络 xff08 Artificial Neural Networks xff0c ANN xff09 是对一组输入信号和一组输出信号之间的关系进行建模 xff0c 使用的模型来源于人类大脑对来自感
  • 飞行前的准备工作

    1 飞控固件 Mission Planner 里的版本 xff0c 好像没有offboard和一些参数的设置 Mission Planner中固件下载 3 3 3 3 4 6 Qground Control中的固件QGC中的固件中有offb
  • make menuconfig 无法启动处理方法

    ake menuconfig Unable to find the ncurses libraries required header files 问题 xff1a lzz 64 lzz virtual machine linux 2 6
  • Ubuntu下自动输入sudo密码

    sudo 自动输入密码 echo 34 password 34 sudo S netstat tlnp S参数 The S stdin option causes sudo to read the password from the sta
  • ssh 或 putty 连接linux报错解决方法

    由于当天多次输入错误密码 xff0c ssh和putty就连接不上了 xff0c 纠结了很久解决问题 ssh连接提示错误 xff1a server unexpectedly closed network connection putty 连
  • Postman 安装及使用入门教程

    安装 本文只是基于 Chrome 浏览器的扩展插件来进行的安装 xff0c 并非单独应用程序 首先 xff0c 你要台电脑 xff0c 其次 xff0c 安装有 Chrome 浏览器 xff0c 那你接着往下看吧 1 官网安装 xff08
  • k8s通过service访问pod(五)--技术流ken

    service 每个 Pod 都有自己的 IP 地址 当 controller 用新 Pod 替代发生故障的 Pod 时 xff0c 新 Pod 会分配到新的 IP 地址 这样就产生了一个问题 xff1a 如果一组 Pod 对外提供服务 x
  • 计算机图形学在GIS中的应用,GIS在交通中的应用与发展-

    xff27 xff29 xff33 在交通中的应用与发展 摘 要 xff1a 地理信息技术的日臻成熟为 xff27 xff29 xff33 在交通领域内的广泛应用创造了一定基础 本文总结了 xff27 xff29 xff33 技术的特点 x
  • 查询MYSQl数据表中的最后一条记录

    mysql select from table order by id DESC limit 1 oracle select from emp where id in select max id from emp 实例 xff1a mysq
  • Windows 10 替换 cmd 的命令行工具

    最近找 Windows 10 的命令行工具 xff0c 发现了 Windows 自带的 PowerShell xff0c 确实功能强大 推荐 查找方法 xff1a 搜索 xff0c PowserShell 打开就能用 https www z
  • 压控恒流源电路

    http bbs 21ic com forum php mod 61 viewthread amp tid 61 1634988 amp highlight 61 4 20ma 最简单简陋的电流输出电路 xff0c 是用 三级管 43 放大
  • OFFBOARD

    Pixhawk的offboard模式 xff0c 是指我们不用遥控器操控飞机 xff0c 也不用地面站给它设定plan 直接用飞机上的板载计算机来与Pixhawk进行通信 xff0c 控制飞机运动 准备工作 xff1a 首先要有一个板载计算
  • 思考: 从曲线中提取出近似直线的一段

    这个问题也是别人问我的 我思考了一些时间 希望抛砖引玉 得到更好的方法 问题是这样的 有一些离散的点 在坐标系中把它们拟合成一条曲线 其中有一段看上去很像是直线 现在要求出这段 34 直线 34 的起始坐标和结束坐标 并把这条线的方程求出来
  • 课程第一天内容《基础交换 一 》

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 项目流程介绍 xff1a 前期 中期 后期 xff1b 项目任务分解 xff1a 工具 甘特图 xff1b 任务 时间 负责人 xff1b 网络设备介绍 xff1a 交换机
  • hewlett-packard 设置 HP启动设置

    开机按 F8进入高级选项安全模式 F9进入启动顺序选择项 F10进入双系统选择项 ESC进入启动选项键选择界面 Delete键进入BIOS please select boot device 请选择启动装置 UEFI boot source

随机推荐