Nodejs 的 C++ 拓展開發(fā)
大廠技術(shù)??高級前端??Node進(jìn)階
點(diǎn)擊上方?程序員成長指北,關(guān)注公眾號
回復(fù)1,加入高級Node交流群
Nodejs 模塊機(jī)制
首先在開始之前先簡單介紹一下 Nodejs 里面的模塊引入機(jī)制。
1. Node.js 核心模塊
例如 fs、net、path 這樣的模塊,代碼在 nodejs 源碼(lib目錄下)中,通過 API 來暴露給開發(fā)者,這些核心模塊都有自己的預(yù)留標(biāo)識,當(dāng) require() 函數(shù)傳入的標(biāo)識和核心模塊相同時(shí),就會返回核心模塊的 API。
const fs = require('fs');
2. 文件模塊
文件模塊則分為兩種方式:
2.1 第三方模塊
這些模塊以 Nodejs 依賴包的形式存在。例如一些常見的 npm 包 axios、webpack等等。
Nodejs require 這一類模塊的話是會去找該模塊項(xiàng)目下面的 package.json 文件,如果 package.json 文件合法,則會解析 main 字段的那個(gè)路徑。
當(dāng) require() 函數(shù)中傳入一個(gè)第三方模塊,例如 axios,那么 Nodejs 對于尋找這個(gè) axios 目錄的路徑的過程是這樣的:
去當(dāng)前文件目錄下 node_modules 中找
沒找到就去當(dāng)前文件父目錄下的 node_modules 中找
還沒找到就再往上一層
還沒找到就重復(fù)3,直到找到符合的模塊或者根目錄為止
以一個(gè) monorepo 項(xiàng)目為例子,一般在 monorepo 中一些包管理工具例如 yarn workspace 下會把一些依賴提升到外層的目錄中來,那么子項(xiàng)目就是這樣去尋找外層的依賴的:
node_modules axios here
packages
package-a
node_modules axios not found here
index.js -> const axios = require('axios');
2.2 項(xiàng)目模塊
在項(xiàng)目中執(zhí)行 require() 來載入 "/"、"./" 或者 "../" 開頭的模塊就是項(xiàng)目模塊。這里根據(jù)相對路徑或者絕對路徑所指向的模塊去進(jìn)行加載。通過加載模塊的時(shí)候如果不指定后綴名,Nodejs 則會通過枚舉去嘗試后綴名。后綴名依次是 .js 、.json 和 .node ,其中 .node后綴的文件就是 C++ 拓展。
例如目錄下有個(gè) addon.node 文件,我們可以 require 去加載(nodejs 是默認(rèn)支持的):
const addon = require('./addon');
什么是 Nodejs C++ 拓展
本質(zhì)
Node.js 是基于 C++ 開發(fā)的(底層用 chrome v8 做 js 引擎 && libuv 完成事件循環(huán)機(jī)制),因此它的所有底層頭文件暴露的 API 也都是適用于 C++ 的。
上一節(jié)中提到 nodejs 模塊尋徑的時(shí)候會默認(rèn)找 .node 為后綴名的模塊,實(shí)際上這是個(gè) C++ 模塊的二進(jìn)制文件,即編譯好之后的 C++ 模塊,本質(zhì)上是個(gè)動態(tài)鏈接庫。例如 (Windows dll/Linux so/Unix dylib)
在 Nodejs 在調(diào)用原生的 C++ 函數(shù)和調(diào)用 C++ 拓展函數(shù)的本質(zhì)區(qū)別在于前者的代碼會直接編譯成 Node.js 可執(zhí)行文件,而后者則是在動態(tài)鏈接庫中。
C++ 拓展加載方式
C++ 拓展的加載過程源碼可以參考:
https://github.com/nodejs/node/blob/master/src/node_binding.cc#L415
通過 uv_dlopen 這個(gè)方法去加載動態(tài)鏈接庫文件來完成
C++ 拓展模塊(.node二進(jìn)制鏈接庫文件)的具體加載過程:
在用戶首次執(zhí)行 require 時(shí)使用 uv_dlopen 來加載cpp addon 的 .node 鏈接庫文件
鏈接庫內(nèi)部把模塊注冊函數(shù)賦值給 mp
將執(zhí)行 require 時(shí)傳入的 module 和 exports 兩個(gè)對象傳入模塊注冊函數(shù)(mp 實(shí)例)進(jìn)行導(dǎo)出
相關(guān)加載代碼參考:
void DLOpen(const FunctionCallbackInfo& args ) {
Environment* env = Environment::GetCurrent(args);
uv_lib_t lib;
...
Local<Object> module = args[0]->ToObject(env->isolate());
node::Utf8Value filename(env->isolate(), args[1]);
// 使用 uv_dlopen 函數(shù)打開 .node 動態(tài)鏈接庫
const bool is_dlopen_error = uv_dlopen(*filename, &lib);
// 將加載出來的動態(tài)鏈接庫的句柄轉(zhuǎn)移給 node_module 的實(shí)例對象上來
node_module* const mp = modpending;
modpending = nullptr;
...
// 最后把一些
mp->nm_dso_handle = lib.handle;
mp->nm_link = modlist_addon;
modlist_addon = mp;
Local<String> exports_string = env->exports_string();
// exports_string 其實(shí)就是 `"exports"`
// 這句的意思是 `exports = module.exports`
Local<Object> exports = module->Get(exports_string)->ToObject(env->isolate());
// exports 和 module 傳給模塊注冊函數(shù)導(dǎo)出出去
if (mp->nm_context_register_func != nullptr) {
mp->nm_context_register_func(exports, module, env->context(), mp->nm_priv);
} else if (mp->nm_register_func != nullptr) {
mp->nm_register_func(exports, module, mp->nm_priv);
} else {
uv_dlclose(&lib);
env->ThrowError("Module has no declared entry point.");
return;
}
}

