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

前端之家收集整理的这篇文章主要介绍了用C/C++来实现 Node.js 的模块(二)前端之家小编觉得挺不错的,现在分享给大家,也给大家做个参考。

温故而知新,可以为湿矣

  首先请大家记住这个 V8 的在线手册——http://izs.me/v8-docs/main.html。

  还记得上次的 building.gyp 文件吗?

代码如下:

  就像这样,举一反三,如果多几个 *.cc 文件的话就是这样的: "sources": [ "addon.cc","myexample.cc" ]

  上次我们把俩步骤分开了,实际上配置和编译可以放在一起的: $ node-gyp configure build

  复习完了吗?没?!

  好的,那我们继续吧。

表番

函数参数

  现在我们终于要讲参数了呢。

  让我们设想有这样一个函数 add(a,b) 代表把 a 和 b 相加返回结果,所以先把函数外框写好:

代码如下:
using namespace v8;

Handle Add(const Arguments& args) { HandleScope scope;

//... 又来! }

Arguments

  这个就是函数的参数了。我们不妨先看看 v8 的官方手册参考。 •int Length() const •Local operator[](int i) const

  其它的我们咱不关心,这两个可重要了!一个代表传入函数的参数个数,另一个中括号就是通过下标索引来访问第 n 个参数的。

  所以如上的需求,我们大致就可以理解为 args.Length() 为 2,args[0] 代表 a 以及 args[1] 代表 b 了。并且我们要判断这两个数的类型必须得是 Number。

  注意到没,中括号的索引操作符返回结果是一个 Local 也就是 Node.js 的所有类型基类。所以传进来的参数类型不定的,我们必须得自己判断是什么参数。这就关系到了这个 Value 类型的一些函数了。

•IsArray() •IsBoolean() •IsDate() •IsFunction() •IsInt32() •IsNativeError() •IsNull() •IsNumber() •IsRegExp() •IsString() •...

  我就不一一列举了,剩下的自己看文档。。:.゚ヽ(*´∀`)ノ゚.:。

ThrowException

  这个是我们等下要用到的一个函数。具体在 v8 文档中可以找到。

  顾名思义,就是抛出错误啦。执行这个语句之后,相当于在 Node.js 本地文件中执行了一条 throw() 语句一样。比如说: ThrowException(Exception::TypeError(String::New("Wrong number of arguments")));

  就相当于执行了一条 Node.js 的: throw new TypeError("Wrong number of arguments");

Undefined()

  这个函数呢也在文档里面。

  具体就是一个空值,因为有些函数并不需要返回什么具体的值,或者说没有返回值,这个时候就需要用 Undefined() 来代替了。

动手吧骚年!

  在理解了以上的几个要点之后,我相信你们很快就能写出 a + b 的逻辑了,我就把 Node.js 官方手册的代码抄过来给你们过一遍就算完事了:

代码如下:
using namespace v8;

Handle Add(const Arguments& args) { HandleScope scope;

// 代表了可以传入 2 个以上的参数,但实际上我们只用前两个 if(args.Length() < 2) { // 抛出错误 ThrowException(Exception::TypeError(String::New("Wrong number of arguments")));

// 返回空值 return scope.Close(Undefined()); }

// 若前两个参数其中一个不是数字的话 if(!args[0]->IsNumber() || !args[1]->IsNumber()) { // 抛出错误并返回空值 ThrowException(Exception::TypeError(String::New("Wrong arguments"))); return scope.Close(Undefined()); }

// 具体参考 v8 文档 // ) // 的 `NumberValue` 函数 Local num = Number::New(args[0]->NumberValue() + args[1]->NumberValue());

return scope.Close(num); }

  函数大功告成!

  最后把尾部的导出函数给写好就 OK 了。

代码如下:
exports) { exports->Set(String::NewSymbol("add"), FunctionTemplate::New(Add)->GetFunction()); }

NODE_MODULE(addon,Init)

  等你编译好之后,我们就能这样用了:

代码如下:

  你会看到一个 2b !✧。٩(ˊᗜˋ)و✧*。

回调函数

  上一章我们只讲了个 Hello world,这一章阿婆主就良心发现一下,再来个回调函数的写法。

  惯例我们先写好框架:

代码如下:
using namespace v8;

Handle RunCallback(const Arguments& args) { HandleScope scope;

// ... 噼里啪啦噼里啪啦

return scope.Close(Undefined()); }

  然后我们决定它的用法是这样的: func(function(msg) { console.log(msg); });

  即它会给回调函数传入一个参数,我们设想它是一个字符串,然后我们可以 console.log() 出来看。

首先你要有一个字符串系列

  废话不多说,先给它一个字符串喂饱了再说吧。(√ ζ ε:)

  不过我们得让这个字符串是通用类型的,因为 Node.js 代码是弱类型的。 Local::New(String::New("hello world"));

  什么?你问我什么是 Local

  那我稍稍讲一下吧,参考自这里和V8参考文档。

  如文档所示,Local 实际上继承自 Handle,我记得上一章已经讲过 Handle 这个东西了。

  然后下面就是讲 Local 了。

Handle 有两种类型, Local Handle 和 Persistent Handle ,类型分别是 Local : Handle 和 Persistent : Handle ,前者和 Handle 没有区别生存周期都在 scope 内。而后者的生命周期脱离 scope ,你需要手动调用 Persistent::Dispose 结束其生命周期。也就是说 Local Handle 相当于在 C++`在栈上分配对象而 Persistent Handle 相当于 C++ 在堆上分配对象。

然后你要有个参数表系列

  终端命令行调用 C/C++ 之后怎么取命令行参数?

代码如下:

void main(int argc,char* argv[]) { // ... }

  对了,这里的 argc 就是命令行参数个数,argv[] 就是各个参数了。那么调用 Node.js 的回调函数,v8 也采用了类似的方法

代码如下:
v8::Function::Call(Handlerecv, int argc, Handle argv[] );

~~QAQ 卡在了 Handle recv 了!!!明天继续写。~~

  好吧,新的一天开始了我感觉我充满了力量。(∩^o^)⊃━☆゚.*・。

  经过我多方面求证(SegmentFault和StackOverflow以及一个扣扣群),终于解决了上面这个函数仨参数的意思。

  后面两个参数就不多说了,一个是参数个数,另一个就是一个参数的数组了。至于第一个参数 Handle recv,StackOverflow 仁兄的解释是这样的:

It is the same as apply in JS. In JS,you do

代码如下:

The object passed as the first argument becomes this within the function scope. More documentation on MDN. If you don't know JS well,you can read more about JS's this here:

—— 摘自 StackOverflow

  总之其作用就是指定了被调用函数的 this 指针。这个 Call 的用法就跟 JavaScript 中的 bind()、call()、apply() 类似。

  所以我们要做的事情就是先把参数表建好,然后传入这个 Call 函数供其执行。

  第一步,显示转换函数,因为本来是 Object 类型: Local cb = Local::Cast(args[0]);

  第二步,建立参数表(数组): Local argv[argc] = { Local::New(String::New("hello world")) };

最后调用函数系列

  调用 cb ,把参数传进去: cb->Call(Context::GetCurrent()->Global(),1,argv);

  这里第一个参数 Context::GetCurrent()->Global() 所代表的意思就是获取全局上下文作为函数的 this;第二个参数就是参数表中的个数(毕竟虽然 Node.js 的数组是有长度属性的,但是 C++ 里面数组的长度实际上系统是不知道的,还得你自己传进一个数来说明数组长度);最后一个参数就是刚才我们建立好的参数表了。

终章之结束文件系列

  相信这一步大家已经轻车熟路了吧,就是把函数写好,然后放进导出函数里面,最后申明一下。

  我就直接放出代码吧,或者直接跑去 Node.js 的文档看也行。

代码如下:
using namespace v8;

Handle RunCallback(const Arguments& args) { HandleScope scope; Local cb = Local::Cast(args[0]); const unsigned argc = 1; Local argv[argc] = { Local::New(String::New("hello world")) }; cb->Call(Context::GetCurrent()->Global(),argc,argv);

return scope.Close(Undefined()); }

void Init(Handle exports,Handle module) { module->Set(String::NewSymbol("exports"), FunctionTemplate::New(RunCallback)->GetFunction()); }

NODE_MODULE(addon,Init)

  Well done! 最后剩下的步骤就自己去吧。至于 Js 里面这么调用这个函数,我在之前已经提到过了。

番外

  嘛嘛,我感觉我的学习笔记写得越来越奔放了求破~

  今天就先写到这里吧,写学习笔记的过程中我又涨姿势了,比如说那个 Call 函数的参数意义。

  如果你们觉得本系列学习笔记对你们还有帮助的话,就来和我一起搞基吧么么哒~Σ>―(〃°ω°〃)♡→

猜你在找的Node.js相关文章