Node C++扩展教程1:实现hello world
1.引言
nodejs为我们提供了丰富的API,使我们能够轻松应对多种场景。
但是太浮华的东西总感觉不踏实,尤其在nodejs面对系统硬件资源的时候。
当我们想要访问串口,i2c,spi等硬件资源时,只能去借助前人开发的库。
nodejs的使用者中,前端工程师占据了一大半。他们根本不关心硬件资源,从而导致这方面的库比较少。
难道我们就此放弃,又回到c语言刀耕火种的年代吗?或者另投其他门派,自废武功?
与其浅尝辄止,不如深入研究,多多压榨nodejs的剩余价值。
那么,让我们练习用c++为nodejs写插件吧。nodejs无法搞定的时候,就祭出c++神器。
2.c++扩展的前世今生
原生API开发:
在早期的时候,Node.js 原生 C++ 模块开发方式是非常暴力的,直接使用其提供的原生模块开发头文件。开发者直接深入到 Node.js 的各种 API,以及 Google V8 的 API。
弊端:
Node.js 原生模块开发的时候,一个版本只能支持特定几个版本的 Node.js,一旦 Node.js 的底层 API 以及 Google V8 的 API 发生变化,而这些原生模块又依赖了变化了的 API 的话,包就作废了。除非包的维护者去支持新版的 API,不过这样依赖,老版 Node.js 下就又无法编译通过新版的包了。
NAN API开发:
它全称 Native Abstractions for Node.js,即 Node.js 原生模块抽象接口。
NAN会判断当前编译时候的 Node.js 版本,自动适配不同版本的 Node.js 来展开不同的结果。我们扔按照原方式使用NAN API,而不用关心底层API更新的内容。
这样做的好处就是,代码只需要随着 NAN 的升级做改变就好,它会帮你兼容各不同 Node.js 版本,使其在任意版本都能被编译使用。
符合 ABI 的 N-API:
自从 Node.js v8.0.0 发布之后,Node.js 推出了全新的用于开发 C++ 原生模块的接口,N-API。
N-API 相较于 NAPI 来说,它把 Node.js 的所有底层数据结构全部黑盒化,抽象成 N-API 当中的接口。
不同版本的 Node.js 使用同样的接口,这些接口是稳定地 ABI 化的,即应用二进制接口(Application Binary Interface)。这使得在不同 Node.js 下,只要 ABI 的版本号一致,编译好的 C++ 扩展就可以直接使用,而不需要重新编译。事实上,在支持 N-API 接口的 Node.js 中,的确就指定了当前 Node.js 所使用的 ABI 版本。
为了使得以后的 C++ 扩展开发、维护更方便,N-API 致力于以下的几个目标:
-
以 C 的风格提供稳定 ABI 接口;
-
消除 Node.js 版本的差异;
-
消除 JavaScript 引擎的差异(如 Google V8、Microsoft ChakraCore 等)。
而这些 API 主要就是用来创建和操作 JavaScript 的值了,我们就再也不用直接使用 Google V8 提供的数据类型了。毕竟在 NAN 中,就算我们有时候看不到 Google V8 的影子,实际上在宏展开后还是无数的 Google V8 数据结构。
为了达成上述隐藏的目标,N-API 的姿势就变成了这样:
-
提供头文件 node_api.h;
-
任何 N-API 调用都返回一个
napi_status
枚举,来表示这次调用成功与否; -
N-API 的返回值由于被
napi_status
占坑了,所以真实返回值由传入的参数来继承,如传入一个指针让函数操作; -
所有 JavaScript 数据类型都被黑盒类型
napi_value
封装,不再是类似于v8::Object
、v8::Number
等类型; -
如果函数调用不成功,可以通过
napi_get_last_error_info
函数来获取最后一次出错的信息。
3.N-API详细介绍
关于N-API的详细介绍可以查阅如下网址:
4.基于N-API实现的Hello World
//引入头文件 #include <node_api.h> #include <iostream> using namespace std; //定义SayHello方法 napi_value SayHello(napi_env env,napi_callback_info info){ cout << "hello world" << endl; return NULL; } //初始化object exports,将需要导出的方法或者属性挂载到object exports上 napi_value Init(napi_env env, napi_value exports){ napi_status status; napi_value method; status = napi_create_function(env,"exports",NAPI_AUTO_LENGTH,SayHello,NULL,&method); if(status!= napi_ok){ return NULL; } status = napi_set_named_property(env,exports,"sayHello",method); if(status != napi_ok){ return NULL; } return exports; } // 注册当前module NAPI_MODULE(NODE_GYP_MODULE_NAME,Init)
使用方法类似nodejs中的模块导出方式:
module.exports={ //添加属性 }
完整的实现代码可以到github拉取: