NodeJS和NW通过ffi调用dll/so动态库

0x01:使用的 npm 包

首先要安装 node-gyp, 用来重新编译依赖包。

1
npm instal -g node-gyp

然后主要用到下面三个包:

  • node-ffi – 使用Javascript调用动态库

  • ref – 用来定义数据类型,提供指针功能

  • ref-array – 用Buffer来实现C语言中的 array 数据类型

1
2
npm install ffi //这个命令会同时安装上 refref-struct
npm instal ref-array

0x02: 测试NODEJS调用

要使用动态库中的函数,首先要对动态库里的函数进行声明。
比如在 Test.dll 库中,有两个函数如下:

1
2
3
void init(string name, int port);
string hello(int times);

js中进行声明的方法如下:

1
2
3
4
5
6
7
8
var ffi = require('ffi');
var Test = ffi.Library('Test.dll',{
'init': ['void',['string','int']],
'hello': ['string', ['int']]
});
#规则就是
'函数名':['返回值数据类型':['参数数据类型',...,'参数数据类型']]

声明完成后,就可以进行调用了

1
2
Test.init('COM1', 9300);
Test.hello(5);

这里用简单的数据类型,来讲解调用动态库的大致流程。剩下比较复杂的地方在于如何模拟像 指针结构体数组 等比较复杂的数据类型。

0x03: 结构体、指针、数组的转化

1. 结构体

结构体需要用到’ref-struct’这个包。假设有以下结构体:

1
2
3
4
5
6
typedef struct {
byte UID[16]; /*餐盘标签 UID,16 进制*/
byte UType[6]; /*餐盘类型,10 进制*/
int ProdNo; /*菜品编码,10 进制*/
int ProdPrice; /*菜品价格,价格以分为单位,10 进制*/
} DishInfo;

int类型的好办,可以直接使用 ref包里含有的类型 ref.types.int
UIDUType是两个bype类型的数组,需要使用ref-array进行模拟。

1
2
3
4
5
6
7
8
9
var refStruct = require('ref-struct');
var refArray = require('ref-array');
var DishInfo = refStruct({
'UID': refArray('byte', 16),
'UType': refArray('byte', 6),
'ProdNo': ref.types.int,
'ProdPrice': ref.types.int
});

2. 指针和引用

假设动态库中有函数如下, 第二个参数为结构体指针, 第三个参数是一个int 引用。

1
int Read(int port, DishInfo * pInfo, int &Count);

在声明函数的时候,就需要指明指针和引用的数据类型。示例如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
var ffi = require('ffi');
var ref = require('ref');
var refStruct = require('ref-struct');
var refArray = require('ref-array');
var DishInfo = refStruct({
'UID': refArray('byte', 16),
'UType': refArray('byte', 6),
'ProdNo': ref.types.int,
'ProdPrice': ref.types.int
});
//数据类型
var intPointer = ref.refType('int');
var DishInfoArrType = refArray(DishInfo); //定义了DishInfo数组类型
var Test = ffi.Library('Test.dll',{
'init': ['void',['string','int']],
'hello': ['string', ['int']],
'Read': ['int', ['int', DishInfoArrType, intPointer]]
});
//实例化
var count = ref.alloc('int');
var DishInfoArr = DishInfoArrType(3);
Test.Read(11, DishInfoArray, count);
//使用deref()获取引用的实际值
var actualCount = count.deref();

0x04: NW 适配

使用NodeJS直接调用没问题后,就可以使用 node-gyp 编译适配 NW 的包了, 这里只说明window环境下的使用方法。

1. 搭建编译环境

  1. 安装 Visual Studio 2015

    💡 [Windows Vista / 7 only] 需要安装 .NET Framework 4.5.1

  1. 安装 python 2.7 (不要装3.x.x,不支持),装完后运行

    1
    npm config set python python2.7
  2. 设置visualstudio版本

    1
    npm config set msvs_version 2015

2. 修改 win_delay_load.cc

打开 Github - nw.js repository, 然后切换自己使用的nw 版本分支。
nwjs选择分支

我这里选择的是 nw14, 然后找到 tools/win_delay_load_hook.cc, 下载替换掉 %APPDATA%\npm\node_modules\node-gyp\src\win_delay_load_hook.cc

3. node-gyp 重编译 ffi 和 ref

1
2
3
4
5
6
7
8
9
# --target 输入nw 版本号,这里实用的是 v0.14.3, arch为 ia32 或者 x64
cd node_modules/ffi
node-gyp configure --target=0.14.3 --arch=ia32
node-gyp build
cd node_modules/ref
node-gyp configure --target=0.14.3 --arch=ia32
node-gyp build

0x05: 参考资料

  1. 通过ffi在node.js中调用动态链接库(.so/.dll文件)
  2. Use Native Node Modules