首頁  >  文章  >  web前端  >  用C/C 實作 Node.js 的模組(二)_node.js

用C/C 實作 Node.js 的模組(二)_node.js

WBOY
WBOY原創
2016-05-16 16:35:311104瀏覽

溫故而知新,可以為濕矣

  首先請大家記住這個 V8 的線上手冊——http://izs.me/v8-docs/main.html。

  還記得上次的 building.gyp 檔案嗎?

複製程式碼 程式碼如下:

{
  "targets": [
    {
      "target_name": "addon",
      "sources": [ "addon.cc" ]
    }
  ]
}

   就像這樣,舉一反三,如果多幾個 *.cc 檔案的話就是這樣的:
"sources": [ "addon.cc", "myexample.cc" ]

   上次我們把倆步驟分開了,實際上配置和編譯可以放在一起的:
$ node-gyp configure build

   複習完了嗎?沒? !

  好的,那我們繼續吧。

表番

函數參數

  現在我們終於要講參數了呢。

  讓我們設想有這樣一個函數 add(a, b) 代表把 a 和 b 相加回傳結果,所以先把函數外框寫好:

複製程式碼 程式碼如下:

#include
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 官方手冊的程式碼抄過來給你們過一遍就算完事了:

複製程式碼 程式碼如下:

#include
using namespace v8;

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

    // 代表了可以傳入 2 個以上的參數,但實際上我們只用前兩個
    if(args.Length()     {
        // 拋出錯誤
        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 文件
    //     http://izs.me/v8-docs/classv8_1_1Value.html#a6eac2b07ed58f1761bbfd53bf0e366dced58f1761bbfd53bf0e366
dc
    // 的 `NumberValue` 函數

    Local num = Number::New(args[0]->NumberValue() args[1]->NumberValue());


    return scope.Close(num);
}

   函數大功告成!


  最後把尾部的導出函數給寫好就 OK 了。

程式碼如下:


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


NODE_MODULE(addon, Init)


   等你編譯好之後,我們就能這樣用了:

程式碼如下:

var addon = require('./build/Release/addon') ;
console.log(addon.add(1, 1) "b");

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

回呼函數

  上一章我們只講了個 Hello world,這一章阿婆主就良心發現一下,再來個回調函數的寫法。


  慣例我們先寫好框架:

程式碼如下:


#include 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 之後怎麼取命令列參數?

複製程式碼 程式碼如下:

#include

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

   對了,這裡的 argc 就是命令列參數個數,argv[] 就是各個參數了。那麼呼叫 Node.js 的回呼函數,v8 也採用了類似的方法:

複製程式碼 程式碼如下:
V8EXPORT Local 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

複製程式碼 程式碼如下:

var context = ...;
cb.apply(context, [ ...args...]);

最初の引数として渡されるオブジェクトは、関数スコープ内で this になります。JS について詳しくない場合は、ここで JS の詳細を参照してください。http://unschooled.org /2012/03/undering-javascript-this/

——StackOverflow

より抜粋

つまり、その機能は呼び出される関数の this ポインタを指定することです。この Call の使用法は、JavaScript の binding()、call()、および apply() に似ています。

したがって、私たちがしなければならないことは、最初にパラメータテーブルを構築し、次に実行のために Call 関数を渡すことです。

最初のステップは変換関数を表示することです。変換関数は元々オブジェクト型であるためです。
ローカル cb = ローカル::Cast(args[0]);

2 番目のステップは、パラメーター テーブル (配列) を作成することです:
Local argv[argc] = { Local::New(String::New("hello world")) };

ラストコール機能シリーズ

cb を呼び出してパラメータを渡します:
cb->Call(Context::GetCurrent()->Global(), 1, argv);

ここでの最初のパラメータ Context::GetCurrent()->Global() は、関数の this としてグローバル コンテキストを取得することを意味します。2 番目のパラメータはパラメータ テーブル内の番号です (結局、Node.js ですが)。配列には長さ属性がありますが、実際にはシステムは C の配列の長さを知らないため、配列の長さを示すために自分で数値を渡す必要があります); 最後のパラメーターは作成したばかりのパラメーター テーブルです。 。

最終章ファイナルドキュメントシリーズ

関数を作成し、それをエクスポートされた関数に入れて、最後に宣言するというこのステップについては、誰もがすでによく知っていると思います。

コードを直接リリースするだけですが、Node.js ドキュメントに直接アクセスすることもできます。

コードをコピー コードは次のとおりです:

#include
名前空間 v8 を使用;

ハンドル<値>RunCallback(const Arguments& args)
{
HandleScope スコープ;
ローカル cb = ローカル::Cast(args[0]);
const unsigned argc = 1;
Local argv[argc] = { Local::New(String::New("hello world")) };
cb->Call(Context::GetCurrent()->Global(), argc, argv);

returnscope.Close(Unknown());
}

void Init(Handle エクスポート、Handle モジュール)
{
モジュール -> Set(String::NewSymbol("exports"),
FunctionTemplate::New(RunCallback)->GetFunction());
}

NODE_MODULE(アドオン、初期化)

完了しました。残りの手順は自分で実行してください。この関数を JS で呼び出すことについては、以前に説明しました。

追加

さて、勉強ノートがどんどん自由になってきている気がしますので、分解してください~

今日はこの辺で、勉強ノートを書いている途中でCall関数のパラメータの意味などでまた困ってしまいました。

この一連の学習ノートがまだ役立つと思われる場合は、ぜひ一緒に楽しんでください〜Σ>―(〃°ω°〃)♡→

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn