node c++和js互相调用

背景

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

方案选型

在这里插入图片描述
选择封装性和便捷性最好的 node-addon-api

关键技术

js调用c++

异步回调(同步方式太简单)

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

SimpleAsyncWorker.h

#pragma once
#include <napi.h>
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 <chrono>
#include <thread>

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 <chrono>
#include <napi.h>
#include <thread>

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': ["<!@(node -p \"require('node-addon-api').include\")"],
      'dependencies': ["<!(node -p \"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

  • 3
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
Node.js 可以通过 C++ 模块来扩展其功能,实现 C++ 模块函数的调用需要使用 Node.js 提供的 `N-API` 接口。下面是一个简单的例子,演示了如何在 C++ 模块中实现一个函数,并在 Node.js调用该函数并传递参数。 C++ 模块代码: ```cpp #include <node_api.h> napi_value add(napi_env env, napi_callback_info info) { size_t argc = 2; napi_value args[2]; napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); int32_t a, b; napi_get_value_int32(env, args[0], &a); napi_get_value_int32(env, args[1], &b); napi_value result; napi_create_int32(env, a + b, &result); return result; } napi_value Init(napi_env env, napi_value exports) { napi_value fn; napi_create_function(env, nullptr, 0, add, nullptr, &fn); napi_set_named_property(env, exports, "add", fn); return exports; } NAPI_MODULE(NODE_GYP_MODULE_NAME, Init) ``` 上述代码定义了一个 `add` 函数,该函数接受两个整数参数,返回它们的和。然后将该函数导出为 Node.js 模块的一个属性。 在 Node.js调用该函数: ```javascript const addon = require('./build/Release/addon'); console.log(addon.add(1, 2)); // 3 ``` 上述代码首先通过 `require` 函数引入了 C++ 模块,然后调用了模块的 `add` 函数,并传入两个整数参数。`add` 函数返回它们的和,该和被输出到控制台上。 这是一个简单的例子,演示了如何在 C++ 模块中实现函数,并在 Node.js调用该函数并传递参数。具体实现方式可以根据实际情况来进行调整。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

骇客之技术

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值