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

 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゚ロ゚)┌┛Σ(ノ´ω`)ノ

(0)

相关推荐

  • 跟我学Nodejs(三)--- Node.js模块

    简介及资料 通过Node.js的官方API可以看到Node.js本身提供了很多核心模块 http://nodejs.org/api/ ,这些核心模块被编译成二进制文件,可以require('模块名')去获取:核心模块具有最高的加载优先级(有模块与核心模块同名时会体现) (本次主要说自定义模块) Node.js还有一类模块为文件模块,可以是JavaScript代码文件(.js作为文件后缀).也可以是JSON格式文本文件(.json作为文件后缀).还可以是编辑过的C/C++文件(.node作为文件后

  • 跟我学Nodejs(二)--- Node.js事件模块

    简介及资料 http://nodejs.org/api/events.html http://www.infoq.com/cn/articles/tyq-nodejs-event events是node.js 最重要的模块,events模块只提供了一个对象events.EventEmitter,EventEmitter 的核心是事件发射与事件监听器. Node.js中大部分的模块,都继承自Event模块. 与DOM树上事件不同,不存在事件冒泡.逐层捕获等行为. EventEmitter 支持若干

  • 在Windows上安装Node.js模块的方法

    不过有消息称Microsoft已经联系Node.js官方,相信很快会有改善. 那么在不安装Cygwin的情况下,是否可以在Windows上搭建Node.js环境进行试验开发哪?我以Node.js + express做了个简单测试,基本可行. 步骤如下: 1. 下载Node.js官方非稳定版Windows可执行程序: http://nodejs.org/#download 我在试验中使用了0.5.7版本:http://nodejs.org/dist/v0.5.7/node.exe 2. 创建c:\

  • node.js使用require()函数加载模块

    详细说明均以写在注释之中,这里就不啰嗦了,小伙伴们自己详细看吧,千万别吧注释当成空气了. 复制代码 代码如下: /*在node中,可以使用require()函数来加载模块.  * require函数使用一个参数,参数值可以带有完整路径的模块的文件名,也可以为模块名.当使用node中提供的模块时,在require函数中只需要指定模块名即可.  * */ //建立一个页面2.js;代码如下 var name="思思博士"; exports.name=name; //建立一个页面1.js;代

  • NODE.JS加密模块CRYPTO常用方法介绍

    使用require('crypto')调用加密模块. 加密模块需要底层系统提供OpenSSL的支持.它提供了一种安全凭证的封装方式,可以用于HTTPS安全网络以及普通HTTP连接. 该模块还提供了一套针对OpenSSL的hash(哈希),hmac(密钥哈希),cipher(编码),decipher(解码),sign(签名)以及verify(验证)等方法的封装. crypto.createCredentials(details) 创建一个凭证对象,可选参数details为一个带键值的字典: key

  • Node.js模块加载详解

    JavaScript是世界上使用频率最高的编程语言之一,它是Web世界的通用语言,被所有浏览器所使用.JavaScript的诞生要追溯到Netscape那个时代,它的核心内容被仓促的开发出来,用以对抗Microsoft,参与当时白热化的浏览器大战.由于过早的发布,无可避免的造成了它的一些不太好的特性. 尽管它的开发时间很短,但是JavaScript依然具备了很多强大的特性,不过,每个脚本共享一个全局命名空间这个特性除外. 一旦Web页面加载了JavaScript代码,它就会被注入到全局命名空间,

  • Node.js 的模块知识汇总

    在写 Node.js 应用程序的时候,你确实可以把所有代码放在巨大原 index.js 文件中,不在乎你的应用程序会变得多大多复杂.Node.js 解释器不会在意这个事情.但在代码组织方面,你很快就会陷入混乱,不能理解代码,而且难以调试.所以,作为一个人,你应该关心代码的结构.这就是使用模块的原因. 你可以把 Node.js 模块当作 JavaScript 库 -- 是整体代码中你想放在一起的某个部分(比如,函数集),你会想把这部分代码相对独立于代码库中的其它部分,可以把事情区分清楚. 就们我们

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

    N久之前的一个坑--用 Node.js 来重构 NBUT 的 Online Judge,包括评测端也得重构一遍.(至于什么时候完成大家就不要关心了,(/‵Д′)/~ ╧╧ 总之我们现在要做的其实简而言之就是--用C/C++来实现 Node.js 的模块. 准备工作 工欲善其事,必先~~耍流氓~~利其器. node-gyp 首先你需要一个 node-gyp 模块. 在任意角落,执行: 复制代码 代码如下: $ npm install node-gyp -g 在进行一系列的 blahblah 之后,

  • node.js基础模块http、网页分析工具cherrio实现爬虫

    一.前言       说是爬虫初探,其实并没有用到爬虫相关第三方类库,主要用了node.js基础模块http.网页分析工具cherrio. 使用http直接获取url路径对应网页资源,然后使用cherrio分析. 这里我主要学习过的案例自己敲了一遍,加深理解.在coding的过程中,我第一次把jq获取后的对象直接用forEach遍历,直接报错,是因为jq没有对应的这个方法,只有js数组可以调用. 二.知识点     ①:superagent抓去网页工具.我暂时未用到.     ②:cherrio

  • 浅析node.js的模块加载机制

    在node.js中,模块使用CommonJS规范,一个文件是一个模块 node.js中的模块可分为三类 内部模块 - node.js提供的模块如 fs,http,path等 自定模块 - 我们自己写的模块 第三方模块 - 通过npm安装的模块 node.js提供了大量的模块供我们使用,比如 想解析一个文件的路径,可以使用path模块下的相应方法实现: const path = require('path'); //返回目标文件的绝对路径 console.log(path.resolve('./1

  • node.js ws模块搭建websocket服务端的方法示例

    首先下载websocket模块,命令行输入 npm install ws node.js的 模块ws,可用于创建websocket服务,基本的express 和 http模块的使用 var express = require('express'); var http = require('http'); var WebSocket = require('ws'); var app = express(); var server = http.createServer(app); var wss

  • Node.js fs模块(文件模块)创建、删除目录(文件)读取写入文件流的方法

    fs模块.js: /* 1. fs.stat 检测是文件还是目录(目录 文件是否存在) 2. fs.mkdir 创建目录 (创建之前先判断是否存在) 3. fs.writeFile 写入文件(文件不存在就创建,但不能创建目录) 4. fs.appendFile 写入追加文件 5.fs.readFile 读取文件 6.fs.readdir 读取目录 7.fs.rename 重命名 8. fs.rmdir 删除目录 9. fs.unlink 删除文件 */ var fs=require('fs');

  • Node.js net模块功能及事件监听用法分析

    本文实例讲述了Node.js net模块功能及事件监听用法.分享给大家供大家参考,具体如下: net模块 对比C语言的网络编程,Node.js有更加简便的开发模式与模块提供.它就是net模块 在需要使用的时候只需要require("net")就可以引入模块了. var net = require("net"); 服务端编程 引入模块的前提下: 1. 创建一个新的TCP或IPC服务 var server = net.createServer(function(clie

  • Node.js JSON模块用法实例分析

    本文实例讲述了Node.js JSON模块用法.分享给大家供大家参考,具体如下: 一.JSON.stringify语法: JSON.stringify(value [, replacer] [, space]) value:是必选字段.就是你输入的对象,比如数组,类等. replacer(可选参数):它又分为2种方式,一种是数组,第二种是方法. (1)replacer为数组时,表示的是Key,只有在类中有出现过的Key的键值对(Key-Value)才会在转化的结果中出现. replacer数组仅

  • Node.js Buffer模块功能及常用方法实例分析

    本文实例讲述了Node.js Buffer模块功能及常用方法.分享给大家供大家参考,具体如下: Buffer模块 alloc()方法 alloc(size,fill,encoding)可以分配一个大小为 size 字节的新建的 Buffer,size默认为0 var buf = Buffer.alloc(10); 参数fill为填充的数据,只要指定了fill就会调用Buffer.fill(fill) 初始化这个Buffer对象 var buf = Buffer.alloc(10,0xff);//

  • 完美解决linux下node.js全局模块找不到的情况

    今天在在linux上用npm安装了pm2准备部署node项目,结果通过pm2命令启动项目的时候报pm2找不到,这很伤,以为pm2没有安装成功,但是在node安装目录下面的bin文件夹里面调用pm2却没问题. /usr/local/node/node-v7.3.0-linux-x64/bin/pm2 list 类似这样调用pm2 list命令查看pm2启动的项目没有任何问题. 原来linux系统上通过npm全局安装了模块以后,不能像windos下面那样自动配置环境变量,需要自己动手配置...好吧,

  • 解决linux下node.js全局模块找不到的问题

    今天在在linux上用npm安装了pm2准备部署node项目,结果通过pm2命令启动项目的时候报pm2找不到,这很伤,以为pm2没有安装成功,但是在node安装目录下面的bin文件夹里面调用pm2却没问题. /usr/local/node/node-v7.3.0-linux-x64/bin/pm2 list 类似这样调用pm2 list命令查看pm2启动的项目没有任何问题. 原来linux系统上通过npm全局安装了模块以后,不能像windos下面那样自动配置环境变量,需要自己动手配置...好吧,

随机推荐