[TOC]
# Node.js插件(addons)
Node.js 插件是用 C++ 编写的动态链接共享对象,可以使用`require()`函数加载到 Node.js 中,且像普通的 Node.js 模块一样被使用。 它们主要用于为运行在 Node.js 中的 JavaScript 与 C/C++ 库之间的沟通提供接口。
Node.js 在硬件、IoT 领域开始流行就是因为能和 C/C++ 代码合体使用。
官方文档在哪里?V8引擎的头文件代码在此——[V8引擎头文件](https://v8.whyun.com/)
# 安装环境
## Mac 下
安装 Xcode,至少打开过一次,接受它的条款。否则可能会失败!
## Windows 下
如果你通过 scoop 已经安装了 `python`,或者已经安装了 visual studio 2017 那么,你不需要再 `windows-build-tools` 了:
```
npm i -g windows-build-tools
```
[windows-build-tools](https://github.com/felixrieseberg/windows-build-tools) 主要是安装以下两个:
* using Python version 3.8.5 found at "C:\Users\ChandlerVer5\scoop\apps\python\current\python.exe"
* using VS2017 (15.8.28010.2036) found at: find VS "C:\Program Files (x86)\Microsoft Visual Studio\2017\BuildTools"
> MSbuild是什么,参考msdn:[https://msdn.microsoft.com/zh-cn/library/0k6kkbsd.aspx](https://msdn.microsoft.com/zh-cn/library/0k6kkbsd.aspx)
## Linux
1. python
2. [make](https://www.gnu.org/software/make/)
3. 适当的 C/C + + 编译器工具链,比如 [GCC](https://gcc.gnu.org/)
## node-gyp
GYP(全称是 Generate Your Project, 是一个用 Python 实现的[工具](https://en.wikipedia.org/wiki/GYP_(software)),所以需要环境里有对应的python)是比 Makefile 更高层次的一种 C/C++(其他语言未知)代码编译工具。
使用 [node-gyp](https://github.com/nodejs/node-gyp) 可以方便开发者直接源码分发(`binding.gyp`),用户在安装的时再直接编译,方便跨平台。
```
npm i -g node-gyp
```
安装过程,如果卡住,不要手动结束!(否则如果编译期间出``现莫名错误,需要重新安装,参考下面*故障问题*)
> [GYP介绍](https://blog.csdn.net/xiaoshixiu/article/details/94359960)
## node-gyp 故障问题
1. 首先清除根目录下的`.node-gyp`
2. 卸载 node-gyp 模块
```
npm uninstall node-gyp -g
```
# 开发方式
nodejs 与 C/C++ 交互目前主流的方式有两种:[N-API](https://github.com/nodejs/node-addon-api) 和 [node-ffi](https://github.com/node-ffi/node-ffi) 。
## node-ffi(不推荐这种方式)
[node-ffi](https://github.com/node-ffi/node-ffi) 是一个用于使用纯 JavaScript 加载和调用动态库的 Node.js 插件。它可以用来在不编写任何 C++ 代码的情况下创建与本地 DLL 库的绑定。同时它负责处理跨 JavaScript 和 C 的类型转换。
~~~
1. 性能有折损
2. 类似其他语言的 FFI 调试,此方法近似黑盒调用,差错比较困难。
~~~
## N-API
http://nodejs.cn/s/eGYeBR
[N-API](https://nodejs.cn/api/n-api.html) 类似于 Native Abstractions for Node([NAN](https://github.com/nodejs/nan)) 项目,但是是由 nodejs 官方维护,从此就不需要安装外部的依赖来导入到头文件。并且提供了可靠的抽象层
它暴露了`node_api.h`头文件,抽象了 nodejs 和包的内部实现,每次 Nodejs 更新,N-API 就会同步进行优化保证 ABI 的可靠性。
* [这里](https://nodejs.cn/api/n-api.html)是 N-API 的所有接口文档,
* [这里](https://nodejs.org/zh-cn/docs/guides/abi-stability/#n-api)是官方对 N-API 的 ABI 稳定性的描述
N-API 同时适合于 C 和 C++,但是 C++ 的 API 使用起来更加的简单,于是,node-addon-api 就应运而生。
### 使用 node-addon-api
它有自己的头文件`napi.h`,包含了 N-API 的所有对 C++ 的封装,并且跟 N-API 一样是由[官方维护](https://github.com/nodejs/node-addon-api)。因为它的使用相较于其他更加的简单,所以在进行 C++ API 封装的时候优先选择该方法。
> [前端使用 node-gyp 构建 Native Addon](https://www.cnblogs.com/BigJ/p/Cxx2JS.html)
# 示例
当然官方提供了很多 C++ 插件的示例:https://github.com/nodejs/node-addon-examples ,所示的例子是直接使用 Node.js 和 V8 的 API 来实现插件。
先创建 `addon.cc
` 文件,这个代码比较好理解:
```
// addon.cc
#include <node.h>
// 构建与 Node 应用程序的某种接口所必需的
namespace demo
{
using v8::Exception;
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::String;
using v8::Value;
// 这是 "add" 方法的实现
// 输入参数使用 const FunctionCallbackInfo<Value>& args 结构传入。
void Add(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
// 检查传入的参数的个数
if (args.Length() < 2) {
// 抛出一个错误并传回到 JavaScript。
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate,
"参数的数量错误").ToLocalChecked()));
return;
}
// 检查参数的类型
if (!args[0]->IsNumber() || !args[1]->IsNumber()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate,
"参数错误").ToLocalChecked()));
return;
}
// 执行操作
double value =
args[0].As<Number>()->Value() + args[1].As<Number>()->Value();
Local<Number> num = Number::New(isolate, value);
// 设置返回值 (使用传入的 FunctionCallbackInfo<Value>&)。
args.GetReturnValue().Set(num);
}
void Init(Local<Object> exports) {
NODE_SET_METHOD(exports, "add", Add);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Init)
// 初始化插件,NODE_GYP_MODULE_NAME 即 binding.gyp 中 target_name
} // namespace demo
```
`NODE_SET_METHOD` 接受三个参数:
1. exports 对象
2. 函数将导出的名称
3. 函数本身
通过在顶部添加 `using v8;`,可以从类型中省略所有`v8::`命名空间说明符,这可能导致名称冲突,因此请小心使用它们。
在 c++ 世界中,我们可以自由地使用 c++ 内置的类型,但是当我们处理 JavaScript 对象以及与 JavaScript 代码的互操作性时,我们必须将 c++ 类型转换为 JavaScript 上下文可以理解的类型。这些是在`v8::`命名空间中公开的类型,比`如v8::String`或`v8::Object`。
所有的 Node.js 插件都必须按照下面的模式导出一个初始化函数:
```
void Initialize(v8::Local exports);
NODE_MODULE(module_name, Initialize)
```
创建 `binding.gyp` 文件 内容一看就懂
```
{
"targets": [
{
"target_name": "addon", "sources": [ "addon.cc" ]
}
]
}
```
然后依次执行两个命令
```
$ node-gyp configure build
```
编译完成后在 `build/Release/` 下面会有一个 `addon.node` 文件。再创建 `test.js`文件:
```js
// test.js
const addon = require('./build/Release/addon');
console.log('This should be eight:', addon.add(3, 5));
```
执行 `$ node test.js` 打印出了 `This should be eight:8`。
# 参考
> https://nodeaddons.com/
> [从暴力到 NAN 再到 NAPI——Node.js 原生模块开发方式变迁](https://www.jianshu.com/p/68b134e5ece3)
> [nodejs-C++ 插件](http://nodejs.cn/api/addons.html#addons_function_arguments)
> [Node.js C++ 插件学习指南](https://www.cnblogs.com/lovesong/p/11217244.html)
> [Node.js介绍4-Addon](https://www.jianshu.com/p/e14815ff52ee)