Node.js调用C/C++

标签: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 install v3.x.x replaced, you can install with npm 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 中的继承关系。

Data继承树
Data继承树

返回目录

<h3 id="v8_handle">Handle</h3>

V8里使用Handle类型来托管 JavaScript 对象,与C++的std::sharedpointer类似,Handle类型间的赋值均是直接传递对象引用,但不同的是,V8使用自己的GC 来管理对象生命周期,而不是智能指针常用的引用计数。

在C++中使用这些类型时,必须使用Handle 托管,以使用GC来管理它们的生命周期,而不使用原生栈和堆。

Handle
Handle

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);

下面这张图就讲解的比较清晰:

Context
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++指针的。

返回目录

最后编辑于 :
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容