node源码详解(五)
?本作品采用知識共享署名 4.0 國際許可協(xié)議進行許可。轉(zhuǎn)載保留聲明頭部與原文鏈接https://luzeshu.com/blog/nodesource5?
本博客同步在https://cnodejs.org/topic/56ed6735b705742136388fa6?
本博客同步在http://www.cnblogs.com/papertree/p/5295344.html
在上一篇博客(詳解四)講了 C++通過v8的Object類和FunctionTemplate類,創(chuàng)建對象、方法,設(shè)置屬性、原型方法等,提供給運行時的 js代碼調(diào)用。
那么這些C++實現(xiàn)的process對象、TCP類是否都在程序啟動的時候就創(chuàng)建到 js的執(zhí)行環(huán)境(context)呢?
不全是。process對象是(見5.2節(jié)),但 TCP類等C++內(nèi)建模塊不是(見5.3節(jié))。
5.1 在main函數(shù)啟動之前 —— C++內(nèi)建模塊的注冊
看一下C++內(nèi)建模塊的實現(xiàn)方法:
圖5-1-1
我們看到最后一行的NODE_MODULE_CONTEXT_AWARE_BUILTIN,通過這個方式來導(dǎo)出一個C++內(nèi)建模塊,這行代碼實現(xiàn)了什么?
5.1.1?NODE_MODULE_CONTEXT_AWARE_BUILTIN 宏
看下這個宏的實現(xiàn),在src/node.h文件:
圖5-1-2
5.1.2 NODE_C_CTOR宏與__attribute__((constructor))聲明 —— 在main函數(shù)前注冊
看到NODE_C_CTOR這個宏的作用時給傳進來的函數(shù)加上這么一個聲明,這是gcc的一個函數(shù)屬性聲明。
來自gcc文檔的說明:(地址:https://gcc.gnu.org/onlnedocs/gcc/Common-Function-Attributes.html#Common-Function-Attributes)
[?The?constructor?attribute causes the function to be called automatically before execution enters?main (). ]
?[筆者譯:constructor屬性導(dǎo)致該函數(shù)在程序進入main函數(shù)之前被執(zhí)行]
注:上面的 “.CRT$XCU”是微軟的編譯器實現(xiàn)類似功能的方法。
5.1.3 node_module_register函數(shù) 與 node::node_module結(jié)構(gòu)體的鏈表modlist_builtin
通過圖5-1-2,可以知道通過NODE_MODULE_CONTEXT_AWARE_BUILTIN( tcp_wrap, TCPWrap::Initialize )宏去注冊一個C++內(nèi)建模塊時,過程如下:
1. 把 tcp_wrap(modname)和 Initilize函數(shù)(regfunc)封裝成一個node_module類型的結(jié)構(gòu)體 _module
2.?調(diào)用node_module_register(&_module) 函數(shù)進行注冊。
圖5-1-3
可以看到這個函數(shù)把傳進來的m插入到內(nèi)建模塊的鏈表modlist_builtin。
?
5.1.4 總結(jié)
通過5.1節(jié),我們知道tcp_wrap.cc里面實現(xiàn)的C++內(nèi)建模塊,在main函數(shù)啟動之前把該內(nèi)建模塊的初始化函數(shù) TCPWrap::Initialize()保存到一個全局的靜態(tài)鏈表modlist_builtin。
那么這些內(nèi)建模塊的Initialize()什么時候執(zhí)行呢?里面可是創(chuàng)建了TCP類對應(yīng)的FunctionTemplate呢,在創(chuàng)建對應(yīng)的FunctionTemplate對象之前,V8的context里頭,js代碼還是沒得直接使用new TCP()呢。
5.2 process.binding —— C++內(nèi)建模塊的初始化與緩存
既然C++內(nèi)建模塊只是保存初始化函數(shù)到鏈表,而不真正創(chuàng)建一個js可以使用的函數(shù)對象(TCP類)到v8的上下文(context),那么初始化這個步驟明顯要交給 js代碼來控制了。
通過process.binding('tcp_wrap') 來引入C++內(nèi)建模塊創(chuàng)建的對象,如果第一次binding這個內(nèi)建模塊,那么就會調(diào)用Initialize函數(shù)。
那么你可能會問,C++內(nèi)建模塊就需要 js里面調(diào)用process.binding()來引入,那process對象也是C++提供的v8::Object類型的對象,為什么可以直接使用process.binding()呢?
因為內(nèi)建模塊并不是你js代碼一定會用到的,所以通過保存TCPWrap::Initialize()的方式,等到要用到時再通過process.binding()初始化。你要一開始就執(zhí)行初始化函數(shù),也是可以創(chuàng)建對應(yīng)的FunctionTemplate對象到v8上下文的。
而process這個對象在main函數(shù)啟動之后、執(zhí)行node.js之前就創(chuàng)建了,那么js代碼運行的上下文(context)里面就可以直接用了。具體代碼見5.3節(jié)。
來看一下process.binding()的過程:
[注:C++設(shè)置給process對象的binding方法是下面的Binding()函數(shù)]
圖5-2-1
1. Binding()函數(shù)通過要binding的模塊名,調(diào)用get_builtin_module(),從5.1.3中提到的內(nèi)建模塊鏈表modlist_buildin去獲取node::node_module結(jié)構(gòu)體對象
2. node_module對象里面的nm_context_register_func字段保存著對應(yīng)的初始化函數(shù)(即5.1.3中保存的TCPWrap::Initialize),圖中第2320行可以看出binding的時候執(zhí)行了初始化函數(shù)。
3. 最后把exports當成process.binding()的返回值。exports哪里被賦值呢?注意到nm_context_register_func其實是tcp_wrap.cc里面的TCPWrap::Initialize()函數(shù),回去圖5-1-1看看這個函數(shù),就會發(fā)現(xiàn)哦原來就是target。
?
?
5.3 process對象的初始化
在3.1.2節(jié)中講到這么一個流程:
main() ->?Start() -> StartNodeInstance() -> LoadEnviroment()
在LoadEnvrionment()里面去執(zhí)行node.js文件,并把process對象傳進去。
5.3.1 process初始化(js部分)
在LoadEnvrionment()執(zhí)行node.js文件,并傳process進去的時候,process.nextTick()這些函數(shù)是在node.js里面初始化的。
5.3.1 process初始化(C++部分)
在main() -> Start() -> StartNodeInstance() 這里,執(zhí)行LoadEnvironment()之前,先調(diào)用了CreateEnvironment()。
在CreateEnvironment()里面創(chuàng)建process對象,并且初始化C++部分的函數(shù)。看代碼:
圖5-3-1
1. 創(chuàng)建v8::Object process_object 在 CreateEnvironment()里面。
2. 給process.binding等賦值在SetupProcessObject()里面。
?
轉(zhuǎn)載于:https://www.cnblogs.com/xieweikai/p/6817671.html
總結(jié)
以上是生活随笔為你收集整理的node源码详解(五)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 逻辑表达式——黑纸白纸
- 下一篇: 51Nod - 1381 硬币游戏