
最近在开发中遇到需要在 NodeJS 中调用 C++代码的问题,在此略作总结。
主要方案
在 NodeJS 中,和其他语言编写的代码通信主要有两种方案:
- 使用 AddOn 技术,使用 C++为 NodeJS 编写一个拓展,然后在代码中调用其他语言所编写的源码 or 动态库
- 使用 FFI(Foreign Function Interface)技术,直接在 Node 中引入其他语言所编写的动态链接库
在对这两种方式进行比较后,发现这两种方式各有优劣。
首先,AddOn 技术比较通用,它可以使用 C++代码来拓展 Node 的行为,很多库都是使用这种方式来完成一些比较底层操作(比如和操作系统的一些通信)的。但是它写起来比较麻烦,要编写一个 C++项目,还要按照 NodeJS 的规范 export 相应的函数,而且每次安装的时候都需要进行编译(以适应本地 Node 的版本)。如果只是调用一个 DLL,那就还需要在项目里重新包装一遍 DLL 的接口。
如果使用 FFI 技术,限制就会比较多,首先,它只能调用其他动态库,如果你想使用 C/C++完成更多功能的话,还需要再封装一层 DLL,另外,它只支持_cdecl
调用约定(也就是 DLL 在导出的时候一定要标记用_cdecl
编译命令),不支持_stdcall
或者_fastcall
调用。但是调用起来就会很方便,可以直接在 JS 代码中声明 DLL 的接口就可以了。
综上比较,如果只调用第三方 DLL(而且恰好是_cdecl
导出),使用 FFI 就再合适不过了(虽然性能可能会有一定的损失,而且调试起来会有困难)。
其实,从理论上来讲,FFI 也是基于 AddOn 技术的,只是它可以帮你把在 JS 中定义的接口直接转换成 C 语言的接口,并利用 NodeJS 的 Buffer 内存,将其同载入的 DLL 共享。当然由于 FFI 的这种通用性,也导致了一定的性能损失。
下面就以在 Windows 平台上使用 FFI 为例,简单聊一下如何使用 NodeJS 和 C++编译而成的 DLL 通信吧。
FFI 使用准备
安装 NodeJS
可能你的环境中已经有 NodeJS 了,但是,如果是最新版本,在安装 FFI 的时候会出现各种兼容性的问题(比如编译无法通过,虽然已经有人提供了 patch,但是还没有被 merge 进主分支,为了避免出现 bug,还是暂时不用为妙)。所以可以安装 LTS 版本代替。
另外,还需要注意要调用的 DLL 是 32 位还是 64 位的,Node 的版本需要和 DLL 的版本匹配。因为如果 64 位 Node 调用了 32 位的 DLL,是无法成功装载的,反之亦然。
安装 Windows 的 C++工具链
这里有两种方案:
- 安装 Visual Studio,并安装相应的工具链。如果使用 VS 2019 版本的话,需要安装 C++桌面开发和 Windows SDK 相关的工具(Node v10 现在只支持 v141 版本的 MSVC),这种方式便于后续的调试工作(虽然也很艰难)
- 在安装 Node 之后,使用管理员权限运行 Powershell,并全局安装 windows-build-tools,参考命令
npm install --global --production windows-build-tools
安装 node-gyp
node-gyp 是一个 Node 中基于 gyp 的跨平台的编译工具,用于编译其他库。
在安装的时候,需要使用 VC 的工具链,所以如果没有把工具链放在全局变量中,需要打开 VS 的Developer Powershell
安装,该命令行一般在开始菜单的 Visual Studio 文件夹中。
参考命令:npm install -g node-gyp
安装 FFI 及 REF
下面的步骤依旧需要 VC 工具链,所以可能依旧需要在Developer Powershell
中执行(建议常备该窗口,后面只要涉及到编译安装的命令都需要用到)。
安装 FFI 及相关工具的时候如果没有 VC 工具链,则会直接安装二进制代码,这样可能会出现包的 ABI 版本和 NodeJS 的 ABI 版本不符合的情况(在下面的 Tip