标签: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.7
recommended,v3.x.x
is 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.x
replaced, 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++
指针的。