為什么要寫 C++ 拓展
C++ 比 js 高效
相同意思的代碼,在 js 解釋器中執(zhí)行 js 代碼效率比直接執(zhí)行一個(gè) Cpp 編譯好后的二進(jìn)制文件要低(后續(xù)會用 demo 驗(yàn)證)
一些已有的 C++ 輪子可以拿來用
例如一些常用的算法市面上只有 Cpp 實(shí)現(xiàn)且代碼太過復(fù)雜,用 JS 實(shí)現(xiàn)不現(xiàn)實(shí)(例如 Bling Hashes 字符串 hash 摘要算法、Open SDK)
一些系統(tǒng)底層 API 或者 V8 API 沒法通過 js 調(diào)用,可以封裝一個(gè) cpp addon 出來(例如:?緩解Node.js因生成heap snapshot導(dǎo)致進(jìn)程退出的一種辦法)
缺點(diǎn):
開發(fā)維護(hù)成本比較高,需要掌握一門 native 語言
增加了 native addon 的編譯流程以及拓展發(fā)布流程
發(fā)展歷史
這里介紹幾種開發(fā) Nodejs 拓展的方式:
原始方式
這種方式比較暴力,直接使用 nodejs 提供的原生模塊來開發(fā)頭文件,例如在 C++ 代碼中直接使用 Nodejs 相關(guān)的各種 API 以及 V8 的各種 API。需要開發(fā)者對 nodejs 以及 v8 文檔比較熟悉。而且隨著相關(guān) API 迭代導(dǎo)致無法跨版本去進(jìn)行使用。
NAN
Native Abstractions for Node.js,即 Node.js 原生模塊抽象接口集
本質(zhì)上是一堆宏判斷,在上層針對 libuv 和 v8 的 API 做了一些兼容性的處理,對用戶側(cè)而言是比較穩(wěn)定的 API 使用,缺點(diǎn)是不符合 ABI(二進(jìn)制應(yīng)用接口) 穩(wěn)定,對于不同版本的 Node.js 每次即使每次重新安裝了 node_modules 之后還需要對 C++ 代碼進(jìn)行重新編譯以適應(yīng)不同版本的Nodejs,即代碼只需要編寫一次,但需要使用者去到處編譯。
N-API
N-API 相比于 NAN 則是將 Nodejs 中底層所有的數(shù)據(jù)結(jié)構(gòu)都黑盒處理了,抽象成 N-API 中的接口。
不同版本的 Node.js 去使用這些接口,都是穩(wěn)定的、ABI 化的。使得在不同的 Node.js 版本下,代碼只需要編譯一次就可以直接使用,不需要去重新進(jìn)行編譯。在 Nodev8.x 時(shí)發(fā)布。
以 C 語言風(fēng)格提供穩(wěn)定的 ABI 接口
消除 Node.js 版本差異
消除 js 引擎差異(例如 Chrome v8、Microsoft ChakraCore 等)
Node-Addon-API
目前 Node.js 社區(qū)推崇的寫 Cpp addon 的方式,實(shí)際上是基于 N-API 的一層 C++ 封裝(本質(zhì)上還是 N-API)。
支持的最早版本是 Nodev10.x(在 v10.x 之后逐步穩(wěn)定)。
API 更簡單
文檔良心,編寫和測試都更方便
官方維護(hù)
今天介紹的也是這種方式來編寫 C++ 拓展。
準(zhǔn)備工作
安裝 node-gyp
npm i node-gyp -g
node-gyp 這里是個(gè) nodejs 官方維護(hù)的 C++ 的構(gòu)建工具,幾乎所有的 Nodejs C++ 拓展都是由它來構(gòu)建。基于 GYP (generate your project,谷歌的一個(gè)構(gòu)建工具)進(jìn)行工作,簡單來說,可以想象成面向 C++ 的 Webpack。
作用是將 C++ 文件編譯成二進(jìn)制文件(即前面提到的后綴名為 .node 的文件)。
node-gyp 附帶的一些依賴環(huán)境(參考官方文檔,以 macos 為例子)
Python(一般 unix 系統(tǒng)都會自帶)
Xcode
同時(shí) node-gyp 也需要在項(xiàng)目下有個(gè) binding.gyp 的文件去進(jìn)行配置,寫法上和 json 類似,不過可以在里面寫注釋。
例如:
{
"targets": [
{
# 編譯之后的拓展文件名稱,例如這里就是 addon.node
"target_name": "addon",
# 待編譯的原 cpp 文件
"sources": [ "src/addon.cpp" ]
}
]
}
一些 demo
這一節(jié)主要是通過一些簡單的 demo 來入門 C++ Addon 的開發(fā):
Hello World
在做好一些準(zhǔn)備工作之后,我們可以先來利用 node-addon-api 開發(fā)一個(gè)簡單的 helloworld
初始化
mkdir hello-world && cd hello-world
npm init -y
# 安裝 node-addon-api 依賴
npm i node-addon-api
# 新建一個(gè) cpp 文件 && js 文件
touch addon.cpp index.js
配置 binding.gyp
{
"targets": [
{
# 編譯出來的 xxx.node 文件名稱,這里是 addon.node
"target_name": "addon",
# 被編譯的 cpp 源文件
"sources": [
"addon.cpp"
],
# 為了簡便,忽略掉編譯過程中的一些報(bào)錯(cuò)
"cflags!": [ "-fno-exceptions"],
"cflags_cc!": ["-fno-exceptions"],
# cpp 文件調(diào)用 n-api 的頭文件的時(shí)候能找到對應(yīng)的目錄
# 增加一個(gè)頭文件搜索路徑
"include_dirs": [
"require('node-addon-api').include")"
],
# 添加一個(gè)預(yù)編譯宏,避免編譯的時(shí)候并行拋錯(cuò)
'defines': [ 'NAPI_DISABLE_CPP_EXCEPTIONS' ],
}
]
}
寫原生的 cpp 拓展
這里貼兩份代碼,為了便于去做個(gè)區(qū)分比較:
原生 Node Cpp Addon ?版本:
// 引用 node.js 中的 node.h 頭文件
#include
namespace demo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
void Method(const FunctionCallbackInfo& args ) {
// 通過 v8 中的隔離實(shí)例(v8的引擎實(shí)例,有各種獨(dú)立的狀態(tài), 包括推管理、垃圾回收等)
// 存取 Nodejs 環(huán)境的實(shí)例
Isolate* isolate = args.GetIsolate();
// 返回一個(gè) v8 的 string 類型,值為 "hello world"
args.GetReturnValue().Set(String::NewFromUtf8(ioslate, "hello world"));
}
void init(Local<Object> exports) {
// nodejs 內(nèi)部宏,用于導(dǎo)出一個(gè) function
// 這里類似于 exports = { "hello": Method }
NODE_SET_METHOD(exports, "hello", Method);
}
// 來自 nodejs 內(nèi)部的一個(gè)宏: https://github.com/nodejs/node/blob/master/src/node.h#L839
// 用于注冊 addon 的回調(diào)函數(shù)
NODE_MODULE(addon, init);
}
Node-addon-api 版本:
// 引用 node-addon-api 的 頭文件
#include
// Napi 這個(gè)實(shí)際上封裝的是 v8 里面的一些數(shù)據(jù)結(jié)構(gòu),搭建了一個(gè)從 JS 到 V8 的橋梁
// 定義一個(gè)返回值為 Napi::String 的 函數(shù)
// CallbackInfo 是個(gè)回調(diào)函數(shù)類型 info 里面存的是 JS 調(diào)用這個(gè)函數(shù)時(shí)的一些信息
Napi::String Method(const Napi::CallbackInfo& info) {
// env 是個(gè)環(huán)境變量,提供一些執(zhí)行上下文的環(huán)境
Napi::Env env = info.Env();
// 返回一個(gè)構(gòu)造好的 Napi::String 類型的值
// New是個(gè)靜態(tài)方法,一般第一個(gè)參數(shù)是當(dāng)前執(zhí)行環(huán)境的上下變量,第二個(gè)是對應(yīng)的值
// 其他參數(shù)不做過多介紹
return Napi::String::New(env, "hello world~");
}
// 導(dǎo)出注冊函數(shù)
// 這里其實(shí)等同于 exports = { hello: Method }
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(
Napi::String::New(env, "hello"),
Napi::Function::New(env, Method)
);
return exports;
}
// node-addon-api 中用于注冊函數(shù)的宏
// hello 為 key, 可以是任意變量
// Init 則會注冊的函數(shù)
NODE_API_MODULE(hello, Init);
這里代碼里面的 Napi:: 命名空間里面的一些類型實(shí)際上是對 v8 原生的一些數(shù)據(jù)結(jié)構(gòu)做了包裝,調(diào)用的時(shí)候更簡單,數(shù)據(jù)結(jié)構(gòu)相關(guān)的文檔可以參考:https://github.com/nodejs/node-addon-api API 文檔那一節(jié)。
這里的 Napi 本質(zhì)上就是 C++ 和 JS 之間的一座相互溝通的橋梁。
這里拆分講解一下這些函數(shù)的作用, Method 函數(shù)是我們的一個(gè)執(zhí)行函數(shù),執(zhí)行該函數(shù)會返回一個(gè) "hello world" 的字符串值。
CallBackInfo 對應(yīng) v8 里面的 FunctionCallbackInfo 類型(里面有一些函數(shù)回調(diào)信息,存在 info 這個(gè)地址里面),里面包含了 JS 函數(shù)調(diào)用這個(gè)方法的時(shí)候需要的一些信息。
在 js 代碼中調(diào)用 cpp addon
我們通過對上面的 cpp 進(jìn)行進(jìn)行 node-gyp 的編譯,得到一個(gè) build 的目錄里面存放的是編譯產(chǎn)物,里面會有編譯出來的 二進(jìn)制動態(tài)鏈接文件(后綴名為 .node):
$ node-gyp configure build
# 或者為了更簡便一點(diǎn)會直接使用 node-gyp rebuild,這個(gè)命令包含了清除緩存并重新打包的功能
$ node-gyo rebuild
編譯之后我們直接在 js 代碼中引入即可:
// hello-world/index.js
const { hello } = require('./build/Release/addon');
console.log(hello());
A + B
在上一節(jié)我們講到了 Napi::CallbackInfo& info info 中會存 JS 調(diào)用該函數(shù)時(shí)的一些上下文信息,因此我們在 js 中給 cpp 函數(shù)傳參數(shù)也可以在 info 中獲取到,于是可以寫出下面一個(gè)簡單的 a + b 的 cpp addon demo:
#include
// 這里為了做演示,把 Napi 直接通過 using namespace 聲明了
// 只要該文件不被其他的 cpp 文件引用就不會出現(xiàn) namespace 污染 這里主要為了簡潔
using namespace Napi;
// 因?yàn)檫@里可能會遇到拋 error 的情況,因此返回值類型設(shè)置為 Value
// Value 包含了 Napi 里面的所有數(shù)據(jù)結(jié)構(gòu)
Value Add(const CallBackInfo& info) {
Env env = info.Env();
if (info.Length() < 2) {
// 異常處理相關(guān)的 API 可以參考
// 不過這里可以看到 cpp 里面拋異常代碼很麻煩... 建議這里可以在 js 端就處理好
// https://github.com/nodejs/node-addon-api/blob/main/doc/error_handling.md
TypeError::New(env, "Number of arg wrong").ThrowAsJavaScriptException();
return env.Nulll();
}
double a = info[0].As<Number>().Doublevalue();
double b = info[1].As<Number>().DoubleValue();
Number num = Number::new(env, a + b);
return num;
}
// exports = { add: Add };
Object Init(Env env, Object exports) {
exports.Set(String::New(env, "add"), Function::new(env, Add));
}
NODE_API_MODULE(addon, Init);
Js 調(diào)用只需要:
const { add } = require('./build/Release/addon');
// output is 5.2
console.log(add(2, 3.2));
callback
回調(diào)函數(shù)也是一樣,通過 info 這個(gè)也可以拿到,再貼個(gè) cpp addon 的 demo:
// addon.cpp
#include
// 這一節(jié)用 namespace 包裹一下,提前聲明一些數(shù)據(jù)結(jié)構(gòu)
// 省得調(diào)用的時(shí)候一直 Napi::xxx ...
namespace CallBackDemo {
using Napi::Value;
using Napi::CallbackInfo;
using Napi::Env;
using Napi::TypeError;
using Napi::Number;
using Napi::Object;
using Napi::String;
using Napi::Function;
void RunCallBack(const CallbackInfo &info) {
Env env = info.Env();
Function cb = info[0].As<Function>();
cb.Call(env.Global(), { String::New(env, "hello world") } );
}
Object Init(Env env, Object exports) {
return Function::New(env, RunCallback);
}
NODE_API_MODULE(addon, Init);
}
實(shí)戰(zhàn) demo
上面簡單講了一些 node native addon 的簡單 API 使用,算是做了個(gè)簡單的入門教學(xué),下面選了個(gè)簡單的實(shí)際 demo 來看一下 node-addon-api 在具體項(xiàng)目中起到的作用:
案例展開講一下,封裝了 v8 的 API 用于 debug
參考案例:緩解Node.js因生成heap snapshot導(dǎo)致進(jìn)程退出的一種辦法
代碼地址:https://github.com/bytedance/diat/tree/master/packages/addon

