Node.js Native Extension Development Basics
Overview of Extension Development Architecture
Node.js native extensions enable developers to write high-performance modules in C/C++ that interact with JavaScript. The core architecture consists of three key components:
- V8 Engine Integration: Handles conversion of JavaScript objects and functions
- libuv Event Loop: Manages asynchronous I/O operations
- Node.js API Layer: Provides high-level abstraction interfaces
Typical Extension Module Structure:
my-addon/
├── binding.gyp # Build configuration file
├── src/
│ ├── addon.cc # Main extension code
│ └── utils.h # Utility functions
├── package.json
└── test/
└── test.js # Test scriptBuild System Configuration
binding.gyp Example:
{
"targets": [
{
"target_name": "my-addon",
"sources": ["src/addon.cc"],
"include_dirs": ["<!(node -e \"require('node-addon-api').include\")"],
"dependencies": ["<!(node -e \"require('node-addon-api').gyp\")"],
"cflags_cc": ["-std=c++17"],
"conditions": [
["OS=='win'", {"defines": ["OS_WIN"]}],
["OS=='mac'", {"defines": ["OS_MAC"]}]
]
}
]
}Build Commands:
# Install node-gyp
npm install -g node-gyp
# Configure build environment
node-gyp configure
# Build extension
node-gyp build
# Rebuild (for development)
node-gyp rebuildCore Functionality Implementation
JavaScript and C++ Interaction
Basic Type Conversion Example:
#include <napi.h>
// JavaScript number -> C++ int
Napi::Number Add(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
// Parameter validation
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsNumber()) {
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
return env.Null();
}
// Type conversion
double arg0 = info[0].As<Napi::Number>().DoubleValue();
double arg1 = info[1].As<Napi::Number>().DoubleValue();
// Compute result
double result = arg0 + arg1;
// Return JavaScript number
return Napi::Number::New(env, result);
}
// Register module
Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "add"),
Napi::Function::New(env, Add));
return exports;
}
NODE_API_MODULE(myaddon, Init)Complex Object Interaction:
#include <napi.h>
#include <vector>
// JavaScript array -> C++ vector
Napi::Array ProcessArray(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsArray()) {
Napi::TypeError::New(env, "Array expected").ThrowAsJavaScriptException();
return env.Null();
}
Napi::Array jsArray = info[0].As<Napi::Array>();
uint32_t length = jsArray.Length();
std::vector<int> cppVector(length);
// Read data from JS array
for (uint32_t i = 0; i < length; i++) {
Napi::Value item = jsArray[i];
if (!item.IsNumber()) {
Napi::TypeError::New(env, "Array elements must be numbers")
.ThrowAsJavaScriptException();
return env.Null();
}
cppVector[i] = item.As<Napi::Number>().Int32Value();
}
// Process data (example: multiply each element by 2)
for (auto& item : cppVector) {
item *= 2;
}
// Create result JS array
Napi::Array result = Napi::Array::New(env, length);
for (uint32_t i = 0; i < length; i++) {
result[i] = Napi::Number::New(env, cppVector[i]);
}
return result;
}Asynchronous Operation Implementation
Asynchronous Work with libuv Example:
#include <napi.h>
#include <uv.h>
struct AsyncData {
Napi::FunctionReference callback;
int input;
int result;
};
// Async work thread execution function
void AsyncWork(uv_work_t* req) {
AsyncData* data = static_cast<AsyncData*>(req->data);
// Simulate time-consuming computation
data->result = data->input * 2;
// Perform real I/O or blocking tasks here
}
// Callback after async work completes
void AsyncAfter(uv_work_t* req, int status) {
Napi::Env env = Env();
AsyncData* data = static_cast<AsyncData*>(req->data);
// Prepare to call JavaScript callback
Napi::HandleScope scope(env);
Napi::Value argv[1] = { Napi::Number::New(env, data->result) };
// Call callback function
data->callback.Call(env.Global(), 1, argv);
// Clean up resources
delete data;
delete req;
}
// Async method exposed to JavaScript
Napi::Value AsyncAdd(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 2 || !info[0].IsNumber() || !info[1].IsFunction()) {
Napi::TypeError::New(env, "Number and callback expected")
.ThrowAsJavaScriptException();
return env.Null();
}
int input = info[0].As<Napi::Number>().Int32Value();
Napi::Function callback = info[1].As<Napi::Function>();
// Create async data
AsyncData* data = new AsyncData;
data->input = input;
data->callback = Napi::Persistent(callback);
// Create uv_work_t request
uv_work_t* req = new uv_work_t;
req->data = data;
// Queue work in libuv
uv_queue_work(uv_default_loop(), req, AsyncWork, AsyncAfter);
// Return undefined (async operation)
return env.Undefined();
}Promise-Based Asynchronous Implementation:
#include <napi.h>
struct PromiseData {
Napi::Promise::Deferred deferred;
int input;
int result;
};
// Async work thread function
void PromiseWork(uv_work_t* req) {
PromiseData* data = static_cast<PromiseData*>(req->data);
data->result = data->input * 3; // Simulate computation
}
// Callback after async completion
void PromiseAfter(uv_work_t* req, int status) {
Napi::Env env = Env();
PromiseData* data = static_cast<PromiseData*>(req->data);
if (status == 0) {
// Resolve Promise successfully
data->deferred.Resolve(Napi::Number::New(env, data->result));
} else {
// Reject Promise
data->deferred.Reject(Napi::Error::New(env, "Async operation failed").Value());
}
delete data;
delete req;
}
// Promise method exposed to JavaScript
Napi::Value PromiseAdd(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
if (info.Length() < 1 || !info[0].IsNumber()) {
Napi::TypeError::New(env, "Number expected").ThrowAsJavaScriptException();
return env.Null();
}
int input = info[0].As<Napi::Number>().Int32Value();
// Create Promise
Napi::Promise::Deferred deferred = Napi::Promise::Deferred::New(env);
// Prepare async data
PromiseData* data = new PromiseData;
data->deferred = deferred;
data->input = input;
// Create work request
uv_work_t* req = new uv_work_t;
req->data = data;
// Queue work
uv_queue_work(uv_default_loop(), req, PromiseWork, PromiseAfter);
// Return Promise object
return deferred.Promise();
}



