FFI 全称 Foreign Function Interface .
主要解决在 Node.js 里用 JS 调用 C/C++ 写的动态库的问题.
https://www.npmjs.com/package/ffi-napi
- 在安装 ffi 之前,请先安装好 node-gyp 相关的依赖,具体请看官方安装说明 https://github.com/nodejs/node-gyp .
- Node.js 12 及以上的版本,请安装 ffi-napi 包,而不是 ffi 包. 原理请看 N-API 介绍: https://xcoder.in/2017/07/01/nodejs-addon-history/
- 请安装 ref-napi 包,而不是 ref 包,参数是指针类型时需要. 其它像 struct, union, array 请找对应的 napi 的包装. https://github.com/node-ffi-napi
先看 FFI官方给的示例:
1 | var ffi = require('ffi-napi'); |
示例是调用 C 的 math 相关的库的 ceil 函数. 在 Mac 或 Linux 下,我们可以通过 man ceil 看函数的 C 签名.
1 | NAME |
可以看到 ceil 函数返回一个 double 值,且需要一个 double 值.
https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial 手册描述 ffi.Library 的函数签名如下:
ffi.Library(libraryFile, { functionSymbol: [ returnType, [ arg1Type, arg2Type, ... ], ... ]);
所以示例代码调用 libm 的 ceil 意思就很明白.
之所以需要 ref 包,是因为在调用指针等 JS 里没有的类型时,需要用它来构建参数的值.
拿 FFI 手册 里的代码来举例
sqlite3_open 等函数的签名请看 https://www.sqlite.org/capi3ref.html#sqlite3_open
1 | var ref = require('ref'); |
函数签名我们可以用 ref.types 来构建,也可以直接写成 string 字符,对于复杂的指针类型,我们其实可以直接用 'pointer' 就行了.但是使用 ref.alloc 创建指针等复杂参数值时,就必须得 ref.types 方式来构建了.
ref.alloc 函数返回的是指针,如果是需要传值,需要调用再调用 deref 方法. 从手册里的下文示例代码可以看出 alloc 返回的是指针类型.
1 | var intPtr = ref.refType('int'); |
异步调用函数示例代码如下:
https://github.com/node-ffi/node-ffi/wiki/Node-FFI-Tutorial#async-library-calls
1 | var libmylibrary = ffi.Library('libmylibrary', { |
开始主题,调用 win32 api . 以 SystemParametersInfo 函数为例.
首先找到 SystemParametersInfo 的函数原型:
https://github.com/tpn/winsdk-10/blob/master/Include/10.0.10240.0/um/WinUser.h
在上文件搜索,没有 SystemParametersInfo 函数,只有 SystemParametersInfo 常量,根据是否为 UNICODE 模式,决定是调用 SystemParametersInfoA(ANSI) 还是 SystemParametersInfoW (WideChar) .因为我们在 Node.js 里调用没有引入头文件编译,所以我们任意选择一个即可,本文选择用 SystemParametersInfoA 来做示例.
SystemParametersInfoA 函数的文档请看: https://docs.microsoft.com/zh-cn/windows/win32/api/winuser/nf-winuser-systemparametersinfoa
1 | BOOL SystemParametersInfoA( |
1,2,4 参数为数字类型, 3 参数为指针.
示例是调用此方法检测系统是否进入屏保状态, 我们定位到 SPI_GETSCREENSAVERRUNNING 关键的那一行,告诉我们参数3需要为一个 bool 的指针,如果检测屏保已经启动,会把指针的值设为 true .
1 | Determines whether a screen saver is currently running on the window station of the calling process. The pvParam parameter must point to a BOOL variable that receives TRUE if a screen saver is currently running, or FALSE otherwise. Note that only the interactive window station, WinSta0, can have a screen saver running. |
uiParam,fWinIni 看文档的说明,在调用 SPI_GETSCREENSAVERRUNNING 时,我们传 0 即可.
所以最终的 js 调用代码如下.
1 | const ffi = require('ffi-napi') |
使用 ffi 调用 win32 api 示例即完成了,如需调用其它函数,只需要找到相关的头文件和文档说明按照上代码的方式编写即可.