本文是对NodeJS C++ Addons原生写法的进一步探索,介绍了利用原生的Node和V8提供的API实现类包装和异步调用的具体做法。在阅读本文之前,如果对NodeJS C++ Addons的基础不熟悉的话,建议先阅读上一篇博客【NodeJS C++ Addons基础】进行了解之后再回来阅读本文。
本文所使用的代码示例可以从该仓库中找到–【cpp-addons】
备注: 本文旨在探究NodeJS C++ Addons的原生写法,了解部分底层知识,所使用的NodeJS版本为8.11.1,由于V8原生的API会发生变动,不同版本的NodeJS的支持情况可能不同。因此不保证代码能兼容所有版本的NodeJS。
一、C++类和对象实例的包装
NodeJS C++插件除了可以向JavaScript提供函数接口之外,还可以将一些C++类或者C++对象实例包装后直接提供给JavaScript使用。举个例子,假设有个用C++实现的类,类名为SomeClass
,现在想要在JavaScript中直接使用该类,通过new SomeClass(...)
直接创建该类的实例并进行使用。
接下来将使用一个简单的例子来说明如何进行C++类和对象的包装。在这个例子中,将实现一个C++类Accumulator
,该类是一个累加器,提供add()
和getAddTimes()
两个方法,add()
方法用于将参数累加并返回当前的累加值,getAddTimes()
则是返回当前的累加次数。在创建Accumulator
实例的时候,可以指定累加开始的初始值。最后,我们期望实现的效果如下,可以在JavaScript中使用这个C++类并创建该类的实例,并且可以调用该类上定义的方法。
// cpp-object-wrap demo
const AccumulatorModule = require('./build/Release/Accumulator')
let acc = new AccumulatorModule.Accumulator(2)
console.log('[ObjectWrapDemo] 2 + 12 = ' + acc.add(12))
console.log('[ObjectWrapDemo] 2 + 12 + 5 = ' + acc.add(5))
console.log('[ObjectWrapDemo] add times: ' + acc.getAddTimes())
在C++中,Accumulator
是一个通过class
关键字定义的普通类,而在JavaScript中,一个类即为一个JS函数。在C++中,Accumulator
的实例是一个普通的C++类实例,在JavaScript中,一个实例即为一个JS对象。JS函数在V8中对应的是一个v8::Function
实例,JS对象在V8中对应的是一个v8::Object
实例,因此,包装要做的事情,便是将一个C++类
包装成一个v8::Function
实例,将一个C++实例对象
包装成一个v8::Object
实例,然后提供给JavaScript使用。下面是该C++插件的实现源码,注释包含了对包装过程的介绍。
#include <node.h>
#include <node_object_wrap.h>
namespace CppObjectWrapDemo {
using v8::Context;
using v8::Function;
using v8::FunctionCallbackInfo;
using v8::FunctionTemplate;
using v8::Isolate;
using v8::Local;
using v8::Number;
using v8::Object;
using v8::Persistent;
using v8::String;
using v8::Value;
using v8::Exception;
/* 将C++类封装给JS使用,需要继承node::ObjectWrap */
class Accumulator : public node::ObjectWrap {
public:
/* 初始化该类的JS构造函数,并返回JS构造函数 */
static Local<Function> init (Isolate* isolate) {
/* 利用函数模板,将一个C++函数包装成JS函数 */
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, AccumulatorJS);
tpl->SetClassName(String::NewFromUtf8(isolate, "Accumulator"));
tpl->InstanceTemplate()->SetInternalFieldCount(1);
/* 类方法定义在构造函数的prototype上 */
NODE_SET_PROTOTYPE_METHOD(tpl, "add", add);
NODE_SET_PROTOTYPE_METHOD(tpl, "getAddTimes", getAddTimes);
/* 获取Accumulator类的JS构造函数 */
Local<Function> fn = tpl->GetFunction();
/* JS构造函数句柄存储于constructor上,后续还会使用到 */
constructor.Reset(isolate, fn);
return fn;
}
private:
/* 成员变量 */
static Persistent<Function> constructor;
double value;
int addTimes;
/* 该类的C++构造函数,设置成员变量初始值 */
explicit Accumulator (double initValue = 0) {
this->value = initValue;
this->addTimes = 0;
}
/* 该类的JS构造函数,创建该类的对象,并包装成JS对象然后进行返回 */
static void AccumulatorJS (const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = args.GetIsolate();
if (args.IsConstructCall()) {
/* 通过 new Accumulator() 创建对象 */
/* 提取参数数值 */
double val = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
/* 创建该类的实例对象 */
Accumulator*