用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 url传值中文乱码之解决之道
Nov 20 Javascript
Javascript学习笔记之函数篇(四):arguments 对象
Nov 23 Javascript
js实现的简单radio背景颜色选择器代码
Aug 18 Javascript
JS未跨域操作iframe里的DOM
Jun 01 Javascript
一次$.getJSON不执行的简单记录
Jul 19 Javascript
Bootstrap选项卡动态切换效果
Nov 28 Javascript
EasyUI学习之DataGird分页显示数据
Dec 29 Javascript
令按钮悬浮在(手机)页面底部的实现方法
May 02 Javascript
JS中LocalStorage与SessionStorage五种循序渐进的使用方法
Jul 12 Javascript
react 父组件与子组件之间的值传递的方法
Sep 14 Javascript
微信小程序常用简易小函数总结
Feb 01 Javascript
node中IO以及定时器优先级详解
May 10 Javascript
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使用sql数据库 获取字段问题介绍
2013/08/12 PHP
php获取文件夹路径内的图片以及分页显示示例
2014/03/11 PHP
php编写的一个E-mail验证类
2015/03/25 PHP
javascript中length属性的探索
2011/07/31 Javascript
jquery子元素过滤选择器使用示例
2013/06/24 Javascript
JavaScript前端图片加载管理器imagepool使用详解
2014/12/29 Javascript
javascript实现网页背景烟花效果的方法
2015/08/06 Javascript
Angular2 (RC4) 路由与导航详解
2016/09/21 Javascript
javascript 解决浏览器不支持的问题
2016/09/24 Javascript
jQuery滚动监听实现商城楼梯式导航效果
2017/03/06 Javascript
JavaScript Drum Kit 指南(纯 JS 模拟敲鼓效果)
2017/07/23 Javascript
Node使用Sequlize连接Mysql报错:Access denied for user ‘xxx’@‘localhost’
2018/01/03 Javascript
angularjs $http调用接口的方式详解
2018/08/13 Javascript
webpack的CSS加载器的使用
2018/09/11 Javascript
微信小程序云开发详细教程
2019/05/16 Javascript
微信小程序实现上拉加载功能
2019/11/20 Javascript
详解ES6实现类的私有变量的几种写法
2021/02/10 Javascript
Python实现批量转换文件编码的方法
2015/07/28 Python
解决Python出现_warn_unsafe_extraction问题的方法
2016/03/24 Python
在Python web中实现验证码图片代码分享
2017/11/09 Python
Python实现多进程共享数据的方法分析
2017/12/04 Python
详解python中递归函数
2019/04/16 Python
python getpass模块用法及实例详解
2019/10/07 Python
Python利用逻辑回归分类实现模板
2020/02/15 Python
Python基于QQ邮箱实现SSL发送
2020/04/26 Python
Python退出时强制运行一段代码的实现方法
2020/04/29 Python
Scrapy-Redis之RedisSpider与RedisCrawlSpider详解
2020/11/18 Python
python中使用asyncio实现异步IO实例分析
2021/02/26 Python
HTML5地理定位与第三方工具百度地图的应用
2016/11/17 HTML / CSS
用HTML5制作视频拼图的教程
2015/05/13 HTML / CSS
介绍一下Make? 为什么使用make
2013/12/08 面试题
Java软件工程师综合面试题笔试题
2013/09/08 面试题
创意活动策划书
2014/01/15 职场文书
《中彩那天》教学反思
2014/02/22 职场文书
慰问信格式规范
2015/03/23 职场文书
MySQL表字段数量限制及行大小限制详情
2022/07/23 MySQL