NodeJS C++插件是一种动态链接库,采用C/C++语言编写,可以通过require()
将插件加载进NodeJS中进行使用。利用V8提供的API,可以实现JavaScript和C++的互相调用,打通JavaScript和C++之间的接口。在做一些高性能或者底层模块的时候,需要用到一些C++库,NodeJS C++插件可以帮助我们封装这些C++库的接口,使得JavaScript具备调用C++库的能力。本文将记录利用基础的V8 API编写NodeJS C++插件的过程,实现C++和JavaScript之间的参数传递、函数调用以及回调、异常处理以及对象函数传递等功能。记录过程中也会对部分概念和API进行阐述。
本文所使用的代码示例可以从该仓库中找到–【cpp-addons】
备注: 本文旨在探究NodeJS C++ Addons的原生写法,了解部分底层知识,所使用的NodeJS版本为8.11.1,由于V8原生的API会发生变动,不同版本的NodeJS的支持情况可能不同。因此不保证代码能兼容所有版本的NodeJS。
一、基本概念
1.1、hello world示例
首先通过一个简单的HelloWorld示例来了解编写C++插件的基本写法和一些API的基本概念。在示例中,C++模块向JavaScript暴露了一个hello
接口,在JavaScript中调用该接口后会得到返回值hello world
。
#include <node.h>
namespace HelloWorldDemo {
using v8::FunctionCallbackInfo;
using v8::Isolate;
using v8::Local;
using v8::Object;
using v8::String;
using v8::Value;
void hello (const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
/* 通过 FunctionCallbackInfo<Value>& args 可以设置返回值 */
args.GetReturnValue().Set(String::NewFromUtf8(isolate, "hello world."));
}
void init (Local<Object> exports) {
/* 设置模块的导出方法 hello */
/* 等价于 js 模块中的 module.exports.hello = hello */
NODE_SET_METHOD(exports, "hello", hello);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, init)
}
JavaScript调用C++模块的方法时,会传递一个V8对象,类型为FunctionCallbackInfo<Value>
。通过这个V8对象,JavaScript可以向C++接口传递参数,C++函数也可以通过这个对象来向JavaScript回传信息,即设置返回值。在C++接口中,通过参数const FunctionCallbackInfo<Value>& args
可以拿到一个Isolate
对象,Isolate
代表一个V8虚拟机实例。通过args.GetIsolate()
可以获取到运行JavaScript调用者的V8虚拟机实例。这个V8实例包含了内存堆,在C++接口中创建V8提供的JavaScript对象类型实例的时候会使用到。例如前面的hello world例子中,在创建一个JS字符串的时候需要传递isolate
对象,表示在该V8虚拟机上创建了一个JS字符串对象,之后该字符串便可以被V8虚拟机上运行的JS调用者所使用。
Local
是一个模板句柄类,Local<SomeType>
代表指向某种类型的句柄。例如模块的exports
属性是一个JavaScript对象,句柄类型为Local<Object>
。传递给init
函数的参数其实是指向相应对象的句柄。
NODE_MODULE
是一个宏,设置模块初始化函数为init
。init
函数中执行模块的初始化,当模块第一次被加载进NodeJS应用中的时候就会执行init
函数,init
函数中可以设置exports
属性将C++接口暴露出去给JavaScript使用。NODE_SET_METHOD
用于设置属性或方法,第二个参数为属性名,第三个参数为方法对应的属性值。如果需要给exports
对象设置多个属性或方法,可以调用多次NODE_SET_METHOD
。exports
对象上设置的属性方法将会作为接口暴露给外部使用。
编写NodeJS C++插件必须遵循以下这种模式:必须有一个初始化函数对模块进行初始化(设置方法属性等),然后加上NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize)
设置模块名和初始化函数。初始化函数可以有两种写法,第一种写法常用于设置模块的exports
对象上的某个属性或方法,第二种写法可用于直接重写整个exports
对象。
// 写法1
void Initialize_1(Local<Object> exports) {
// 进行初始化...
// example
// 等价于js模块中的 module.exports.hello = hello
NODE_SET_METHOD(exports, "hello", hello);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize_1)
// 写法2
void Initialize_2(Local<Object> exports, Local<Object> module) {
// 进行初始化...
// example
// 等价于js模块中的 module.exports = hello
NODE_SET_METHOD(module, "exports", hello);
}
NODE_MODULE(NODE_GYP_MODULE_NAME, Initialize_2)
1.2、构建方法
编写完C++代码后需要将其编译构建成node文件才能够被NodeJS使用。利用node-gyp
可以很方便地进行构建。首先在C++代码文件的根目录下创建一个binding.gyp
文件,在文件中写入类似下面的J