用C/C++来实现 Node.js 的模块(一)


Posted in Javascript onSeptember 24, 2014

 N久之前的一个坑——用 Node.js 来重构 NBUT 的 Online Judge,包括评测端也得重构一遍。(至于什么时候完成大家就不要关心了,(/?Д′)/~ ??

总之我们现在要做的其实简而言之就是——用C/C++来实现 Node.js 的模块。

准备工作

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

node-gyp

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

在任意角落,执行:

$ npm install node-gyp -g

 

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

Python

然后你需要有个 python 环境。

自己去官网搞一个来。

注意: 根据 node-gyp 的GitHub显示,请务必保证你的 python 版本介于 2.5.0 和 3.0.0 之间。
 
编译环境

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

入门

我就拿官网的入门 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 Studio 的 vcxproj 文件等,也许是 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゚ロ゚)┌┛Σ(ノ´ω`)ノ

Javascript 相关文章推荐
js控制iframe的高度/宽度让其自适应内容
Apr 09 Javascript
浅谈JSON中stringify 函数、toJosn函数和parse函数
Jan 26 Javascript
JQuery菜单效果的两个实例讲解(3)
Sep 17 Javascript
JS+CSS实现大气清新的滑动菜单效果代码
Oct 22 Javascript
js原型链与继承解析(初体验)
May 09 Javascript
jQuery实现鼠标经过购物车出现下拉框代码(推荐)
Jul 21 Javascript
JavaScript中捕获与冒泡详解及实例
Feb 03 Javascript
jQuery 控制文本框自动缩小字体填充
Jun 16 jQuery
详解angularjs 学习之 scope作用域
Jan 15 Javascript
Vue $emit $refs子父组件间方法的调用实例
Sep 12 Javascript
Node Express用法详解【安装、使用、路由、中间件、模板引擎等】
May 13 Javascript
vue/cli 配置动态代理无需重启服务的方法
May 20 Vue.js
JS实现一个列表中包含上移下移删除等功能
Sep 24 #Javascript
一个JavaScript函数把URL参数解析成Json对象
Sep 24 #Javascript
js监听鼠标点击和键盘点击事件并自动跳转页面
Sep 24 #Javascript
JavaScript设计模式之单例模式实例
Sep 24 #Javascript
JavaScript中实现异步编程模式的4种方法
Sep 24 #Javascript
JavaScript设计模式之观察者模式(发布者-订阅者模式)
Sep 24 #Javascript
JavaScript获取图片真实大小代码实例
Sep 24 #Javascript
You might like
我常用的几个类
2006/10/09 PHP
PHP实现基于栈的后缀表达式求值功能
2017/11/10 PHP
PHP常用函数之获取汉字首字母功能示例
2019/10/21 PHP
js+css使DIV始终居于屏幕中间 左下 左上 右上 右下的代码集合
2011/03/10 Javascript
ExtJS实现文件下载的方法实例
2013/11/09 Javascript
js中精确计算加法和减法示例
2014/03/28 Javascript
浏览器窗口大小变化时使用resize事件对框架不起作用的解决方法
2014/05/11 Javascript
删除条目时弹出的确认对话框
2014/06/05 Javascript
node.js入门实例helloworld详解
2015/12/23 Javascript
Javascript基于对象三大特性(封装性、继承性、多态性)
2016/01/04 Javascript
Bootstarp风格的toggle效果分享
2016/02/23 Javascript
JavaScript中匿名函数的递归调用
2017/01/22 Javascript
微信小程序使用picker实现时间和日期选择框功能【附源码下载】
2017/12/11 Javascript
javascript二维数组和对象的深拷贝与浅拷贝实例分析
2019/10/26 Javascript
详谈Vue.js框架下main.js,App.vue,page/index.vue之间的区别
2020/08/12 Javascript
[03:40]DOTA2抗疫特别篇《英雄年代》
2020/02/28 DOTA
Python实现对PPT文件进行截图操作的方法
2015/04/28 Python
python根据日期返回星期几的方法
2015/07/06 Python
Eclipse中Python开发环境搭建简单教程
2016/03/23 Python
Python第三方库的安装方法总结
2016/06/06 Python
浅谈Python 集合(set)类型的操作——并交差
2016/06/30 Python
Python初学时购物车程序练习实例(推荐)
2017/08/08 Python
基于Python闭包及其作用域详解
2017/08/28 Python
详解用Python处理HTML转义字符的5种方式
2017/12/27 Python
python sys,os,time模块的使用(包括时间格式的各种转换)
2018/04/27 Python
Python高级用法总结
2018/05/26 Python
pandas进行数据的交集与并集方式的数据合并方法
2018/06/27 Python
Tensorflow实现酸奶销量预测分析
2019/07/19 Python
社区庆中秋节活动方案
2014/02/07 职场文书
保险内勤岗位职责
2014/04/05 职场文书
征用土地赔偿协议书
2014/09/26 职场文书
党员教师群众路线思想汇报范文
2014/10/28 职场文书
2015年高考寄语或鼓励的话
2015/03/23 职场文书
聚众斗殴罪辩护词
2015/05/21 职场文书
酒店厨房管理制度
2015/08/06 职场文书
何时使用Map来代替普通的JS对象
2021/04/29 Javascript