node c++和js互相调用

node c++和js互相调用,第1张

背景

一些业务功能需要nodejs和c++互相调用

方案选型


选择封装性和便捷性最好的 node-addon-api

关键技术 js调用c++ 异步回调(同步方式太简单)

异步回调需要继承AsyncWorker,一般需要覆写Execute方法。
编译可参考下面 binding.gyp 模板。

SimpleAsyncWorker.h

#pragma once
#include 
using 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<Number>();
  Function callback = info[1].As<Function>();
  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<Value>;
using DataType = int;
void CallJs(Napi::Env env, Function callback, Context *context, DataType *data);
using TSFN = TypedThreadSafeFunction<Context, DataType, CallJs>;
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<Number>().Int32Value();

  // Create a new context set to the the receiver (ie, `this`) of the function
  // call
  Context *context = new Reference<Value>(Persistent(info.This()));

  // Create a ThreadSafeFunction
  tsfn = TSFN::New(
      env,
      info[0].As<Function>(), // 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

  1. 先全局安装 cmake-js npm i -g cmake-js 已安装就不需要这一步了
  2. 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 "[\r\n\"]" "" 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

  1. 先全局安装 node-gyp npm i -g node-gyp 已安装就不需要这一步了
  2. node-gyp build
    binding.gyp 模板如下
{
  'targets': [
    {
      'target_name': 'napi-asyncworker-example-native',
      'sources': [ 'src/RunSimpleAsyncWorker.cc', 'src/SimpleAsyncWorker.cc' ],
      'include_dirs': ["\"require('node-addon-api').include\")"],
      'dependencies': ["\"require('node-addon-api').gyp\")"],
      'cflags!': [ '-fno-exceptions' ],
      'cflags_cc!': [ '-fno-exceptions' ],
      'xcode_settings': {
        '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

欢迎分享,转载请注明来源:内存溢出

原文地址:https://54852.com/langs/1498820.html

(0)
打赏 微信扫一扫微信扫一扫 支付宝扫一扫支付宝扫一扫
上一篇 2022-06-25
下一篇2022-06-25

发表评论

登录后才能评论

评论列表(0条)

    保存