N久以前的一個坑——用 Node.js 來重構 NBUT 的 Online Judge,包括評測端也得重構一遍。(至於何時完成你們就不要關心了,(/‵Д′)/~ ╧╧javascript
總之咱們如今要作的其實簡而言之就是——用C/C++來實現 Node.js 的模塊。html
工欲善其事,必先耍流氓利其器。java
首先你須要一個 node-gyp
模塊。node
在任意角落,執行:python
$ npm install node-gyp -g
在進行一系列的 blahblah
以後,你就安裝好了。git
而後你須要有個 python
環境。github
本身去官網搞一個來。shell
注意: 根據
node-gyp
的GitHub顯示,請務必保證你的python
版本介於2.5.0
和3.0.0
之間。npm
嘛嘛,我就偷懶點不細寫了,還請本身移步到 node-gyp 去看編譯器的需求。而且倒騰好。json
我就拿官網的入門 Hello World說事兒了。
請準備一個 C++
文件,好比就叫 sb.cc hello.cc。
而後咱們一步步來,先往裏面搞出頭文件和定義好命名空間:
#include <node.h> #include <v8.h> using namespace v8;
接下去咱們寫一個函數,其返回值是 Handle<Value>
。
Handle<Value> Hello(const Arguments& args) { //... 嗷嗷待寫 }
而後我來粗粗解析一下這些東西:
V8 裏使用 Handle 類型來託管 JavaScript 對象,與 C++ 的 std::sharedpointer 相似,Handle 類型間的賦值均是直接傳遞對象引用,但不一樣的是,V8 使用本身的 GC 來管理對象生命週期,而不是智能指針經常使用的引用計數。
JavaScript 類型在 C++ 中均有對應的自定義類型,如 String 、 Integer 、 Object 、 Date 、 Array 等,嚴格遵照在 JavaScript 中的繼承關係。 C++ 中使用這些類型時,必須使用 Handle 託管,以使用 GC 來管理它們的生命週期,而不使用原生棧和堆。
而這個所謂的 Value ,從 V8 引擎的頭文件 v8.h 中的各類繼承關係中能夠看出來,其實就是 JavaScript 中各類對象的基類。
在瞭解了這件事以後,咱們大體能明白上面那段函數的申明的意思就是說,咱們寫一個 Hello
函數,其返回的是一個不定類型的值。
注意: 咱們只能返回特定的類型,即在 Handle 託管下的 String 啊 Integer 啊等等等等。
這個就是傳入這個函數的參數了。咱們都知道在 Node.js
中,參數個數是亂來的。而這些參數傳進去到 C++
中的時候,就轉變成了這個 Arguments
類型的對象了。
具體的用法咱們在後面再說,在這裏只須要明白這個是個什麼東西就好。(爲毛要賣關子?由於 Node.js
官方文檔中的例子就是分開來說的,我如今只是講第一個 Hello World
的例子而已( ´థ౪థ)σ
接下去咱們就開始添磚加瓦了。就最簡單的兩句話:
Handle<Value> Hello(const Arguments& args) { HandleScope scope; return scope.Close(String::New("world")); }
這兩句話是什麼意思呢?大體的意思就是返回一個 Node.js
中的字符串 "world"
。
同參考自這裏。
Handle 的生命週期和 C++ 智能指針不一樣,並非在 C++ 語義的 scope 內生存(即{} 包圍的部分),而須要經過 HandleScope 手動指定。HandleScope 只能分配在棧上,HandleScope 對象聲明後,其後創建的 Handle 都由 HandleScope 來管理生命週期,HandleScope 對象析構後,其管理的 Handle 將由 GC 判斷是否回收。
因此呢,咱們得在須要管理他的生命週期的時候申明這個 Scope
。好的,那麼爲何咱們的代碼不這麼寫呢?
Handle<Value> Hello(const Arguments& args) { HandleScope scope; return String::New("world"); }
由於當函數返回時,scope
會被析構,其管理的Handle也都將被回收,因此這個 String
就會變得沒有意義。
因此呢 V8 就想出了個神奇的點子——HandleScope::Close(Handle<T> Value)
函數!這個函數的用處就是關閉這個 Scope 而且把裏面的參數轉交給上一個 Scope 管理,也就是進入這個函數前的 Scope。
因而就有了咱們以前的代碼 scope.Close(String::New("world"));
。
這個 String
類所對應的就是 Node.js
中原生的字符串類。繼承自 Value
類。與此相似,還有:
- Array
- Integer
- Boolean
- Object
- Date
- Number
- Function
- ...
這些東西有些是繼承自 Value
,有些是二次繼承。咱們這裏就很少作研究,本身能夠看看 V8 的代碼(至少是頭文件)研究研究或者看看這個手冊。
而這個 New
呢?這裏能夠看的。就是新建一個 String
對象。
至此,這個主要函數咱們就解析完畢了。
咱們來溫習一下,若是是在 Node.js
裏面寫的話,咱們怎麼導出函數或者對象什麼的呢?
exports.hello = function() {}
那麼,在 C++
中咱們該如何作到這一步呢?
首先,咱們寫個初始化函數:
void init(Handle<Object> exports) { //... 嗷嗷待寫你妹啊!#゚Å゚)⊂彡☆))゚Д゚)・∵ }
這是龜腚!函數名什麼的無所謂,可是傳入的參數必定是一個 Handle<Object>
,表明咱們下面將要在這貨上導出東西。
而後,咱們就在這裏面寫上導出的東西了:
void init(Handle<Object> exports) { exports->Set(String::NewSymbol("hello"), FunctionTemplate::New(Hello)->GetFunction()); }
大體的意思就是說,爲這個 exports
對象添加一個字段叫 hello
,所對應的東西是一個函數,而這個函數就是咱們親愛的 Hello
函數了。
用僞代碼寫直白點就是:
void init(Handle<Object> exports) { exports.Set("hello", function hello); }
大功告成!
(大功告成你妹啊!閉嘴( ‘д‘⊂彡☆))Д´)
這纔是最後一步,咱們最後要申明,這個就是導出的入口,因此咱們在代碼的末尾加上這一行:
NODE_MODULE(hello, init)
納了個尼?!這又是什麼東西?
彆着急,這個 NODE_MODULE
是一個宏,它的意思呢就是說咱們採用 init
這個初始化函數來把要導出的東西導出到 hello
中。那麼這個 hello
哪來呢?
**它來自文件名!**對,沒錯,它來自文件名。你並不須要事先申明它,你也沒必要擔憂不能用,總之你的這個最終編譯好的二進制文件名叫什麼,這裏的 hello
你就填什麼,固然要除去後綴名了。
詳見官方文檔。
Note that all Node addons must export an initialization function:
void Initialize (Handle<Object> exports); NODE_MODULE(module_name, Initialize)
There is no semi-colon after NODE_MODULE as it's not a function (see node.h).
The module_name needs to match the filename of the final binary (minus the .node suffix).
來吧,讓咱們一塊兒編譯吧!
咱們再新建一個相似於 Makefile
的歸檔文件吧——binding.gyp
。
而且在裏面添加這樣的代碼:
{ "targets": [ { "target_name": "hello", "sources": [ "hello.cc" ] } ] }
爲何這麼寫呢?能夠參考 node-gyp
的官方文檔。
在文件搞好以後,咱們要在這個目錄下面執行這個命令了:
$ node-gyp configure
若是一切正常的話,應該會生成一個 build
的目錄,而後裏面有相關文件,也許是 M$ Visual Studio 的 vcxproj
文件等,也許是 Makefile
,視平臺而定。
Makefile
也生成好以後,咱們就開始構造編譯了:
$ node-gyp build
等到一切編譯完成,纔算是真正的大功告成了!不信你去看看 build/Release
目錄,下面是否是有一個 hello.node
文件了?沒錯,這個就是 C++ 等下要給 Node.js 撿的肥皂!
咱們在剛纔那個目錄下新建一個文件 jianfeizao.js
:
var addon = require("./build/Release/hello"); console.log(addon.hello());
看到沒!看到沒!出來了出來了!Node.js 和 C++ 搞基的結果!這個 addon.hello()
就是咱們以前在 C++ 代碼中寫的 Handle<Value> Hello(const Arguments& args)
了,咱們如今就已經把它返回的值給輸出了。
時間不早了,今天就寫到這裏了,至此爲止你們都能搞出最基礎的 Hello world 的 C++ 擴展了吧。下一次寫的應該會更深刻一點,至於下一次是何時,我也不知道啦其實。 (喂喂喂,擼主怎麼能夠這麼不負責!(o゚ロ゚)┌┛Σ(ノ´ω`)ノ