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
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· Manus重磅发布:全球首款通用AI代理技术深度解析与实战指南
· 开源Multi-agent AI智能体框架aevatar.ai,欢迎大家贡献代码
· 被坑几百块钱后,我竟然真的恢复了删除的微信聊天记录!
· AI技术革命,工作效率10个最佳AI工具