用C/C++来实现 Node.js 的模块(一)_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゚ロ゚)┌┛Σ(ノ´ω`)ノ

时间: 2024-11-01 15:29:54

用C/C++来实现 Node.js 的模块(一)_node.js的相关文章

使用node+vue.js实现SPA应用_node.js

业务需求 最近公司要求开发web版的app,由于app是偏向内容方面,而且带了一个聊天模块,所以一般的多页开发不是很适合,而且主要是手机浏览,对加载速度或者用户体验来说都比较苛刻.调研了很多框架和模式,最后自己东拼西凑搞出来了这么一个玩意. 服务端 毫无疑问使用node,使用typescript可以有效的在编码同时查错,强类型语言写服务端毫无压力. #app.ts 只贴重要代码 var webpack = require('webpack') var webpackDevMiddleware =

node.js调用C++开发的模块实例_node.js

如何用C++和node交互,在node的程序中,如果有大数据量的计算,处理起来比较慢,可以用C++来处理,然后通过回调(callback的形式),返回给node.先回顾一下正统的用 C++ 开发 native 模块的方法 #include <node.h> #include <v8.h> using namespace v8; // 这里是 hello 函数的 C++ 实现部分 Handle<Value> Method(const Arguments& args

在Mac OS下使用Node.js的简单教程_node.js

这里有一篇很好的 Node.js 介绍文章 great nodejs intro ,它将给你一个非常方便的介绍 Node.js 和 CouchDB,并给出一个实例实现 REST 的服务用于执行书签的 CRUD 操作,使用 CouchDB 作为数据库. 本文将介绍在 Mac OS X 下安装并开始使用 Node.js ,这个过程大概需要 30 分钟左右的时间,其中我们还将安装 CouchDB,并实现基于 CouchDB 的 REST API. 本文假设你机器上已经装有Git,如果还没有,请参考此文

学习Node.js模块机制_node.js

一.CommonJS的模块规范 Node与浏览器以及 W3C组织.CommonJS组织.ECMAScript之间的关系 Node借鉴CommonJS的Modules规范实现了一套模块系统,所以先来看看CommonJS的模块规范. CommonJS对模块的定义十分简单,主要分为模块引用.模块定义和模块标识3个部分. 1. 模块引用 模块引用的示例代码如下: var math = require('math'); 在CommonJS规范中,存在require()方法,这个方法接受模块标识,以此引入一

Zabbix添加Node.js监控的方法_node.js

目前网上已有 pm2-zabbix 工具可以实现Zabbix对Node.js的监控报警,Github地址. 特征: 自动发现通过PM2管理的Node.js进程. 报告Nodes.js进程状态.CPU占用率.内存占用以及进程是否重启. 监控PM2守护进程自身状态.资源占用和PID改变. 已提供易于安装的Zabbix监控项模板. 1.Node.js服务器安装 pm2-zabbix npm install -g pm2-zabbix 测试自动发现功能是否正常: pm2-zabbix --discove

基于socket.io和node.js搭建即时通信系统_node.js

使用socket.io和nodejs搭建websocket服务器端 socket.io不仅可以搭建客户端的websocket服务,而且支持nodejs服务器端的websocket. 下面让我来介绍一下怎么安装配置nodejs. 进入http://nodejs.org/下载msi文件.一直点next安装.最后文件会自动安装在C:\nodejs目录下. 安装完成后,会自动配置环境环境变量.如果没有自动配置,自己手动在path处加上 ;C:\nodejs\. 安装完成后,需要配置npm来管理node.

简单实现node.js图片上传_node.js

本文实例为大家分享了node.js图片上传的具体代码,供大家参考,具体内容如下 1.node-formidable 对文件上传提供帮助的组件 2.app.js var formidable = require('formidable'); var http = require( 'http' ); var sys = require('sys'); http.createServer(function( request ,response ){ if( request.url == '/uplo

Nodejs极简入门教程(一):模块机制_node.js

JavaScript 规范(ECMAScript)没有定义一套完善的能适用于大多数程序的标准库.CommonJS 提供了一套 JavaScript 标准库规范.Node 实现了 CommonJS 规范. 模块基础 在 Node 中,模块和文件是一一对应的.我们定义一个模块: 复制代码 代码如下: // circle.js var PI = Math.PI;   // 导出函数 area exports.area = function(r) {     return PI * r * r; }  

win7下安装配置node.js+express开发环境_node.js

1.安装 node.js. 进入官网的下载地址:http://www.nodejs.org/download/ . 选择Windows Installer或者选择Windows Installer (.msi) 32-bit,下载安装包. 下载完成后,双击安装. 2.安装 Express. 通过nmp包管理器进行安装,安装分为: 全局安装:自动安装到"C:\Users\[当前用户]\AppData\Roaming\npm"下,并且自动将路径添加到环境变量"Path"

2014年最火的Node.JS后端框架推荐_node.js

Node.js框架是可以帮助你建立网站应用服务的javascript框架.它可以帮助你更快地开发Web应用 Node.js 是基于Chrome的JavaScript运行环境.可方便地构建快速.可扩展的网络应用.节点. 当涉及到Web应用程序的开发,你就需要JS框架,加快你的开发过程. Node.js有非常多不同类型的框架,如MVC框架,REST API和generators,全栈框架,大量的服务器库,它完全能够提供Web服务,无需使用Apache等外部软件. Node.js使用谷歌的V8 Jav