让Node.js和C++一块儿搞基 —— 1

  N久以前的一个坑——用 Node.js 来重构 NBUT 的 Online Judge,包括评测端也得重构一遍。(至于何时完成你们就不要关心了,(/‵Д′)/~ ╧╧javascript

  总之咱们如今要作的其实简而言之就是——用C/C++来实现 Node.js 的模块。html

准备工做

  工欲善其事,必先耍流氓利其器。java

node-gyp

  首先你须要一个 node-gyp 模块。node

  在任意角落,执行:python

$ npm install node-gyp -g

  在进行一系列的 blahblah 以后,你就安装好了。git

Python

  而后你须要有个 python 环境。github

  本身去官网搞一个来。shell

注意: 根据 node-gypGitHub显示,请务必保证你的 python 版本介于 2.5.03.0.0 之间。npm

编译环境

  嘛嘛,我就偷懒点不细写了,还请本身移步到 node-gyp 去看编译器的需求。而且倒腾好。json

入门

  我就拿官网的入门 Hello World说事儿了。

Hello World

  请准备一个 C++ 文件,好比就叫 sb.cc hello.cc。

  而后咱们一步步来,先往里面搞出头文件和定义好命名空间:

#include <node.h>
#include <v8.h>
using namespace v8;

主要函数

  接下去咱们写一个函数,其返回值是 Handle<Value>

Handle<Value> Hello(const Arguments& args)
{
    //... 嗷嗷待写
}

  而后我来粗粗解析一下这些东西:

Handle<Value>

  作人要有节操,我事先申明我是从这里(@fool)参考的。

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

JavaScript 类型在 C++ 中均有对应的自定义类型,如 String 、 Integer 、 Object 、 Date 、 Array 等,严格遵照在 JavaScript 中的继承关系。 C++ 中使用这些类型时,必须使用 Handle 托管,以使用 GC 来管理它们的生命周期,而不使用原生栈和堆。

  而这个所谓的 Value ,从 V8 引擎的头文件 v8.h 中的各类继承关系中能够看出来,其实就是 JavaScript 中各类对象的基类。

  在了解了这件事以后,咱们大体能明白上面那段函数的申明的意思就是说,咱们写一个 Hello 函数,其返回的是一个不定类型的值。

注意: 咱们只能返回特定的类型,即在 Handle 托管下的 String 啊 Integer 啊等等等等。

Arguments

  这个就是传入这个函数的参数了。咱们都知道在 Node.js 中,参数个数是乱来的。而这些参数传进去到 C++ 中的时候,就转变成了这个 Arguments 类型的对象了。

  具体的用法咱们在后面再说,在这里只须要明白这个是个什么东西就好。(为毛要卖关子?由于 Node.js 官方文档中的例子就是分开来说的,我如今只是讲第一个 Hello World 的例子而已( ´థ౪థ)σ

添砖加瓦

  接下去咱们就开始添砖加瓦了。就最简单的两句话:

Handle<Value> Hello(const Arguments& args)
{
    HandleScope scope;
    return scope.Close(String::New("world"));
}

  这两句话是什么意思呢?大体的意思就是返回一个 Node.js 中的字符串 "world"

HandleScope

  同参考自这里

Handle 的生命周期和 C++ 智能指针不一样,并非在 C++ 语义的 scope 内生存(即{} 包围的部分),而须要经过 HandleScope 手动指定。HandleScope 只能分配在栈上,HandleScope 对象声明后,其后创建的 Handle 都由 HandleScope 来管理生命周期,HandleScope 对象析构后,其管理的 Handle 将由 GC 判断是否回收。

  因此呢,咱们得在须要管理他的生命周期的时候申明这个 Scope 。好的,那么为何咱们的代码不这么写呢?

Handle<Value> Hello(const Arguments& args)
{
    HandleScope scope;
    return String::New("world");
}

  由于当函数返回时,scope 会被析构,其管理的Handle也都将被回收,因此这个 String 就会变得没有意义。

  因此呢 V8 就想出了个神奇的点子——HandleScope::Close(Handle<T> Value) 函数!这个函数的用处就是关闭这个 Scope 而且把里面的参数转交给上一个 Scope 管理,也就是进入这个函数前的 Scope。

  因而就有了咱们以前的代码 scope.Close(String::New("world"));

String::New

  这个 String 类所对应的就是 Node.js 中原生的字符串类。继承自 Value 类。与此相似,还有:

  • Array
  • Integer
  • Boolean
  • Object
  • Date
  • Number
  • Function
  • ...

  这些东西有些是继承自 Value,有些是二次继承。咱们这里就很少作研究,本身能够看看 V8 的代码(至少是头文件)研究研究或者看看这个手册

  而这个 New 呢?这里能够看的。就是新建一个 String 对象。

  至此,这个主要函数咱们就解析完毕了。

导出对象

  咱们来温习一下,若是是在 Node.js 里面写的话,咱们怎么导出函数或者对象什么的呢?

exports.hello = function() {}

  那么,在 C++ 中咱们该如何作到这一步呢?

初始化函数

  首先,咱们写个初始化函数:

void init(Handle<Object> exports)
{
    //... 嗷嗷待写你妹啊!#゚Å゚)⊂彡☆))゚Д゚)・∵
}

  这是龟腚!函数名什么的无所谓,可是传入的参数必定是一个 Handle<Object>,表明咱们下面将要在这货上导出东西。

  而后,咱们就在这里面写上导出的东西了:

void init(Handle<Object> exports)
{
    exports->Set(String::NewSymbol("hello"),
        FunctionTemplate::New(Hello)->GetFunction());
}

  大体的意思就是说,为这个 exports 对象添加一个字段叫 hello,所对应的东西是一个函数,而这个函数就是咱们亲爱的 Hello 函数了。

  用伪代码写直白点就是:

void init(Handle<Object> exports)
{
    exports.Set("hello", function hello);
}

  大功告成!

  (大功告成你妹啊!闭嘴( ‘д‘⊂彡☆))Д´)

真·导出

  这才是最后一步,咱们最后要申明,这个就是导出的入口,因此咱们在代码的末尾加上这一行:

NODE_MODULE(hello, init)

  纳了个尼?!这又是什么东西?

  别着急,这个 NODE_MODULE 是一个宏,它的意思呢就是说咱们采用 init 这个初始化函数来把要导出的东西导出到 hello 中。那么这个 hello 哪来呢?

  **它来自文件名!**对,没错,它来自文件名。你并不须要事先申明它,你也没必要担忧不能用,总之你的这个最终编译好的二进制文件名叫什么,这里的 hello 你就填什么,固然要除去后缀名了。

  详见官方文档

Note that all Node addons must export an initialization function:

void Initialize (Handle<Object> exports);
NODE_MODULE(module_name, Initialize)

There is no semi-colon after NODE_MODULE as it's not a function (see node.h).

The module_name needs to match the filename of the final binary (minus the .node suffix).

编译 (๑•́ ₃ •̀๑)

  来吧,让咱们一块儿编译吧!

  咱们再新建一个相似于 Makefile 的归档文件吧——binding.gyp

  而且在里面添加这样的代码

{
  "targets": [
    {
      "target_name": "hello",
      "sources": [ "hello.cc" ]
    }
  ]
}

  为何这么写呢?能够参考 node-gyp官方文档

configure

  在文件搞好以后,咱们要在这个目录下面执行这个命令了:

$ node-gyp configure

  若是一切正常的话,应该会生成一个 build 的目录,而后里面有相关文件,也许是 M$ Visual Studiovcxproj 文件等,也许是 Makefile ,视平台而定。

build

  Makefile 也生成好以后,咱们就开始构造编译了:

$ node-gyp build

  等到一切编译完成,才算是真正的大功告成了!不信你去看看 build/Release 目录,下面是否是有一个 hello.node 文件了?没错,这个就是 C++ 等下要给 Node.js 捡的肥皂!

搞基吧!Node ヽ(✿゚▽゚)ノ C++

  咱们在刚才那个目录下新建一个文件 jianfeizao.js

var addon = require("./build/Release/hello");
console.log(addon.hello());

  看到没!看到没!出来了出来了!Node.js 和 C++ 搞基的结果!这个 addon.hello() 就是咱们以前在 C++ 代码中写的 Handle<Value> Hello(const Arguments& args) 了,咱们如今就已经把它返回的值给输出了。

洗洗睡吧,下节更深刻

  时间不早了,今天就写到这里了,至此为止你们都能搞出最基础的 Hello world 的 C++ 扩展了吧。下一次写的应该会更深刻一点,至于下一次是何时,我也不知道啦其实。   (喂喂喂,撸主怎么能够这么不负责!(o゚ロ゚)┌┛Σ(ノ´ω`)ノ

相关文章
相关标签/搜索