更多 demo
可以參考:
Nodejs 官方的 addon examples:?https://github.com/nodejs/node-addon-examples
Nodejs 來一打 cpp 拓展隨書代碼:?https://github.com/XadillaX/nyaa-nodejs-demo
性能對比
可以通過一個(gè)簡單的 Demo 去做一下對比:
quickSort (O(nlogn))
我們可以手寫個(gè)快排分別在 JS 或者 CPP 兩邊去 run 一下來對比性能:
首先我們的 cpp addon 代碼可以這樣寫:
#include
#include
#include
// 快排 時(shí)間復(fù)雜度 O(nlogn) 空間復(fù)雜度 O(1)
void quickSort(int a[], int l, int r) {
if (l >= r) return;
int x = a[(l + r) >> 1], i = l -1, j = r + 1;
while (i < j) {
while (a[++i] < x);
while (a[--j] > x);
if (i < j) {
std::swap(a[i], a[j]);
}
}
quickSort(a, l, j);
quickSort(a, j + 1, r);
}
Napi::Value Main(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::Array arr = info[0].AsArray>();
int len = arr.Length();
// 存返回值
Napi::Array res = Napi::Array::New(env, len);
int* arr2 = new int[len];
// 轉(zhuǎn)化一下數(shù)據(jù)結(jié)構(gòu)
for (int i = 0; i < len; i++) {
Napi::Value value = arr[i];
arr2[i] = value.ToNumber().Int64Value();
}
quickSort(arr2, 0, len - 1);
// for (int i = 0; i < len; i ++) {
// std::cout << arr2[i] << " ";
// }
// std::cout << std::endl;
// 轉(zhuǎn)回 JS 的數(shù)據(jù)結(jié)構(gòu)
for (int i = 0; i < len; i ++) {
res[i] = Napi::Number::New(env, arr2[i]);
}
return res;
}
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(
Napi::String::New(env, "quicksortCpp"),
Napi::Function::New(env, Main)
);
return exports;
}
NODE_API_MODULE(addon, Init);
JS側(cè)的代碼可以這樣寫:
// 這里使用 bindings 這個(gè)庫,他會幫我們自動去尋找 addon.node 對應(yīng)目錄
// 不需要再去指定對應(yīng)的 build 目錄了
const { quicksortCpp } = require('bindings')('addon.node');
// 構(gòu)造一個(gè)函數(shù)出來
const arr = Array.from(new Array(1e3), () => Math.random() * 1e4 | 0);
let arr1 = JSON.parse(JSON.stringify(arr));
let arr2 = JSON.parse(JSON.stringify(arr));
console.time('JS');
const solve = (arr) => {
let n = arr.length;
const quickSortJS = (arr, l, r) => {
if (l >= r) {
return;
}
let x = arr[Math.floor((l + r) >> 1)], i = l - 1, j = r + 1;
while (i < j) {
while(arr[++i] < x);
while(arr[--j] > x);
if (i < j) {
[arr[i], arr[j]] = [arr[j], arr[i]];
}
}
quickSortJS(arr, l, j);
quickSortJS(arr, j + 1, r);
}
quickSortJS(arr, 0, n - 1);
}
solve(arr2);
console.timeEnd('JS');
console.time('C++');
const a = quicksortCpp(arr1);
console.timeEnd('C++');
這里兩側(cè)代碼基本上從實(shí)現(xiàn)上來說都是一模一樣的,在實(shí)際運(yùn)行中,通過去修改數(shù)組的長度對比兩者的效率,我們可以得到如下的數(shù)據(jù):
| 數(shù)組長度/時(shí)間 | 1e2 | 1e3 | 1e4 | 1e5 | 1e6 |
|---|---|---|---|---|---|
| JS | 0.255ms | 4.391ms | 10.810ms | 26.004ms | 116.914ms |
| C++ | 0.065ms | 0.347ms | 2.908ms | 23.637ms | 234.757ms |
那么我們可以看到在數(shù)組長度相對而言比較低的時(shí)候,C++ Addon 的快排效率是要完爆 JS 的,但隨著數(shù)組長度的增長,C++ 就呈現(xiàn)一種被完爆的趨勢。
導(dǎo)致這種情況的原因是因?yàn)?V8 的數(shù)據(jù)結(jié)構(gòu)與 C++ 里面原生的數(shù)據(jù)結(jié)構(gòu)轉(zhuǎn)換所帶來的消耗:

