深入出不來nodejs源碼-內置模塊引入再探


  我發現每次細看源碼都能發現我之前寫的一些東西是錯誤的,去改掉吧,又很不協調,不改吧,看着又腦闊疼……

  所以,這一節再探,是對之前一些說法的糾正,另外再縫縫補補一些新的內容。

  錯誤在哪呢?在之前的初探中,有這么一塊代碼:

  // The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
  // lib/internal/bootstrap/node.js, each included as a static C string
  // defined in node_javascript.h, generated in node_javascript.cc by
  // node_js2c.
  Local<String> loaders_name =
      FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
  Local<Function> loaders_bootstrapper =
      GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);

  當時,我的理解是這樣的:

 輔助函數則是加載了internal/bootstrap中的兩個JS文件,加載的時候參數傳入了C++代碼生成的特殊對象。

  但是在我調試這塊代碼的時候,發現根本沒有任何readFile的痕跡,才發現事情並沒有那么簡單,也就是說這個地方壓根就沒有加載對應的JS文件。

  那么問題來了,既然沒有加載這個JS文件,那這個文件有什么意義?何處加載的?

  第一個問題,我猜大概是開發者想讓我們直觀的了解到加載了什么東西,所以以文件的形式保留在文件夾中方便查看。

  第二個問題,根據注釋,可以很快的知道答案,但是當時哪里注意那么多喲。

  簡單講,這個文件的內容以靜態的字符串的形式定義在node_javascript.h中,內容則在node_javascript.cc中,並使用node_js2c進行JS代碼到C++代碼的轉換。

  

  問題的答案很簡單,探索過程對我來說還是挺心酸的,這里一共有兩行代碼,首先看第一行。

  FIXED_ONE_BYTE_STRING是一個宏,這里暫不討論內部實現,根據參數和返回類型可以簡單判斷這是一個轉換函數,可以將const char*類型轉換成Local<String>類型,至於Local是什么,可以參考我上一節內容,或者查閱其他的資料。

  對於第二行代碼,需要關注的是LoaderBootstrapperSource這個方法,進去之后會發現又是一個調用:

v8::Local<v8::String> LoadersBootstrapperSource(Environment* env) {
  return internal_bootstrap_loaders_value.ToStringChecked(env->isolate());
}

  這個internal_bootstrap_loaders_value是一個結構體,形式比較簡單(C++代碼結構都很簡單),源碼如下:

static struct : public v8::String::ExternalOneByteStringResource {
    // 重寫父類函數
    // 強制進行const unsigned char[] => const char*的類型轉換
    const char* data() const override {
        return reinterpret_cast<const char*>(raw_internal_bootstrap_loaders_value);
    }
    // 數組長度
    size_t length() const override { return arraysize(raw_internal_bootstrap_loaders_value); }
    // 默認delete函數
    void Dispose() override { /* Default calls `delete this`. */ }
    // const char* => Local<String>的類型轉換
    v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {
        return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
    }
} internal_bootstrap_loaders_value;

  幾個內部的屬性作用非常明朗,注釋都有寫。

  暫時不去深入了解ToStringChecked方法的內部實現,從返回類型來看,最終也是生成一個Local<String>類型的方法,而這個String的源頭,就是上面另外一個變量raw_internal_bootstrap_loaders_value。

  這個東西是這么定義的:

static const uint8_t raw_internal_bootstrap_loaders_value[] = { 47,47,32,84,104,105,115,32,102,105,108,101,32,99,114,101,97,116,101,115,... }

  很長很長的一個數組,uint8_t是unsigned char的別名,也就是說,這是一個超長的char數組。

  根據C++的基礎知識,數組的名字本質上是一個指針,指向數組的第一個值,而數組的值又恰好是char類型的,所以說,對該值進行reinterpret_cast<const char*>的轉換是不會有問題的。

  那么另外一個問題是,const char*更為熟知的類型是string字符串,這里一個數字數組是怎么變成字符串的?

  干想果然是浪費時間的,我把這個大數組弄到本地自己打印了一下,發現輸出的內容竟然是:

  怎么感覺這么熟悉,翻開JS文件,果然……

  簡單思考后,原來這里是因為ASCII表轉換,把對應的一個個字符轉換成了數字保存在字符數組中,真的是惡心啊。

  那么問題就解決了,加載的輔助JS文件內容其實是以字符數組保存在C++中的,獲取完整內容后通過對JS到C++的轉換,然后執行對應的代碼

 

  既然完成了JS文件內容、文件名的內容獲取,下一步就是構建對應的函數體,方法就是GetBootstrapper,源碼簡化后如下:

// 靜態公共方法 專門負責生成初始化輔助函數體
// env => 上下文環境
// source => JS格式的函數字符串
// script_name => 資源名
static Local<Function> GetBootstrapper(Environment* env, Local<String> source,
    Local<String> script_name) {
    EscapableHandleScope scope(env->isolate());
    // ...
    // 解析JS字符串並轉換成Local<Value>類型
    Local<Value> bootstrapper_v = ExecuteString(env, source, script_name);
    // 檢測返回的數據類型是否是函數並進行強制類型轉換
    CHECK(bootstrapper_v->IsFunction());
    Local<Function> bootstrapper = Local<Function>::Cast(bootstrapper_v);
    
    return scope.Escape(bootstrapper);
}

  這里省略了一些無關的錯誤處理,比較關鍵的幾步可以看注釋描述,有幾點需要特殊說明一下:

1、關於EscapableHandleScope,正常情況下都是使用的HandleScope來管理作用域的Local,但是如果函數需要返回臨時創建的Local,在返回前Local已經被V8的GC進行了處理,這里必須使用EscapableHandleScope類創建一個特殊的scope,並在最后調用Escape方法將指定的Local返回。

2、返回類型的Local<Value>,如果有看過上一節對於V8引擎一些基本概念的講解,應該會發現Value是所有數據類型的根類,在對類型進行CHECK后再強制轉換可以保證類型安全。

 

  最后看一眼ExecuteString方法:

static Local<Value> ExecuteString(Environment* env,
    Local<String> source,
    Local<String> filename) {
    EscapableHandleScope scope(env->isolate());
    // ...
    // 編譯解析一條龍
    ScriptOrigin origin(filename);
    MaybeLocal<v8::Script> script =
        v8::Script::Compile(env->context(), source, &origin);
    MaybeLocal<Value> result = script.ToLocalChecked()->Run(env->context());
    // 返回Local<Value>類型的C++代碼
    return scope.Escape(result.ToLocalChecked());
}

  這里同樣省略一些無關代碼,可以發現,處理過程非常直白,直接利用Script類對字符串進行編譯解析,然后返回執行完后生成的函數體。

  對於"internal/bootstrap/node.js"的加載過程也類似,就不再重復了。

  目前得到了函數體,但是並沒有執行,后面再分析這塊內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM