node源码详解(四) —— js代码如何调用C++的函数
本作品采用知識(shí)共享署名 4.0 國際許可協(xié)議進(jìn)行許可。轉(zhuǎn)載保留聲明頭部與原文鏈接https://luzeshu.com/blog/nodesource4?
本博客同步在https://cnodejs.org/topic/56ed249356d74f3d3624b3ff?
本博客同步在http://www.cnblogs.com/papertree/p/5285705.html
上面講到node調(diào)用Script::Compile()和Script::Run()解析執(zhí)行app.js,并把io操作和callback保存到default_loop_struct,那么app.js里面js代碼如何調(diào)用C++的函數(shù)呢?
在4.2節(jié)進(jìn)行解釋,先在4.1節(jié)來點(diǎn)知識(shí)預(yù)熱。
4.1?V8運(yùn)行js代碼的基礎(chǔ)知識(shí) —— V8的上下文
來看看google V8開發(fā)者文檔的一點(diǎn)介紹:(地址:https://developers.google.com/v8/get_started)
- A?context?is an execution environment that allows separate, unrelated, JavaScript code to run in a single instance of V8. You must explicitly specify the context in which you want any JavaScript code to be run.
大概意思就是context(上下文)是用來執(zhí)行javascript代碼的運(yùn)行環(huán)境,而且運(yùn)行javascript代碼的時(shí)候必須指定一個(gè)context。
從文檔里面摘了一段hello world代碼:
int main(int argc, char* argv[]) {// Initialize V8. V8::InitializeICU();V8::InitializeExternalStartupData(argv[0]);Platform* platform = platform::CreateDefaultPlatform();V8::InitializePlatform(platform);V8::Initialize();// Create a new Isolate and make it the current one. ArrayBufferAllocator allocator;Isolate::CreateParams create_params;create_params.array_buffer_allocator = &allocator;Isolate* isolate = Isolate::New(create_params);{Isolate::Scope isolate_scope(isolate);// Create a stack-allocated handle scope. HandleScope handle_scope(isolate);// Create a new context.Local<Context> context = Context::New(isolate);// Enter the context for compiling and running the hello world script. Context::Scope context_scope(context);// Create a string containing the JavaScript source code.Local<String> source =String::NewFromUtf8(isolate, "'Hello' + ', World!'",NewStringType::kNormal).ToLocalChecked();// Compile the source code.Local<Script> script = Script::Compile(context, source).ToLocalChecked();// Run the script to get the result.Local<Value> result = script->Run(context).ToLocalChecked();// Convert the result to an UTF8 string and print it. String::Utf8Value utf8(result);printf("%s\n", *utf8);}// Dispose the isolate and tear down V8.isolate->Dispose();V8::Dispose();V8::ShutdownPlatform();delete platform;return 0; }?
你可能會(huì)發(fā)現(xiàn),上面說了script->Run(context)?一定要指定一個(gè)context。那么看回3.1.2 中的圖3-1-3,node.cc里面的script->Run()并沒有context參數(shù)。
跳到v8的源碼,deps/v8/src/api.cc,就會(huì)發(fā)現(xiàn)這實(shí)際上是兩個(gè)重載函數(shù),無參Script::Run()會(huì)先從Script對象取得當(dāng)前的context,再調(diào)用Script::Run(Local<Context> context)。
?
圖4-1-1
?
?
4.2 理解js代碼如何調(diào)用C++函數(shù) —— 運(yùn)行時(shí)的上下文
看個(gè)例子:
左邊為node 原生lib模塊網(wǎng)絡(luò)socket操作部分的文件?—— net.js,我們平時(shí)使用server.listen()時(shí),最終調(diào)用到net.js里面,先通過new TCP()創(chuàng)建一個(gè)handle對象,再調(diào)用handle.listen()。而這個(gè)TCP和listen,均來自左邊tcp_wrap.cc文件。
也就是說,通過net.js里面的handle.listen()調(diào)用了tcp_wrap.cc里面的TCPWrap::Listen()函數(shù),并且傳給handle.listen()的 js參數(shù)—— backlog,被包裝到了C++的 FunctionCallbackInfo<Value>類對象args。
圖4-2-1?
?
如果你第一感覺是js代碼調(diào)用C++代碼無法理解,那么一定是受到“語法”的干擾。
確實(shí),從靜態(tài)的角度來看,js和C++是兩種語言,語法不互通,直接在js代碼調(diào)用C++函數(shù)那是不可能的。
那么,從動(dòng)態(tài)的角度(運(yùn)行時(shí))來看呢?別忘了,任何編程語言最終運(yùn)行起來都不過是進(jìn)程空間里的二進(jìn)制代碼和數(shù)據(jù)。
?
?
圖 4-2-2
4.2.1 從js代碼到context
4.1 中已經(jīng)講了,Script::Compile()和Script::Run() 的時(shí)候必須為 js代碼指定一個(gè)運(yùn)行環(huán)境(context)。那么 js代碼和context的關(guān)聯(lián)是很自然的。
4.2.2 設(shè)置C++函數(shù)到context
那么,上圖藍(lán)色標(biāo)號1-5這幾個(gè)步驟,即在C++代碼層面,把C++函數(shù)設(shè)置到context的細(xì)節(jié)和相應(yīng)的V8 接口是什么呢?
?
4.3 node的js模塊調(diào)用C++模塊的細(xì)節(jié)
在node里面,在C++代碼里面提供給運(yùn)行時(shí)javascript代碼使用的無非就是這幾種:
1.?一個(gè)對象(比如process),對象上設(shè)置屬性(比如process.versions)、或者方法(比如process._kill)
2.?函數(shù)對象(比如TCP),設(shè)置原型方法(比如TCP.prototype.listen)
4.3.1 ?process對象 —— V8的Object類
在3.2中講到,main函數(shù)啟動(dòng)后會(huì)加載執(zhí)行src/node.js文件,并且把process對象傳給node.js文件,在里面設(shè)置process.nextTick()等方法。
那么來看看 C++如何創(chuàng)建一個(gè)給js使用的對象。
4.3.1.1 類型
回去3.1.2節(jié)看一下“圖3-1-3”。在LoadEnvironment() 里面執(zhí)行 f->Call()調(diào)用node.js里的匿名函數(shù)時(shí),傳過去的process對象是通過env->process_object()獲取的。
env->process_object()的實(shí)現(xiàn)如下:
圖 4-3-1
這里是個(gè)宏,展開就是
inline v8::Local<v8::Object> Environment::process_object() const {return StrongPersistentToLocal(process_object_); }?
那么上面標(biāo)紅的process_object_ 成員,定義如下:
圖 4-3-2
這里也是一個(gè)宏,展開就是
class Environment {v8::Persistent<v8::Object> process_object_; }那么這里可以看到,C++里面提供給js代碼的對象,就是一個(gè)v8::Object類型的對象。
4.3.1.2 設(shè)置屬性或方法
那么,v8::Object類型的對象如何在C++里面設(shè)置屬性呢?
圖4-3-3
這里可以看到,v8::Object類提供了Set()方法,來讓你設(shè)置供js訪問的屬性或方法。
?
4.3.2 TCP類 —— v8的FunctionTemplate類
那么第二種類型,就是設(shè)置prototype方法。在js里面,沒有真正的類的概念,而是通過給函數(shù)對象TCP的prototype屬性設(shè)置方法,使用的時(shí)候通過new TCP()去創(chuàng)建實(shí)例。
那么,v8如何設(shè)置原型方法?
4.3.2.1 設(shè)置原型方法
圖4-3-4
這里可以看到,通過創(chuàng)建一個(gè)v8::FunctionTemplate類型的對象 t,通過 t->PrototypeTemplate() 去獲取函數(shù)對象的prototype,并進(jìn)一步調(diào)用Set()去設(shè)置prototype上的方法。
最后再通過 t->GetFunction() 去獲取一個(gè)該函數(shù)模版的方法。
注:關(guān)于 js文件process.binding('tcp_wrap')引入TCP函數(shù)對象的機(jī)制,在下一篇博客講。
?
轉(zhuǎn)載于:https://www.cnblogs.com/papertree/p/5285705.html
總結(jié)
以上是生活随笔為你收集整理的node源码详解(四) —— js代码如何调用C++的函数的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 软件测试 实验一
- 下一篇: 辅助判卷程序项目的扩展--自动出题