Nodejs C++外掛(N-API)
阿新 • • 發佈:2021-12-16
Nodejs C++外掛(N-API)
N-API 作為Nodejs專案的一部分,旨在更加便捷穩定的建立Nodejs Native 外掛。這個API作為應用二進位制介面(ABI),在Node.js的各個版本中都是穩定的,而且ABI允許一個在主要版本編譯的模組,在以後的Node.js版本上執行,而無需重新編譯。
0. 環境搭建
- 安裝最新的npm,且Nodejs版本10.x以上
- 工具鏈
- Windows執行
npm install --global windows-build-tools
- MacOS執行
xcode-select --install
- Linux使用系統自帶的GCC即可
- Windows執行
- 安裝構建工具
npm install --global node-gyp
- 初始化測試專案
npm init
- 新增napi模組
npm i node-addon-api
- 為VSCode新增智慧提示所需標頭檔案(以Windows為例)
-
Ctrl+Shift+P
-> C++ 新增c_cpp_properties.json
檔案 -
執行
node-gyp configure
檢視-Dnode_root_dir
-
c_cpp_properties.json
includePath 中加入C:\\Users\\gaobowen\\AppData\\Local\\node-gyp\\Cache\\10.16.0\\include\\node
-
1. JS中呼叫C++方法
1.1 JS中呼叫原始檔的C++方法
- 編寫功能函式
test.h、test.cc
#ifndef SAMPLE_TEST_H #define SAMPLE_TEST_H #include <napi.h> #include <windows.h> #include <sstream> #include <thread> Napi::Value plus(const Napi::CallbackInfo& info); #endif
#include "test.h"
Napi::Value plus(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::EscapableHandleScope scope(env);
if (info.Length() < 1)
return Napi::Boolean::New(env, false);
if (!info[0].IsNumber())
return Napi::Boolean::New(env, false);
if (!info[1].IsNumber())
return Napi::Boolean::New(env, false);
auto a = info[0].As<napi::number>().DoubleValue();
auto b = info[1].As<napi::number>().DoubleValue();
return Napi::Number::New(env, a + b);
}
- 編寫匯出函式
exports.cc
#include <napi.h>
#include "test.h"
Napi::Object Init(Napi::Env env, Napi::Object exports)
{
exports.Set(Napi::String::New(env, "plus"), Napi::Function::New(env, plus));
return exports;
}
NODE_API_MODULE(napi_sample, Init)
- 新增
binding.gyp
檔案
{
"targets": [
{
"target_name": "napi_sample",
"sources": [ ],
"cflags!": [ "-fno-exceptions" ],
"cflags_cc!": [ "-fno-exceptions" ],
"defines": [ "NAPI_DISABLE_CPP_EXCEPTIONS" ],
"include_dirs": [
"<!--@(node -p \"require('node-addon-api').include\")"
],
"conditions": [
['OS=="win"', {
"sources": [ "sample/exports.cc", "sample/test.cc" ],
}]
]
}
]
}
- 命令列執行
node-gyp rebuild
生成./build/Release/napi_sample.node
檔案 - 測試使用
napi_sample.node
外掛
node .\test.js
let { plus } = require('./build/Release/napi_sample.node');
console.log('test export=-->', plus);
console.log('test call=>', plus(1.2, 1.3));
控制檯輸出
test export=> function () { [native code] }
test call=> 2.5
1.2 JS中呼叫動態庫的C++方法
Napi::Value call_dll(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::EscapableHandleScope scope(env);
//注,若動態庫還有其他依賴庫,請放在同一級目錄下
auto m = LoadLibraryExA("user32.dll", NULL, LOAD_WITH_ALTERED_SEARCH_PATH);
typedef int(*MyMessageBoxA)(int, char*, char*, int);
auto box = (MyMessageBoxA)GetProcAddress(m, "MessageBoxA");
box(0, "js call dll function", "abc", 0);
return Napi::Boolean::New(env, true);
}
控制檯輸出
test call_dll=> true
2. C++中呼叫JS方法
2.1. C++單執行緒呼叫JS方法
test.cpp
Napi::Value cpp_call_js(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::EscapableHandleScope scope(env);
if (info.Length() < 0)
return Napi::Boolean::New(env, false);
if (!info[0].IsFunction())
return Napi::Boolean::New(env, false);
auto jscb = info[0].As<napi::function>();
//回撥函式入參
napi_value a, b;
napi_create_double(env, 1, &a);
napi_create_double(env, 2, &b);
napi_value argv[] = { a, b };
//回撥函式返回值
napi_value result;
napi_call_function(env, env.Global(), jscb, 2, argv, &result);
return Napi::Value(env, result);
}
test.js
let { cpp_call_js } = require('./build/Release/napi_sample.node');
(async function () {
let c = 3;
let js_callback = function(a, b){
return a + b + c;
}
console.log('test cpp_call_js=>', cpp_call_js(js_callback));
})();
控制檯輸出
test cpp_call_js=> 6
2.2. C++多執行緒呼叫JS方法
test.cpp
Napi::ThreadSafeFunction tsfunc;
void thread_function() {
auto threadid = std::this_thread::get_id();
std::stringstream ss;
ss << threadid;
auto threadidstr = new std::string(ss.str());
auto callback = [threadid](Napi::Env env, Napi::Function jscb, std::string* p_str) {
jscb.Call({ Napi::String::New(env, *p_str) });
delete p_str;
};
tsfunc.Acquire();
napi_status status = tsfunc.BlockingCall(threadidstr, callback);
if (status != napi_ok) {
printf("tsfunc.BlockingCall error.\n");
}
tsfunc.Release();
}
Napi::Value cpp_thread_call_js(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Napi::EscapableHandleScope scope(env);
if (info.Length() < 0)
return Napi::Boolean::New(env, false);
if (!info[0].IsFunction())
return Napi::Boolean::New(env, false);
auto jscb = info[0].As<napi::function>();
tsfunc = Napi::ThreadSafeFunction::New(env, jscb, "my-tsfunc", 2, 2);
std::thread(thread_function).join();
std::thread(thread_function).join();
return Napi::Boolean::New(env, true);
}
test.js
let { cpp_thread_call_js } = require('./build/Release/napi_sample.node');
(async function () {
let js_callback_async = function(id){
console.log('thread_id',id);
}
console.log('test cpp_thread_call_js=>', cpp_thread_call_js(js_callback_async));
})();
控制檯輸出
test cpp_thread_call_js=> true
thread_id 6768
thread_id 12352