[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)