一些业务功能需要nodejs和c++互相调用
方案选型
选择封装性和便捷性最好的 node-addon-api
异步回调需要继承AsyncWorker,一般需要覆写Execute方法。
编译可参考下面 binding.gyp 模板。
SimpleAsyncWorker.h
#pragma once #includeusing namespace Napi; class SimpleAsyncWorker : public AsyncWorker { public: SimpleAsyncWorker(Function& callback, int runTime); virtual ~SimpleAsyncWorker(){}; void Execute(); void OnOK(); private: int runTime; };
SimpleAsyncWorker.cc
#include "SimpleAsyncWorker.h" #include#include SimpleAsyncWorker::SimpleAsyncWorker(Function& callback, int runTime) : AsyncWorker(callback), runTime(runTime){}; void SimpleAsyncWorker::Execute() { std::this_thread::sleep_for(std::chrono::seconds(runTime)); if (runTime == 4) SetError("Oops! Failed after 'working' 4 seconds."); }; void SimpleAsyncWorker::OnOK() { std::string msg = "SimpleAsyncWorker returning after 'working' " + std::to_string(runTime) + " seconds."; Callback().Call({Env().Null(), String::New(Env(), msg)}); };
模块导出 RunSimpleAsyncWorker.cc
#include "SimpleAsyncWorker.h"
Value runSimpleAsyncWorker(const CallbackInfo& info) {
int runTime = info[0].As();
Function callback = info[1].As();
SimpleAsyncWorker* asyncWorker = new SimpleAsyncWorker(callback, runTime);
asyncWorker->Queue();
std::string msg =
"SimpleAsyncWorker for " + std::to_string(runTime) + " seconds queued.";
return String::New(info.Env(), msg.c_str());
};
Object Init(Env env, Object exports) {
exports["runSimpleAsyncWorker"] = Function::New(
env, runSimpleAsyncWorker, std::string("runSimpleAsyncWorker"));
return exports;
}
NODE_API_MODULE(addon, Init)
测试 Test.js
const runWorker = require('../build/Release/napi-asyncworker-example-native');
let result = runWorker.runSimpleAsyncWorker(2, AsyncWorkerCompletion);
console.log("runSimpleAsyncWorker returned '"+result+"'.");
result = runWorker.runSimpleAsyncWorker(4, AsyncWorkerCompletion);
console.log("runSimpleAsyncWorker returned '"+result+"'.");
result = runWorker.runSimpleAsyncWorker(8, AsyncWorkerCompletion);
console.log("runSimpleAsyncWorker returned '"+result+"'.");
function AsyncWorkerCompletion (err, result) {
if (err) {
console.log("SimpleAsyncWorker returned an error: ", err);
} else {
console.log("SimpleAsyncWorker returned '"+result+"'.");
}
};
输出如下
runSimpleAsyncWorker returned 'SimpleAsyncWorker for 2 seconds queued.'. runSimpleAsyncWorker returned 'SimpleAsyncWorker for 4 seconds queued.'. runSimpleAsyncWorker returned 'SimpleAsyncWorker for 8 seconds queued.'. SimpleAsyncWorker returned 'SimpleAsyncWorker returning after 'working' 2 seconds.'. SimpleAsyncWorker returned an error: [Error: Oops! Failed after 'working' 4 seconds.] SimpleAsyncWorker returned 'SimpleAsyncWorker returning after 'working' 8 seconds.'.c++调用js
c++异步回调js方法
通过TypedThreadSafeFunction 构造全局tsfn对象,再在native线程调用
编译可参考后面CMakeLists.txt模板
c++ 代码 clock.cc
#include#include #include using namespace Napi; using Context = Reference ; using DataType = int; void CallJs(Napi::Env env, Function callback, Context *context, DataType *data); using TSFN = TypedThreadSafeFunction ; using FinalizerDataType = void; std::thread nativeThread; TSFN tsfn; Value Start(const CallbackInfo &info) { Napi::Env env = info.Env(); if (info.Length() < 2) { throw TypeError::New(env, "Expected two arguments"); } else if (!info[0].IsFunction()) { throw TypeError::New(env, "Expected first arg to be function"); } else if (!info[1].IsNumber()) { throw TypeError::New(env, "Expected second arg to be number"); } int count = info[1].As ().Int32Value(); // Create a new context set to the the receiver (ie, `this`) of the function // call Context *context = new Reference (Persistent(info.This())); // Create a ThreadSafeFunction tsfn = TSFN::New( env, info[0].As (), // JavaScript function called asynchronously "Resource Name", // Name 0, // Unlimited queue 1, // Only one thread will use this initially context, [](Napi::Env, FinalizerDataType *, Context *ctx) { // Finalizer used to clean threads up nativeThread.join(); delete ctx; }); // Create a native thread nativeThread = std::thread([count] { for (int i = 0; i < count; i++) { // Create new data int *value = new int(clock()); // Perform a blocking call napi_status status = tsfn.BlockingCall(value); if (status != napi_ok) { // Handle error break; } std::this_thread::sleep_for(std::chrono::seconds(1)); } // Release the thread-safe function tsfn.Release(); }); return Boolean::New(env, true); } // Transform native data into JS data, passing it to the provided // `callback` -- the TSFN's JavaScript function. void CallJs(Napi::Env env, Function callback, Context *context, DataType *data) { // Is the JavaScript environment still available to call into, eg. the TSFN is // not aborted if (env != nullptr) { // On Node-API 5+, the `callback` parameter is optional; however, this example // does ensure a callback is provided. if (callback != nullptr) { callback.Call(context->Value(), {Number::New(env, *data)}); } } if (data != nullptr) { // We're finished with the data. delete data; } } Napi::Object Init(Napi::Env env, Object exports) { exports.Set("start", Function::New(env, Start)); return exports; } NODE_API_MODULE(clock, Init)
测试 Test.js
const { start } = require('bindings')('clock');
start.call(new Date(), function (clock) {
const context = this;
console.log(context, clock);
}, 5);
编译
cmake-js 方式
需要 CMakeLists.txt
- 先全局安装 cmake-js npm i -g cmake-js 已安装就不需要这一步了
- cmake-js build
CMakeLists.txt模板如下
project (clock)
include_directories(${CMAKE_JS_INC} node_modules/node-addon-api/)
cmake_minimum_required(VERSION 3.18)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(${CMAKE_JS_INC})
file(GLOB SOURCE_FILES "*.cc")
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})
# Include Node-API wrappers
execute_process(COMMAND node -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
string(REGEX REPLACE "[rn"]" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
# define NAPI_VERSION
add_definitions(-DNAPI_VERSION=4)
node-gyp方式
需要 binding.gyp
- 先全局安装 node-gyp npm i -g node-gyp 已安装就不需要这一步了
- node-gyp build
binding.gyp 模板如下
{
'targets': [
{
'target_name': 'napi-asyncworker-example-native',
'sources': [ 'src/RunSimpleAsyncWorker.cc', 'src/SimpleAsyncWorker.cc' ],
'include_dirs': ["
'GCC_ENABLE_CPP_EXCEPTIONS': 'YES',
'CLANG_CXX_LIBRARY': 'libc++',
'MACOSX_DEPLOYMENT_TARGET': '10.7'
},
'msvs_settings': {
'VCCLCompilerTool': { 'ExceptionHandling': 1 },
}
}
]
}
vs方式
可参考cmake-js 或者node-gyp 编译生成的中间解决方案
参考n-api