1e5 的數(shù)據(jù)規(guī)模下,實(shí)際上 cpp 的 quickSort 算法只跑了大概 6.9ms,而算上數(shù)據(jù)轉(zhuǎn)換的時(shí)間,一共就跑了 28.9ms......
隨著數(shù)據(jù)規(guī)模的增大這種轉(zhuǎn)換帶來的開銷就越來越大,因此在這種時(shí)候如果使用 C++ 的話,可能會得不償失。
綜上來看,有時(shí)候 C++ 寫出來的包確實(shí)會在性能上稍微高于 Nodejs 的 JS 代碼,但如果高出來的這部分性能還比不過 Nodejs 打開并且執(zhí)行 C++ Addon 所消耗掉的 I/O 時(shí)間或者在 v8 數(shù)據(jù)結(jié)構(gòu)與 C++ 數(shù)據(jù)結(jié)構(gòu)之前進(jìn)行轉(zhuǎn)換的所消耗的時(shí)間(例如上面的 Case) ,這個(gè)時(shí)候用 C++ 可能就得不償失了。
不過一般情況下,針對并非并行 && 計(jì)算密集型代碼來說,C++ 效率還是會好于 Nodejs 的。
總結(jié)
隨著 N_API 體系的發(fā)展以及 nodejs 開發(fā)團(tuán)隊(duì)的不斷迭代更新,未來開發(fā) native addon 的成本也會越來越低,在一些特定的場景里面(例如需要用到一些 v8 的 API 場景或者 electron + openCV 場景),nodejs addon 可能會變得極其重要,未來使用場景也會不斷的提高。
參考資料
《Nodejs,來一打 C++ 拓展》原書
https://github.com/nodejs/node-addon-api
https://github.com/nodejs/node-addon-examples
我組建了一個(gè)氛圍特別好的 Node.js 社群,里面有很多 Node.js小伙伴,如果你對Node.js學(xué)習(xí)感興趣的話(后續(xù)有計(jì)劃也可以),我們可以一起進(jìn)行Node.js相關(guān)的交流、學(xué)習(xí)、共建。下方加 考拉 好友回復(fù)「Node」即可。

???“分享、點(diǎn)贊、在看” 支持一波??
