C++ Native Addon independent of Node.js version using Napi/node-addon-api and Cmake
This is a tutorial for c++ Node-addon-api / Napi addon using cmake.
Napi makes it independent of node.js version, means our addon will be compatible with all future version of node.js .
Cmake is a cross-platform code compiling tool for c++ code this makes us independent of complex node-gyp process.
…
Requirements :-
- install Node.js > 10.x
Note:- Remember to check automatically install the necessary tools.
(this will be required to install vs-build tool on windows )
2. install Cmake > 3.15
…
Overview -
- Napi addon require four major part
- package.json file- to handle dependencies, names, version, installation commands etc.
- CMakeLists.txt file- commands needed to compile c++ code.
- A node script - to use native addon.
- C++ source files
2. Addon will be OS dependent, so if you want to make it cross-platform compatible. Then it will be needed to built(once) on all platforms separately.
…
…
Node.js app (package.json)
- Make a directory for your project and open terminal/power-shell in it.
- Since node.js is installed already we will create a project(package.json) using npm.
npm init
This will ask you package name, version, description, etc.
Enter accordingly or just press enter until it’s done with defaults.
…
…
Install npm Packages
- install node-addon-api / napi package for node.js addon
npm install node-addon-api --save
2. install cmake.js package for Cmake handling
npm install cmake-js --save-dev
…
…
C++ source code
We will be using a simple program which takes two argument and print their sum.
(If you want to run multi-threaded code with filesystem head over to end of article)
- Now lets use Napi syntex to export this c++ function to node-js.
- Create a source directory for your c++ files named
src
- Create a c++ header file in that directory named as
src/example.h
and add below code in it.
#include <napi.h>
#include <iostream>using namespace std;namespace example{
//add number function
int add(int x, int y);//add function wrapper
Napi::Number addWrapped(const Napi::CallbackInfo& info); //Export API
Napi::Object Init(Napi::Env env, Napi::Object exports);
NODE_API_MODULE(addon, Init)
}
- napi.h is a header file for node-addon-api .
- Then we are declaring c++ function and Its wrapper which will call c++ function for us whenever wrapper will be called from node-js.
- Last two line export functions name to nodejs and register addon in nodejs.
3. Create a c++ source file in src directory named as src/example.cpp
and add below code in it.
#include "example.h"using namespace std;int example::add(int x, int y){
return (x+y);
}Napi::Number example::addWrapped(const Napi::CallbackInfo& info){
Napi::Env env = info.Env(); //check if arguments are integer only.
if(info.Length()<2 || !info[0].IsNumber() || !info[1].IsNumber()){
Napi::TypeError::New(env, "arg1::Number, arg2::Number expected").ThrowAsJavaScriptException();
} //convert javascripts datatype to c++
Napi::Number first = info[0].As<Napi::Number>();
Napi::Number second = info[1].As<Napi::Number>();//run c++ function return value and return it in javascript
Napi::Number returnValue = Napi::Number::New(env, example::add(first.Int32Value(), second.Int32Value()));
return returnValue;
}Napi::Object example::Init(Napi::Env env, Napi::Object exports)
{
//export Napi function
exports.Set("add", Napi::Function::New(env, example::addWrapped)); return exports;
}
- Call example.h header file.
- write definition of c++ function.
- write definition of wrapper validate arguments and convert them to c++ datatype.
- wrapper will call c++ function with argument and return result.
- last function export wrapper function to node-js.
…
…
CMake configuration (CMakeLists.txt)
CMakeLists.txt: this file will contain all configuration and command required by cmake to compile c++ code.
- Create a file in project directory named as
CMakeLists.txt
and add below code in it.
cmake_minimum_required(VERSION 3.15)# Name of the project (will be the name of the plugin)
project (addon)set(CMAKE_CXX_STANDARD 17)
# Don't add this line if you will try_compile with boost.
set(CMAKE_CXX_STANDARD_REQUIRED ON)# Essential include files to build a node addon,
# you should add this line in every CMake.js based project.
include_directories(${CMAKE_JS_INC})# Declare the location of the source files
file(GLOB SOURCE_FILES "src/*.cpp" "src/*.h")# This line will tell CMake that we're building a shared library
# from the above source files
# named after the project's name
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES} ${CMAKE_JS_SRC})# This line will give our library file a .node extension without any "lib" prefix
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")# Essential library files to link to a node addon,
# you should add this line in every CMake.js based project.
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB})# Include N-API wrappers
execute_process(COMMAND node -p "require('node-addon-api').include"
WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}
OUTPUT_VARIABLE NODE_ADDON_API_DIR
)
string(REPLACE "\n" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
string(REPLACE "\"" "" NODE_ADDON_API_DIR ${NODE_ADDON_API_DIR})
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})add_definitions(-DNAPI_VERSION=3)
- require cmake minimum version.
- set c++ version (in our case it is c++ 17).
- then some boilerplate to include source files and libraries.
- set node-js libraries specially napi.
- add napi version
…
…
Configure package.json for cmake.js
- Add install command in Script field like below code
"scripts": {
"install": "cmake-js compile"
},
- This will trigger Cmake to compile c++ code using CMakeLists.txt file configuration.
- Optionally you can pass architecture ia32 or x64 like this:
"scripts": {
"preinstall": "npm config set cmake_js_arch ia32",
"install": "cmake-js compile"
},
…
…
Node script to call c++ functions
Lastly call c++ function in a node script with argument
- Create a index.js file in project directory and add below code in it.
const {add} = require("./build/Release/addon.node");console.log(add(2, 3));
- Here we are importing addon as a node_module and then call our C++ function form it.
…
…
Install and Run
- execute both commands from project directory
npm install
node index
- First command will compile all c++ files one by one and generates a .node file in build folder.
- Second command will execute node script, which calls c++ function and print output on console.
2. Output should look like this:
atiq@edit02 napi_example % npm install
>cmake-js compile
[
'/usr/local/bin/node',
'/Users/atiq/Desktop/napi_example/node_modules/.bin/cmake-js',
'compile',
'--arch',
'x64'
]
...
...
...
...
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++
-- Check for working CXX compiler: /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/c++ - works
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
...
...
...
Scanning dependencies of target addon
[ 50%] Building CXX object CMakeFiles/addon.dir/src/example.cpp.o
[100%] Linking CXX shared library Release/addon.node
[100%] Built target addon
npm WARN test@1.0.0 No description
npm WARN test@1.0.0 No repository field.
audited 139 packages in 2.236satiq@edit02 napi_example % node index
5
…
…
Code Repository
You can access complete code of this project here.
…
Support my work
Additional Possibilities
- You can call any C++ header/source file in above c++ code and call their functions.
- Multi-thread code can be called in these c++ functions but exception handling must be handled in some way.
- Napi has a lot of examples for different concept of c++.
- Native addon can be used with electron, nw.js with two three lines of code.
I have another tutorial for using c++ native addon in electron.js. - You can ask any question in comments below or find me on twitter.