用C/C++來(lái)實(shí)現(xiàn) Node.js 的模塊(二)
溫故而知新,可以為濕矣
首先請(qǐng)大家記住這個(gè) V8 的在線手冊(cè)——http://izs.me/v8-docs/main.html。
還記得上次的 building.gyp 文件嗎?
{
"targets": [
{
"target_name": "addon",
"sources": [ "addon.cc" ]
}
]
}
就像這樣,舉一反三,如果多幾個(gè) *.cc 文件的話就是這樣的:
"sources": [ "addon.cc", "myexample.cc" ]
上次我們把倆步驟分開了,實(shí)際上配置和編譯可以放在一起的:
$ node-gyp configure build
復(fù)習(xí)完了嗎?沒?!
好的,那我們繼續(xù)吧。
表番
函數(shù)參數(shù)
現(xiàn)在我們終于要講參數(shù)了呢。
讓我們?cè)O(shè)想有這樣一個(gè)函數(shù) add(a, b) 代表把 a 和 b 相加返回結(jié)果,所以先把函數(shù)外框?qū)懞茫?br />
#include <node.h>
using namespace v8;
Handle<Value> Add(const Arguments& args)
{
HandleScope scope;
//... 又來(lái)!
}
Arguments
這個(gè)就是函數(shù)的參數(shù)了。我們不妨先看看 v8 的官方手冊(cè)參考。
•int Length() const
•Local<Value> operator[](int i) const
其它的我們?cè)鄄魂P(guān)心,這兩個(gè)可重要了!一個(gè)代表傳入函數(shù)的參數(shù)個(gè)數(shù),另一個(gè)中括號(hào)就是通過(guò)下標(biāo)索引來(lái)訪問(wèn)第 n 個(gè)參數(shù)的。
所以如上的需求,我們大致就可以理解為 args.Length() 為 2,args[0] 代表 a 以及 args[1] 代表 b 了。并且我們要判斷這兩個(gè)數(shù)的類型必須得是 Number。
注意到?jīng)],中括號(hào)的索引操作符返回結(jié)果是一個(gè) Local<Value> 也就是 Node.js 的所有類型基類。所以傳進(jìn)來(lái)的參數(shù)類型不定的,我們必須得自己判斷是什么參數(shù)。這就關(guān)系到了這個(gè) Value 類型的一些函數(shù)了。
•IsArray()
•IsBoolean()
•IsDate()
•IsFunction()
•IsInt32()
•IsNativeError()
•IsNull()
•IsNumber()
•IsRegExp()
•IsString()
•...
我就不一一列舉了,剩下的自己看文檔。。:.゚ヽ(*´∀`)ノ゚.:。
ThrowException
這個(gè)是我們等下要用到的一個(gè)函數(shù)。具體在 v8 文檔中可以找到。
顧名思義,就是拋出錯(cuò)誤啦。執(zhí)行這個(gè)語(yǔ)句之后,相當(dāng)于在 Node.js 本地文件中執(zhí)行了一條 throw() 語(yǔ)句一樣。比如說(shuō):
ThrowException(Exception::TypeError(String::New("Wrong number of arguments")));
就相當(dāng)于執(zhí)行了一條 Node.js 的:
throw new TypeError("Wrong number of arguments");
Undefined()
這個(gè)函數(shù)呢也在文檔里面。
具體就是一個(gè)空值,因?yàn)橛行┖瘮?shù)并不需要返回什么具體的值,或者說(shuō)沒有返回值,這個(gè)時(shí)候就需要用 Undefined() 來(lái)代替了。
動(dòng)手吧騷年!
在理解了以上的幾個(gè)要點(diǎn)之后,我相信你們很快就能寫出 a + b 的邏輯了,我就把 Node.js 官方手冊(cè)的代碼抄過(guò)來(lái)給你們過(guò)一遍就算完事了:
#include <node.h>
using namespace v8;
Handle<Value> Add(const Arguments& args)
{
HandleScope scope;
// 代表了可以傳入 2 個(gè)以上的參數(shù),但實(shí)際上我們只用前兩個(gè)
if(args.Length() < 2)
{
// 拋出錯(cuò)誤
ThrowException(Exception::TypeError(String::New("Wrong number of arguments")));
// 返回空值
return scope.Close(Undefined());
}
// 若前兩個(gè)參數(shù)其中一個(gè)不是數(shù)字的話
if(!args[0]->IsNumber() || !args[1]->IsNumber())
{
// 拋出錯(cuò)誤并返回空值
ThrowException(Exception::TypeError(String::New("Wrong arguments")));
return scope.Close(Undefined());
}
// 具體參考 v8 文檔
// http://izs.me/v8-docs/classv8_1_1Value.html#a6eac2b07dced58f1761bbfd53bf0e366)
// 的 `NumberValue` 函數(shù)
Local<Number> num = Number::New(args[0]->NumberValue() + args[1]->NumberValue());
return scope.Close(num);
}
函數(shù)大功告成!
最后把尾部的導(dǎo)出函數(shù)給寫好就 OK 了。
void Init(Handle<Object> exports)
{
exports->Set(String::NewSymbol("add"),
FunctionTemplate::New(Add)->GetFunction());
}
NODE_MODULE(addon, Init)
等你編譯好之后,我們就能這樣用了:
console.log(addon.add(1, 1) + "b");
你會(huì)看到一個(gè) 2b !✧。٩(ˊᗜˋ)و✧*。
回調(diào)函數(shù)
上一章我們只講了個(gè) Hello world,這一章阿婆主就良心發(fā)現(xiàn)一下,再來(lái)個(gè)回調(diào)函數(shù)的寫法。
慣例我們先寫好框架:
#include <node.h>
using namespace v8;
Handle<Value> RunCallback(const Arguments& args)
{
HandleScope scope;
// ... 噼里啪啦噼里啪啦
return scope.Close(Undefined());
}
然后我們決定它的用法是這樣的:
func(function(msg) {
console.log(msg);
});
即它會(huì)給回調(diào)函數(shù)傳入一個(gè)參數(shù),我們?cè)O(shè)想它是一個(gè)字符串,然后我們可以 console.log() 出來(lái)看。
首先你要有一個(gè)字符串系列
廢話不多說(shuō),先給它一個(gè)字符串喂飽了再說(shuō)吧。(√ ζ ε:)
不過(guò)我們得讓這個(gè)字符串是通用類型的,因?yàn)?Node.js 代碼是弱類型的。
Local<Value>::New(String::New("hello world"));
什么?你問(wèn)我什么是 Local<Value>?
那我稍稍講一下吧,參考自這里和V8參考文檔。
如文檔所示,Local<T> 實(shí)際上繼承自 Handle<T>,我記得上一章已經(jīng)講過(guò) Handle<T> 這個(gè)東西了。
然后下面就是講 Local 了。
Handle 有兩種類型, Local Handle 和 Persistent Handle ,類型分別是 Local<T> : Handle<T> 和 Persistent<T> : Handle<T> ,前者和 Handle<T> 沒有區(qū)別生存周期都在 scope 內(nèi)。而后者的生命周期脫離 scope ,你需要手動(dòng)調(diào)用 Persistent::Dispose 結(jié)束其生命周期。也就是說(shuō) Local Handle 相當(dāng)于在 C++`在棧上分配對(duì)象而 Persistent Handle 相當(dāng)于 C++ 在堆上分配對(duì)象。
然后你要有個(gè)參數(shù)表系列
終端命令行調(diào)用 C/C++ 之后怎么取命令行參數(shù)?
#include <stdio.h>
void main(int argc, char* argv[])
{
// ...
}
對(duì)了,這里的 argc 就是命令行參數(shù)個(gè)數(shù),argv[] 就是各個(gè)參數(shù)了。那么調(diào)用 Node.js 的回調(diào)函數(shù),v8 也采用了類似的方法:
int argc,
Handle<Value> argv[]
);
~~QAQ 卡在了 Handle<Object> recv 了?。?!明天繼續(xù)寫。~~
好吧,新的一天開始了我感覺我充滿了力量。(∩^o^)⊃━☆゚.*・。
經(jīng)過(guò)我多方面求證(SegmentFault和StackOverflow以及一個(gè)扣扣群),終于解決了上面這個(gè)函數(shù)仨參數(shù)的意思。
后面兩個(gè)參數(shù)就不多說(shuō)了,一個(gè)是參數(shù)個(gè)數(shù),另一個(gè)就是一個(gè)參數(shù)的數(shù)組了。至于第一個(gè)參數(shù) Handle<Object> recv,StackOverflow 仁兄的解釋是這樣的:
It is the same as apply in JS. In JS, you do
var context = ...;
cb.apply(context, [ ...args...]);
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: http://unschooled.org/2012/03/understanding-javascript-this/
—— 摘自 StackOverflow
總之其作用就是指定了被調(diào)用函數(shù)的 this 指針。這個(gè) Call 的用法就跟 JavaScript 中的 bind()、call()、apply() 類似。
所以我們要做的事情就是先把參數(shù)表建好,然后傳入這個(gè) Call 函數(shù)供其執(zhí)行。
第一步,顯示轉(zhuǎn)換函數(shù),因?yàn)楸緛?lái)是 Object 類型:
Local<Function> cb = Local<Function>::Cast(args[0]);
第二步,建立參數(shù)表(數(shù)組):
Local<Value> argv[argc] = { Local<Value>::New(String::New("hello world")) };
最后調(diào)用函數(shù)系列
調(diào)用 cb ,把參數(shù)傳進(jìn)去:
cb->Call(Context::GetCurrent()->Global(), 1, argv);
這里第一個(gè)參數(shù) Context::GetCurrent()->Global() 所代表的意思就是獲取全局上下文作為函數(shù)的 this;第二個(gè)參數(shù)就是參數(shù)表中的個(gè)數(shù)(畢竟雖然 Node.js 的數(shù)組是有長(zhǎng)度屬性的,但是 C++ 里面數(shù)組的長(zhǎng)度實(shí)際上系統(tǒng)是不知道的,還得你自己傳進(jìn)一個(gè)數(shù)來(lái)說(shuō)明數(shù)組長(zhǎng)度);最后一個(gè)參數(shù)就是剛才我們建立好的參數(shù)表了。
終章之結(jié)束文件系列
相信這一步大家已經(jīng)輕車熟路了吧,就是把函數(shù)寫好,然后放進(jìn)導(dǎo)出函數(shù)里面,最后申明一下。
我就直接放出代碼吧,或者直接跑去 Node.js 的文檔看也行。
#include <node.h>
using namespace v8;
Handle<Value> RunCallback(const Arguments& args)
{
HandleScope scope;
Local<Function> cb = Local<Function>::Cast(args[0]);
const unsigned argc = 1;
Local<Value> argv[argc] = { Local<Value>::New(String::New("hello world")) };
cb->Call(Context::GetCurrent()->Global(), argc, argv);
return scope.Close(Undefined());
}
void Init(Handle<Object> exports, Handle<Object> module)
{
module->Set(String::NewSymbol("exports"),
FunctionTemplate::New(RunCallback)->GetFunction());
}
NODE_MODULE(addon, Init)
Well done! 最后剩下的步驟就自己去吧。至于 Js 里面這么調(diào)用這個(gè)函數(shù),我在之前已經(jīng)提到過(guò)了。
番外
嘛嘛,我感覺我的學(xué)習(xí)筆記寫得越來(lái)越奔放了求破~
今天就先寫到這里吧,寫學(xué)習(xí)筆記的過(guò)程中我又漲姿勢(shì)了,比如說(shuō)那個(gè) Call 函數(shù)的參數(shù)意義。
如果你們覺得本系列學(xué)習(xí)筆記對(duì)你們還有幫助的話,就來(lái)和我一起搞基吧么么噠~Σ>―(〃°ω°〃)♡→
相關(guān)文章
使用GruntJS構(gòu)建Web程序之Tasks(任務(wù))篇
任務(wù)(Tasks)是grunt的核心概念,你所做的很多工作比如資源合并(concat)、壓縮(uglify)都是在配置任務(wù)。 每次grunt運(yùn)行的時(shí)候,你指定的一個(gè)或多個(gè)任務(wù)也在運(yùn)行,如果你沒有指定任務(wù),那么一個(gè)默認(rèn)名為“default”的任務(wù)將自動(dòng)運(yùn)行。2014-06-06詳解用node搭建簡(jiǎn)單的靜態(tài)資源管理器
本篇文章主要介紹了詳解用node搭建簡(jiǎn)單的靜態(tài)資源管理器,主要用node的fs模塊,自己手寫一個(gè)簡(jiǎn)單的靜態(tài)資源管理器。有興趣的可以了解一下2017-08-08async/await與promise(nodejs中的異步操作問(wèn)題)
這篇文章主要介紹了async/await與promise(nodejs中的異步操作問(wèn)題),非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友可以參考下2017-03-03解決Node.js使用MySQL出現(xiàn)connect ECONNREFUSED 127.0.0.1:3306的問(wèn)題
這篇文章主要介紹了解決Node.js使用MySQL出現(xiàn)connect ECONNREFUSED 127.0.0.1:3306報(bào)錯(cuò)的相關(guān)資料,文中將問(wèn)題描述的很清楚,解決的方法也介紹的很完整,需要的朋友可以參考借鑒,下面來(lái)一起看看吧。2017-03-03Node.js實(shí)現(xiàn)批量替換文件內(nèi)容示例
這篇文章主要為大家介紹了Node.js實(shí)現(xiàn)批量替換文件內(nèi)容示例,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-08-08Node.js與Sails ~項(xiàng)目結(jié)構(gòu)與Mvc實(shí)現(xiàn)及日志機(jī)制
Sails是一個(gè)Node.js的中間架構(gòu),很方便的幫助我們搭建web應(yīng)用程序。還有node.js與Sails日志機(jī)制在本文中也講到了,需要的朋友可以一起學(xué)習(xí)下2015-10-10零基礎(chǔ)搭建Node.js、Express、Ejs、Mongodb服務(wù)器及應(yīng)用開發(fā)入門
這篇文章主要介紹了零基礎(chǔ)搭建Node.js、Express、Ejs、Mongodb服務(wù)器及應(yīng)用開發(fā)入門,本文在windows8系統(tǒng)下完成本教程,其它系統(tǒng)也可參考,需要的朋友可以參考下2014-12-12如何使node也支持從url加載一個(gè)module詳解
這篇文章主要給大家介紹了關(guān)于如何使node也支持從url加載一個(gè)module的相關(guān)資料,文中通過(guò)示例代碼將實(shí)現(xiàn)的方法介紹非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起看看吧2018-06-06