NAPI 開發 C++ Addon


實現C++和NodeJS代碼的聯合編程,總結下來有下面幾種途徑:

1. Nodeffi

由於node-ffi只支持win32系統,在nodejs11以及以上版本也就不再支持,用的會越來越少

2. Emscripten

脫胎於asm.js,適用於C++代碼比較固化的情況。

3. v8

官方的原生編寫c++ addon的接口,代碼不容易理解

4. Napi

本文介紹,對v8進行進一步的封裝。

 

一. NAPI 介紹

Node-API(以前稱為N-API)是用於構建本機插件的API。它獨立於底層JavaScript運行時(例如V8),並作為Node.js本身的一部分進行維護。該API在所有版本的Node.js中都是穩定的應用程序二進制接口(ABI)。它旨在使附加組件與基礎JavaScript引擎中的更改隔離,並使為一個主要版本編譯的模塊可以在Node.js的更高主要版本上運行,而無需重新編譯。ABI穩定性指南》提供了更深入的說明。

使用標題為C ++ Addons的部分中概述的相同方法/工具來構建/打包Addons唯一的區別是本機代碼使用的API集。代替使用V8或Node.js API的本機抽象,使用Node-API中可用的功能。

Node-API公開的API通常用於創建和操作JavaScript值。概念和操作通常映射到ECMA-262語言規范中指定的概念。API具有以下屬性:

  • 所有Node-API調用均返回類型為的狀態碼napi_status此狀態指示API調用是成功還是失敗。
  • API的返回值通過out參數傳遞。
  • 所有JavaScript值都在名為的不透明類型之后抽象 napi_value
  • 如果是錯誤狀態代碼,則可以使用獲取其他信息napi_get_last_error_info在錯誤處理部分錯誤處理中可以找到更多信息

Node-API是一種C API,可確保跨Node.js版本和不同編譯器級別的ABI穩定性。C ++ API可能更易於使用。

為了支持使用C ++,項目維護了一個名為的C ++包裝器模塊node-addon-api該包裝器提供了一個可內聯的C ++ API。內置的二進制文件node-addon-api將取決於由Node.js導出的基於Node-API C的函數的符號。node-addon-api是編寫調用Node-API的代碼的更有效方法。

node-addon-api不是node.js的一部分,需要獨立安裝,后面會演示用法

 

二. Hello world 示例

官網上提供的最簡單的example,js調用hello,返回world

 1 // hello.cc using Node-API
 2 #include <node_api.h>
 3 
 4 namespace demo {
 5 
 6 napi_value Method(napi_env env, napi_callback_info args) {
 7   napi_value greeting;
 8   napi_status status;
 9 
10   status = napi_create_string_utf8(env, "world", NAPI_AUTO_LENGTH, &greeting);
11   if (status != napi_ok) return nullptr;
12   return greeting;
13 }
14 
15 napi_value init(napi_env env, napi_value exports) {
16   napi_status status;
17   napi_value fn;
18 
19   status = napi_create_function(env, nullptr, 0, Method, nullptr, &fn);
20   if (status != napi_ok) return nullptr;
21 
22   status = napi_set_named_property(env, exports, "hello", fn);
23   if (status != napi_ok) return nullptr;
24   return exports;
25 }
26 
27 NAPI_MODULE(NODE_GYP_MODULE_NAME, init)
28 
29 }  // namespace demo

 

三. NAPI 的編譯

編譯NAPI有兩種方式:node-gyp 和 cmake-js

node-gyp:

安裝:npm install -g node-gyp

在項目中要使用node-gyp, 在項目目錄下添加binding.gyp文件:

{
  "targets": [
    {
      "target_name": "addon",
      "include_dirs": ["/path/to/include/dir"],
      "library_dirs": ["/path/to/library/dir"],
      "libraries": ["libXXX.so"],
      "sources": [ "addon.cc", "example.cc" ]
    }
  ]
}

執行命令 node-gyp configure build 之后可以看到項目build文件夾下有個XXX.node文件,就是我們調用時要的二進制文件。

cmake-js:

安裝: npm install -g cmake-js 

安裝完成后,可以編寫CMakeLists.txt文件來編譯:

cmake_minimum_required(VERSION 3.5)

project(cmake-js-test LANGUAGES CXX)

include_directories(${CMAKE_JS_INC})

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB SOURCE_FILES "./*cc" "./*.h")

add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})

set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")

target_link_libraries(${PROJECT_NAME} ${PROJECT_LINK_LIBS} ${CMAKE_JS_LIB})

在目錄下執行cmake-js compile 或者用Qt Creator也可以打開該項目,點擊build

編譯好了以后,同樣在build目錄下有XXX.node文件。

