我發現每次細看源碼都能發現我之前寫的一些東西是錯誤的,去改掉吧,又很不協調,不改吧,看着又腦闊疼……
所以,這一節再探,是對之前一些說法的糾正,另外再縫縫補補一些新的內容。
錯誤在哪呢?在之前的初探中,有這么一塊代碼:
// 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"的加載過程也類似,就不再重復了。
目前得到了函數體,但是並沒有執行,后面再分析這塊內容。