napi cmake 编译node包
使用napi提供的方法把cmake c++项目打成node包,提供给javaScript用require引用
环境准备
第一步: 下载cmake-js
npm i cmake-js -g
第二步: 改造CMakeLists.txt文件
cmake_minimum_required(VERSION 3.9)
# 0042索引对应的策略使用新策略,具体策略未知
cmake_policy(SET CMP0042 NEW)
set (CMAKE_CXX_STANDARD 11)
project (build-node-addon-api-with-cmake)
# conan相关, 原项目的内容,不必理会, 如果不用conan,请删除下面两行
include(${CMAKE_BINARY_DIR}/conanbuildinfo.cmake)
conan_basic_setup()
# 使用utf-8解析代码, 原项目的内容
if(MSVC)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} /EHsc /utf-8")
endif()
# napi 头文件目录
include_directories(${CMAKE_JS_INC})
aux_source_directory(${CMAKE_CURRENT_SOURCE_DIR} SRC)
# 生成库时增加了cmake-js的东西
add_library(${PROJECT_NAME} SHARED ${SRC} ${CMAKE_JS_SRC})
# SUFFIX 导入.node后缀的库,而不仅仅是.dll
# PREFIX 导入库的前缀,而不仅仅是lib
set_target_properties(${PROJECT_NAME} PROPERTIES PREFIX "" SUFFIX ".node")
# 链接cmake-js的库和conan的库,如果不用conan不要加 ${CONAN_LIBS}
target_link_libraries(${PROJECT_NAME} ${CMAKE_JS_LIB} ${CONAN_LIBS})
# 打印命令结果到变量中,命令会返回addon的c头文件目录
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指定头文件发现目录
target_include_directories(${PROJECT_NAME} PRIVATE ${NODE_ADDON_API_DIR})
# define NPI_VERSION
add_definitions(-DNAPI_VERSION=3)
第三步: 增加package.json文件
{
"name": "build-node-addon-api-with-cmake",
"version": "0.0.0",
"description": "Build N-API native addon with CMake and node-addon-api C++ wrapper.",
"main": "hello.js",
"private": true,
"dependencies": {
"bindings": "~1.2.1",
"node-addon-api": "^1.0.0"
},
"scripts": {
"install": "cmake-js compile",
"test": "node hello.js"
}
}
第四步: 增加给javaScript调用的接口
#include <napi.h>
#include "test.h"
static Napi::String MMethod(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
return Napi::String::New(env, PrintHello());
}
static Napi::Boolean GetBool(const Napi::CallbackInfo& info) {
Napi::Env env = info.Env();
Compair(0,0);
if (info.Length() < 2) {
Napi::TypeError::New(env, "Wrong number of arguments")
.ThrowAsJavaScriptException();
return Napi::Boolean::New(env, Compair(0,2));
}
std::string arg0 = info[0].As<Napi::String>();
std::string arg1 = info[1].As<Napi::String>();
return Napi::Boolean::New(env, Compair(arg0,arg1));
}
static Napi::Object Init(Napi::Env env, Napi::Object exports) {
exports.Set(Napi::String::New(env, "gethello"),
Napi::Function::New(env, MMethod));
exports.Set(Napi::String::New(env, "getbool"),
Napi::Function::New(env, GetBool));
return exports;
}
NODE_API_MODULE(addon, Init)
其中CNODE_API_MODULE(addon, Init)
这一行, 第一个参数可以随便写,应该是暴露出去的类名, 第二个和Init函数同名, Init函数中调用exports.Set
可以把想要暴露的接口和变量注册进去。
前面都是函数的定义:
第一个
MMethod
函数,接收0个参数,返回一个字符串,这个字符串是通过test.h中的c++函数PrintHello()
生成的。
第二个函数GetBool
接收两个字符串参数,如果参数不足2个,会向javaScript抛出一个异常,如果参数满足条件,会调用c++的Compair
返回一个bool值。
需要注意的是,在c++函数中获取javaScript参数的方法:
如果是字符串, 直接使用
std::string arg0 = info[0].As<Napi::String>()
获取就可以了。
但是如果是其他类型,如double, 就需要用下面的方式获取double arg0 = info[0].As<Napi::Number>().DoubleValue()
获取数字类型,有两个步骤,第一个是获取到
Napi::Number
类型,然后在调用DoubleValue()转换为c++的double类型。Napi::Number
定义如下:/// A JavaScript number value. class Number : public Value { public: static Number New( napi_env env, ///< N-API environment double value ///< Number value ); Number(); ///< Creates a new _empty_ Number instance. Number(napi_env env, napi_value value); ///< Wraps a N-API value primitive. operator int32_t() const; ///< Converts a Number value to a 32-bit signed integer value. operator uint32_t() const; ///< Converts a Number value to a 32-bit unsigned integer value. operator int64_t() const; ///< Converts a Number value to a 64-bit signed integer value. operator float() const; ///< Converts a Number value to a 32-bit floating-point value. operator double() const; ///< Converts a Number value to a 64-bit floating-point value. int32_t Int32Value() const; ///< Converts a Number value to a 32-bit signed integer value. uint32_t Uint32Value() const; ///< Converts a Number value to a 32-bit unsigned integer value. int64_t Int64Value() const; ///< Converts a Number value to a 64-bit signed integer value. float FloatValue() const; ///< Converts a Number value to a 32-bit floating-point value. double DoubleValue() const; ///< Converts a Number value to a 64-bit floating-point value. };
还有一些特殊的参数,比如数组,对象,函数。数组和对象这里不做过多介绍,函数参数可以实现c++的回调函数,可以达到c++调用javaScript的目的,这里简单讲一下用法。
c++文件:
//这个函数接收一个javaScript的函数,参数,并且立即调用, void RunCallback(const Napi::CallbackInfo& info) { Napi::Env env = info.Env(); Napi::Function cb = info[0].As<Napi::Function>(); cb.Call(env.Global(), {Napi::String::New(env, "hello world callback")}); }
javascript文件:
addon.runCallback(function(msg){ console.log(msg); });
第五步: 写一个hell.js测试
var addon = require('./build/lib/build-node-addon-api-with-cmake');
console.log(addon.gethello()); // 'world'
console.log(addon.getbool('111', '111')); // 'world'
其中require的路径,就看你产物的目录了,你可以把产物迁移到其他目录
第六步: 编译运行
npm i // 下载package.josn中声明的js依赖
cmake-js build //构建, 或者执行 npm run install
node hello.js //运行js测试代码, 或者执行 npm run test
构建输出:
➜ cpp_to_nodejs git:(master) ✗ npm run install [20/463]
> build-node-addon-api-with-cmake@0.0.0 install /home/打码/work/cpp_to_nodejs
> cmake-js compile
[
'/node/v14.5.0/bin/node',
'/home/打码/.nvm/versions/node/v14.5.0/bin/cmake-js',
'compile'
]
info TOOL Using Unix Makefiles generator.
info CMD CONFIGURE
info RUN cmake "/home/打码/work/cpp_to_nodejs" --no-warn-unused-cli -G"Unix Makefiles" -DCMAKE_JS_VERSION="6.1.0" -DCMAKE_BUILD_TYPE="Release" -DC
MAKE_LIBRARY_OUTPUT_DIRECTORY="/home/打码/work/cpp_to_nodejs/build/Release" -DCMAKE_JS_INC="/home/打码/.cmake-js/node-x64/v14.5.0/include/node" -DC
MAKE_JS_SRC="" -DNODE_RUNTIME="node" -DNODE_RUNTIMEVERSION="14.5.0" -DNODE_ARCH="x64"
Not searching for unused variables given on the command line.
-- The C compiler identification is GNU 10.2.0
-- The CXX compiler identification is GNU 10.2.0
-- Detecting C compiler ABI info
-- Detecting C compiler ABI info - done
-- Check for working C compiler: /usr/bin/cc - skipped
-- Detecting C compile features
-- Detecting C compile features - done
-- Detecting CXX compiler ABI info
-- Detecting CXX compiler ABI info - done
-- Check for working CXX compiler: /usr/bin/c++ - skipped
-- Detecting CXX compile features
-- Detecting CXX compile features - done
-- Conan: Adjusting output directories
-- Conan: Using cmake global configuration
-- Conan: Adjusting default RPATHs Conan policies
-- Conan: Adjusting language standard
-- Current conanbuildinfo.cmake directory: /home/打码/work/cpp_to_nodejs/build
-- Conan: Compiler GCC>=5, checking major version 10
-- Conan: Checking correct version: 10
-- Configuring done
-- Generating done
-- Build files have been written to: /home/打码/work/cpp_to_nodejs/build
info CMD BUILD
info RUN cmake --build "/home/打码/work/cpp_to_nodejs/build" --config Release
Scanning dependencies of target build-node-addon-api-with-cmake
[ 33%] Building CXX object CMakeFiles/build-node-addon-api-with-cmake.dir/src/test.cpp.o
[ 66%] Building CXX object CMakeFiles/build-node-addon-api-with-cmake.dir/src/test_node_api.cpp.o
[100%] Linking CXX shared library lib/build-node-addon-api-with-cmake.node
[100%] Built target build-node-addon-api-with-cmake
测试demo输出:
➜ cpp_to_nodejs git:(master) ✗ npm run test
> build-node-addon-api-with-cmake@0.0.0 test /home/打码/work/cpp_to_nodejs
> node hello.js
11:05:11.194 [thread 47105][info] [test::main][PrintHello:14]: logger sink flags: 1
11:05:11.194 [thread 47105][info] [test::main][PrintHello:15]: echo hello world!
11:05:11.194 [thread 47105][info] [test::main][PrintHello:16]: json string {"pi":3.14}
hello world!
11:05:11.199 [thread 47105][info] [test::main][Compair:28]: arg1: 0.0
11:05:11.199 [thread 47105][info] [test::main][Compair:29]: arg2: 0.0
11:05:11.199 [thread 47105][info] [test::main][Compair:22]: arg1: 111
11:05:11.199 [thread 47105][info] [test::main][Compair:23]: arg2: 111
true
输出的带时间戳的日志是c++打印的, hello world!
和true
是js打印的。
总结
总体来看,还是比较简单的,比其使用x86架构交叉编译arm架构,或者交叉编译安卓,这种编译node的包的方法会简单很多。