用Qt Creator編譯的代碼可以用Qt Creator附加js進程調試,VSCode應該也可以。

 

nodejs調用上例中的hello world,就2行代碼:

1 const addon = require('./build/cmake-js-test');
2 console.log(addon.hello());

 

四. NAPI 包裝類

Wrap一個類在napi中代碼比較難以理解,本例中使用node-addon-api。

在js項目中安裝依賴:

npm install --save-dev node-addon-api

npm install --save-dev bindings

在目錄下寫一個類:

MyObject.h

 1 #ifndef MYOBJECT_H
 2 #define MYOBJECT_H
 3 
 4 #include <napi.h>
 5 
 6 class MyObject : public Napi::ObjectWrap<MyObject> {
 7  public:
 8   static Napi::Object Init(Napi::Env env, Napi::Object exports);
 9   MyObject(const Napi::CallbackInfo& info);
10 
11  private:
12   Napi::Value GetValue(const Napi::CallbackInfo& info);
13   Napi::Value PlusOne(const Napi::CallbackInfo& info);
14   Napi::Value Multiply(const Napi::CallbackInfo& info);
15 
16   double value_;
17 };
18 
19 #endif

MyObject.cc

 1 #include "example.h"
 2 
 3 Napi::Object MyObject::Init(Napi::Env env, Napi::Object exports) {
 4   Napi::Function func =
 5       DefineClass(env,
 6                   "MyObject",
 7                   {InstanceMethod("plusOne", &MyObject::PlusOne),
 8                    InstanceMethod("value", &MyObject::GetValue),
 9                    InstanceMethod("multiply", &MyObject::Multiply)});
10 
11   Napi::FunctionReference* constructor = new Napi::FunctionReference();
12   *constructor = Napi::Persistent(func);
13   env.SetInstanceData(constructor);
14 
15   exports.Set("MyObject", func);
16   return exports;
17 }
18 
19 MyObject::MyObject(const Napi::CallbackInfo& info)
20     : Napi::ObjectWrap<MyObject>(info) {
21   Napi::Env env = info.Env();
22 
23   int length = info.Length();
24 
25   if (length <= 0 || !info[0].IsNumber()) {
26     Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
27     return;
28   }
29 
30   Napi::Number value = info[0].As<Napi::Number>();
31   this->value_ = value.DoubleValue();
32 }
33 
34 Napi::Value MyObject::GetValue(const Napi::CallbackInfo& info) {
35   double num = this->value_;
36 
37   return Napi::Number::New(info.Env(), num);
38 }
39 
40 Napi::Value MyObject::PlusOne(const Napi::CallbackInfo& info) {
41   this->value_ = this->value_ + 1;
42 
43   return MyObject::GetValue(info);
44 }
45 
46 Napi::Value MyObject::Multiply(const Napi::CallbackInfo& info) {
47   Napi::Number multiple;
48   if (info.Length() <= 0 || !info[0].IsNumber()) {
49     multiple = Napi::Number::New(info.Env(), 1);
50   } else {
51     multiple = info[0].As<Napi::Number>();
52   }
53 
54   Napi::Object obj = info.Env().GetInstanceData<Napi::FunctionReference>()->New(
55       {Napi::Number::New(info.Env(), this->value_ * multiple.DoubleValue())});
56 
57   return obj;
58 }

接口addon.cc

1 #include <napi.h>
2 #include <node/node_api.h>
3 #include "example.h"
4 
5 Napi::Object InitAll(Napi::Env env, Napi::Object exports) {
6   return MyObject::Init(env, exports);
7 }
8 
9 NODE_API_MODULE(NODE_GYP_MODULE_NAME, InitAll)

CMakeList:

cmake_minimum_required(VERSION 3.5)

project(cmake-js-test LANGUAGES CXX)

include_directories(${CMAKE_JS_INC})

set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

file(GLOB SOURCE_FILES "./*cc" "./*.h")

include_directories(/usr/lib/node_modules/node-addon-api)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})

set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")

target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})

編譯號后build文件夾看到addon.node

 

 nodejs測試:

 1 var addon = require('bindings')('addon');
 2 
 3 var obj = new addon.MyObject(10);
 4 console.log( obj.plusOne() ); // 11
 5 console.log( obj.plusOne() ); // 12
 6 console.log( obj.plusOne() ); // 13
 7 
 8 console.log( obj.multiply().value() ); // 13
 9 console.log( obj.multiply(10).value() ); // 130
10 
11 var newobj = obj.multiply(-1);
12 console.log( newobj.value() ); // -13
13 console.log( obj === newobj ); // false

這里有豐富的例子可以學習。

https://github.com/nodejs/node-addon-examples

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM