标签:node.js v8 c/c++
占坑先。最近在看node的源码,心血来潮,整理一下Node.js调用C++的方法。
ps:简书不支持页内跳转,略坑。
<h2 id="catalog">目录</h2>
Node.js是在V8引擎基础上,做了封装和改进,让JavaScript能够运行在服务端。
JavaScript的基本类型、函数、对象都在V8中实现了。如果要改写自己的某些模块为C/C++或封装的C/C++库以便Node.js调用,需要先了解V8的concept、structure和api(戳我)。
Let us begin!
<h2 id="install">安装</h2>
install node-gyp with
npm install node-gyp -g
具体安装需要的步骤可以参见其github主页。
- On Unix:
-
python(v2.7recommended,v3.x.xis not supported) make- A proper C/C++ compiler toolchain, like
GCC - On Mac OSX:
-
python(as Unix) (already installed on Mac OSX, if you installv3.x.xreplaced, you can install withnpm install node-gyp -g --python=python2.7 -
Xcode(easily installed) - Windows (never mind) (/= _ =)/~┴┴
<h2 id="hello_world">一个简单的例子</h2>
从一个简单的例子开始,说明JavaScript调用C/C++的流程。示例函数包含3部分:
- 配置文件
- js文件
- cc文件
配置文件
binding.gyp(一定要叫这个名字 (/= _ =)/~┴┴)
简单的配置如下:
{
"targets": [
{
"target_name": "test",
"sources": [ "test.cc" ]
}
]
}
详细的方式(戳我)。
js文件
test.js,hello接受两个参数,一个字符串,一个回调函数:
var test = require('./build/Release/test');
test.hello('test', function(data) {
console.log(data);
});
cc文件
test.cc,文件应用两个头部node.h和v8.h,整体结构和node的module非常相似,都是先定义Function/Object,然后export:
#include <node.h>
#include <v8.h>
using namespace v8;
// 传入了两个参数,args[0] 字符串,args[1] 回调函数
void hello(const FunctionCallbackInfo<Value>& args) {
// 使用 HandleScope 来管理生命周期
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// 判断参数格式和格式
if (args.Length() < 2 || !args[0]->IsString()) {
isolate->ThrowException(Exception::TypeError(
String::NewFromUtf8(isolate, "Wrong arguments")));
return;
}
// callback, 使用Cast方法来转换
Local<Function> callback = Local<Function>::Cast(args[1]);
Local<Value> argv[1] = {
// 拼接String
String::Concat(Local<String>::Cast(args[0]), String::NewFromUtf8(isolate, " world"))
};
// 调用回调, 参数: 当前上下文,参数个数,参数列表
callback->Call(isolate->GetCurrentContext()->Global(), 1, argv);
}
// 相当于在 exports 对象中添加 { hello: hello }
void init(Handle<Object> exports) {
NODE_SET_METHOD(exports, "hello", hello);
}
// 将 export 对象暴露出去
// 原型 `NODE_MODULE(module_name, Initialize)`
NODE_MODULE(test, init);
编译和使用
在项目根目录下使用:
node-gyp configure
node-gyp build
在mac上,如果python版本不对,可以带上--python=python2.7。
Hello World讲解完毕,下面整理下V8的相关知识。
<h2 id="v8">Google V8引擎</h2>
JavaScript的所有基本对象都在v8.h中定义了,找不到api的时候直接看源码即可。
<h3 id="v8_value">Value</h3>
Data是JavaScript中各种结构的基础,Value是JavaScript中各种对象的基类。
JavaScript 类型在 C++ 中均有对应的自定义类型,如 String、Object 、Date 、Array 等,严格遵守在 JavaScript 中的继承关系。

<h3 id="v8_handle">Handle</h3>
V8里使用Handle类型来托管 JavaScript 对象,与C++的std::sharedpointer类似,Handle类型间的赋值均是直接传递对象引用,但不同的是,V8使用自己的GC 来管理对象生命周期,而不是智能指针常用的引用计数。
在C++中使用这些类型时,必须使用Handle 托管,以使用GC来管理它们的生命周期,而不使用原生栈和堆。

Handle分为Local和Presistent。Local可以被HandleScope管理。
Presistent相当于全局,不受HandleScope管理,需要Persistent::New/Persistent::Dispose配对使用,相当于C++的new/delete。
Persistent::MakeWeak:如果一个对象的唯一引用Handle是一个Persistent,则可以使用MakeWeak方法来如果该引用,该方法可以触发GC对被引用对象的回收。
<h3 id="v8_handle_scope">HandleScope</h3>
HandleScope则相当于用来装Handle(Local)的容器,当HandleScope生命周期结束的时候,Handle也将会被释放,会引起Heap中对象引用的更新。
HandleScope是分配在栈上,不能通过New的方式进行创建。对于同一个作用域内可以有多个HandleScope,新的HandleScope将会覆盖上一个HandleScope,并对Handle(Local)进行管理。
所以在编写C++函数时,开头都是固定的写法:
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
<h3 id="v8_context">Context</h3>
Context是JavaScript的执行环境。每个JavaScript都必须执行在一个Context中。Context有多个,而且可以在不同的Context中进行切换。
在Hello中,我们使用了isolate->GetCurrentContext()来获取上下文。
或者可以这样新建一个Context:
// 申请一个context
Persistent<Context> context = Context::New();
// 切换
Context::Scope context_scope(context);
下面这张图就讲解的比较清晰:

<h2 id="practice">Practice</h2>
在Hello World中,展示了参数args、callback、ThrowException的用法。下面来讲一下其他例子。
官方教程中有很多例子,可以看看。
<h3 id="cc_class">Class</h3>
下面通过一个例子说明C++类的使用。
binding.gyp
{
"targets": [
{
"target_name": "addon",
"sources": ["addon.cc", "point.cc"]
}
]
}
addon.cc
addon.cc,这个是module.exports:
#include <node.h>
#include "point.h"
using namespace v8;
void InitAll(Handle<Object> exports) {
Point::Init(exports);
}
NODE_MODULE(addon, InitAll);
C++ 头文件
定义一个Point类:
#ifndef POINT_H
#define POINT_H
#include <node.h>
#include <node_object_wrap.h>
// 继承node的ObjectWrap,一般自定义C++类都应该继承node的ObjectWrap
class Point : public node::ObjectWrap {
public:
// 静态方法,用于注册类和方法
static void Init(v8::Handle<v8::Object> exports);
private:
// 不允许转换构造函数进行的隐式转换
explicit Point(double _x = 0, double _y = 0);
~Point();
// new方法
static void New(const v8::FunctionCallbackInfo<v8::Value>& args);
// move(x, y)
static void Move(const v8::FunctionCallbackInfo<v8::Value>& args);
// toString方法
static void toString(const v8::FunctionCallbackInfo<v8::Value>& args);
static v8::Persistent<v8::Function> constructor;
// fields
double _x;
double _y;
};
#endif
C++ 文件
point.cc,具体的Point实现如下:
#include <iostream>
#include <sstream>
#include <string>
#include "point.h"
using namespace std;
using namespace v8;
Persistent<Function> Point::constructor;
// 构造函数
Point::Point(double _x, double _y) {
this->_x = _x;
this->_y = _y;
}
Point::~Point() {
}
void Point::Init(Handle<Object> exports) {
Isolate* isolate = Isolate::GetCurrent();
// Function模板
Local<FunctionTemplate> tpl = FunctionTemplate::New(isolate, New);
// 类名
tpl->SetClassName(String::NewFromUtf8(isolate, "Point"));
// InternalField
tpl->InstanceTemplate()->SetInternalFieldCount(1);
// 设置Prototype函数
NODE_SET_PROTOTYPE_METHOD(tpl, "move", Move);
NODE_SET_PROTOTYPE_METHOD(tpl, "toString", toString);
// 设置constructor
constructor.Reset(isolate, tpl->GetFunction());
// export `Point`
exports->Set(String::NewFromUtf8(isolate, "Point"),
tpl->GetFunction());
}
void Point::New(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
if (args.IsConstructCall()) {
// 使用new调用 `new Point(...)`
double _x = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
double _y = args[1]->IsUndefined() ? 0 : args[1]->NumberValue();
// new一个对象
Point* point = new Point(_x, _y);
// 包装this指针
point->Wrap(args.This());
args.GetReturnValue().Set(args.This());
} else {
// 使用`Point(...)`
const int argc = 2;
Local<Value> argv[argc] = { args[0], args[1] };
// 使用constructor构建Function
Local<Function> cons = Local<Function>::New(isolate, constructor);
args.GetReturnValue().Set(cons->NewInstance(argc, argv));
}
}
void Point::Move(const FunctionCallbackInfo<Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// 解封this指针
Point* point = ObjectWrap::Unwrap<Point>(args.Holder());
// 参数
double _x = args[0]->IsUndefined() ? 0 : args[0]->NumberValue();
double _y = args[1]->IsUndefined() ? 0 : args[1]->NumberValue();
// move
point->_x += _x;
point->_y += _y;
}
void Point::toString(const v8::FunctionCallbackInfo<v8::Value>& args) {
Isolate* isolate = Isolate::GetCurrent();
HandleScope scope(isolate);
// 解封this指针
Point* point = ObjectWrap::Unwrap<Point>(args.Holder());
// c++流处理
ostringstream oss;
oss << "(" << point->_x << ", " << point->_y << ")";
// 设置返回值
args.GetReturnValue().Set(String::NewFromUtf8(isolate, oss.str().c_str()));
}
InternalField
当JS new一个对象的时候,C++中也会同步的new一个对象并将该指针保存在C++内部,并维护这个指针list,这就是V8 InternalField的作用。所有需要跟JS绑定的C++指针都存在这个InternalField中,其实就是一个list,一个V8 Object可以拥有任意数量的InternalField。
Wrap和Unwrap
Move方法中用到了Wrap方法,toString中用到了Unwrap方法。Wrap和Unwrap是专门用来专门用来封装C++指针的。