用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 相关文章推荐
利用javascript的面向对象的特性实现限制试用期
Aug 04 Javascript
js实现在页面上弹出蒙板技巧简单实用
Apr 16 Javascript
javascript中createElement的两种创建方式
May 14 Javascript
javascript原型模式用法实例详解
Jun 04 Javascript
jQuery插件bgStretcher.js实现全屏背景特效
Jun 05 Javascript
前端面试题及答案整理(二)
Aug 26 Javascript
JS动态的把左边列表添加到右边的实现代码(可上下移动)
Nov 17 Javascript
关于jQuery中fade(),show()起始位置的一点小发现
Apr 25 jQuery
vue 将页面公用的头部组件化的方法
Dec 18 Javascript
浅谈Vue.js 关于页面加载完成后执行一个方法的问题
Apr 01 Javascript
过滤器vue.filters的使用方法实现
Sep 18 Javascript
jquery实现直播视频弹幕效果
Feb 25 jQuery
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
php miniBB中文乱码问题解决方法
2008/11/25 PHP
php download.php实现代码 跳转到下载文件(response.redirect)
2009/08/26 PHP
php ckeditor上传图片文件名乱码解决方法
2013/11/15 PHP
php把session写入数据库示例
2014/02/26 PHP
PHP使用CURL模拟登录的方法
2015/07/08 PHP
thinkPHP模板中函数的使用方法示例
2016/11/30 PHP
PHP实现批量重命名某个文件夹下所有文件的方法
2017/09/04 PHP
脚本吧 - 幻宇工作室用到js,超强推荐expand.js
2006/12/23 Javascript
IE与FireFox的兼容性问题分析
2007/04/22 Javascript
每天一篇javascript学习小结(Array数组)
2015/11/11 Javascript
JS常用字符串方法(推荐)
2021/01/15 Javascript
深入学习js瀑布流布局
2016/10/14 Javascript
微信开发之调起摄像头、本地展示图片、上传下载图片实例
2016/12/08 Javascript
微信小程序 swiper制作tab切换实现附源码
2017/01/21 Javascript
jq.ajax+php+mysql实现关键字模糊查询(示例讲解)
2018/01/02 Javascript
angular基于ng-alain定义自己的select组件示例
2018/02/23 Javascript
vue.js 获取select中的value实例
2018/03/01 Javascript
vue项目部署上线遇到的问题及解决方法
2018/06/10 Javascript
JS中循环遍历数组的四种方式总结
2021/01/23 Javascript
[56:12]LGD vs Optic Supermajor小组赛D组胜者组决赛 BO3 第一场 6.3
2018/06/04 DOTA
[56:41]iG vs Winstrike 2018国际邀请赛小组赛BO2 第二场
2018/08/17 DOTA
[42:32]VP vs RNG 2019国际邀请赛淘汰赛 败者组 BO3 第一场 8.21.mp4
2020/07/19 DOTA
Python使用BeautifulSoup库解析HTML基本使用教程
2016/03/31 Python
Django 迁移、操作数据库的方法
2019/08/02 Python
如何用Anaconda搭建虚拟环境并创建Django项目
2020/08/02 Python
如何用PyPy让你的Python代码运行得更快
2020/12/02 Python
阿迪达斯越南官网:adidas越南
2020/07/19 全球购物
新书发布会策划方案
2014/06/09 职场文书
预防艾滋病宣传标语
2014/06/25 职场文书
仓库管理制度范本
2015/08/04 职场文书
2016中秋节月饼促销广告语
2016/01/28 职场文书
八年级作文之友情
2019/11/25 职场文书
浅谈由position属性引申的css进阶讨论
2021/05/25 HTML / CSS
详解Java ES多节点任务的高效分发与收集实现
2021/06/30 Java/Android
vue中的可拖拽宽度div的实现示例
2022/04/08 Vue.js
Windows下载并安装MySQL8.0.x 版本的完整教程
2022/04/10 MySQL