參考文章-寫得不錯 都說node的一些底層藉助了一些c++函數,到底如何交互的以及爲什麼會用到C++
node爲什麼會用到C++
通過使用 C++ 函數,可以極大程度的增強 JavaScript 腳本的能力,如文件讀寫,網絡 / 數據庫訪問,圖形 / 圖像處理等等
C++ 和 JS 交互
本章主要來講講如何通過 V8 來實現 JS 調用 C++。JS 調用 C++,分爲 JS 調用 C++ 函數(全局),和調用 C++ 類。
數據及模板
由於 C++ 原生數據類型與 JavaScript 中數據類型有很大差異,因此 V8 提供了 Value 類,從 JavaScript 到 C++,從 C++ 到 JavaScrpt 都會用到這個類及其子類,比如:
Handle<Value> Add(const Arguments& args){ int a = args[0]->Uint32Value(); int b = args[1]->Uint32Value(); return Integer::New(a+b); }
Integer 即爲 Value 的一個子類。
V8 中,有兩個模板 (Template) 類 (並非 C++ 中的模板類):
- 對象模板 (ObjectTemplate)
- 函數模板 (FunctionTemplate) 這兩個模板類用以定義 JavaScript 對象和 JavaScript 函數。我們在後續的小節部分將會接觸到模板類的實例。通過使用 ObjectTemplate,可以將 C++ 中的對象暴露給腳本環境,類似的,FunctionTemplate 用以將 C++ 函數暴露給腳本環境,以供腳本使用。
JS 使用 C++ 變量
在 JavaScript 與 V8 間共享變量事實上是非常容易的,基本模板如下:
static char sname[512] = {0}; static Handle<Value> NameGetter(Local<String> name, const AccessorInfo& info) { return String::New((char*)&sname,strlen((char*)&sname)); } static void NameSetter(Local<String> name, Local<Value> value, const AccessorInfo& info) { Local<String> str = value->ToString(); str->WriteAscii((char*)&sname); }
定義了 NameGetter, NameSetter 之後,在 main 函數中,將其註冊在 global 上:
// Create a template for the global object. Handle<ObjectTemplate> global = ObjectTemplate::New(); //public the name variable to script global->SetAccessor(String::New("name"), NameGetter, NameSetter);
JS 調用 C++ 函數
在 JavaScript 中調用 C++ 函數是腳本化最常見的方式,通過使用 C++ 函數,可以極大程度的增強 JavaScript 腳本的能力,如文件讀寫,網絡 / 數據庫訪問,圖形 / 圖像處理等等,類似於 JAVA 的 jni 技術。
在 C++ 代碼中,定義以下原型的函數:
Handle<Value> func(const Arguments& args){//return something}
然後,再將其公開給腳本:
global->Set(String::New("func"),FunctionTemplate::New(func));
JS 使用 C++ 類
如果從面向對象的視角來分析,最合理的方式是將 C++ 類公開給 JavaScript,這樣可以將 JavaScript 內置的對象數量大大增加,從而儘可能少的使用宿主語言,而更大的利用動態語言的靈活性和擴展性。事實上,C++ 語言概念衆多,內容繁複,學習曲線較 JavaScript 遠爲陡峭。最好的應用場景是:既有腳本語言的靈活性, 又有 C/C++ 等系統語言的效率。使用 V8 引擎,可以很方便的將 C++ 類” 包裝” 成可供 JavaScript 使用的資源。
我們這裏舉一個較爲簡單的例子,定義一個 Person 類,然後將這個類包裝並暴露給 JavaScript 腳本,在腳本中新建 Person 類的對象,使用 Person 對象的方法。 首先,我們在 C++ 中定義好類 Person:
class Person { private: unsigned int age; char name[512]; public: Person(unsigned int age, char *name) { this->age = age; strncpy(this->name, name, sizeof(this->name)); } unsigned int getAge() { return this->age; } void setAge(unsigned int nage) { this->age = nage; } char *getName() { return this->name; } void setName(char *nname) { strncpy(this->name, nname, sizeof(this->name)); } };
Person 類的結構很簡單,只包含兩個字段 age 和 name,並定義了各自的 getter/setter. 然後我們來定義構造器的包裝:
Handle<Value> PersonConstructor(const Arguments& args){ Handle<Object> object = args.This(); HandleScope handle_scope; int age = args[0]->Uint32Value(); String::Utf8Value str(args[1]); char* name = ToCString(str); Person *person = new Person(age, name); object->SetInternalField(0, External::New(person)); return object; }
從函數原型上可以看出,構造器的包裝與上一小節中,函數的包裝是一致的,因爲構造函數在 V8 看來,也是一個函數。需要注意的是, 從 args 中獲取參數並轉換爲合適的類型之後,我們根據此參數來調用 Person 類實際的構造函數,並將其設置在 object 的內部字段中。緊接着,我們需要包裝 Person 類的 getter/setter:
Handle<Value> PersonGetAge(const Arguments& args){ Local<Object> self = args.Holder(); Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); void *ptr = wrap->Value(); return Integer::New(static_cast<Person*>(ptr)->getAge()); } Handle<Value> PersonSetAge(const Arguments& args) { Local<Object> self = args.Holder(); Local<External> wrap = Local<External>::Cast(self->GetInternalField(0)); void* ptr = wrap->Value(); static_cast<Person*>(ptr)->setAge(args[0]->Uint32Value()); return Undefined(); }
而 getName 和 setName 的與上例類似。在對函數包裝完成之後,需要將 Person 類暴露給腳本環境: 首先,創建一個新的函數模板,將其與字符串”Person” 綁定,並放入 global:
Handle<FunctionTemplate> person_template = FunctionTemplate::New(PersonConstructor); person_template->SetClassName(String::New("Person")); global->Set(String::New("Person"), person_template);
然後定義原型模板:
Handle<ObjectTemplate> person_proto = person_template->PrototypeTemplate(); person_proto->Set("getAge", FunctionTemplate::New(PersonGetAge)); person_proto->Set("setAge", FunctionTemplate::New(PersonSetAge)); person_proto->Set("getName", FunctionTemplate::New(PersonGetName)); person_proto->Set("setName", FunctionTemplate::New(PersonSetName));
最後設置實例模板:
Handle<ObjectTemplate> person_inst = person_template->InstanceTemplate(); person_inst->SetInternalFieldCount(1);
C++ 調用 JS 函數
我們直接看下 src/timer_wrap.cc 的例子,V8 編譯執行 timer.js, 構造了 Timer 對象。
static void OnTimeout(uv_timer_t* handle) { TimerWrap* wrap = static_cast<TimerWrap*>(handle->data); Environment* env = wrap->env(); HandleScope handle_scope(env->isolate()); Context::Scope context_scope(env->context()); wrap->MakeCallback(kOnTimeout, 0, nullptr); } inline v8::Local<v8::Value> AsyncWrap::MakeCallback(uint32_t index, int argc, v8::Local<v8::Value>* argv) { v8::Local<v8::Value> cb_v = object()->Get(index); CHECK(cb_v->IsFunction()); return MakeCallback(cb_v.As<v8::Function>(), argc, argv); }
TimerWrap
對象通過數組的索引尋址,找到 Timer 對象索引 0 的對象,而對其賦值的是在 lib/timer.js 裏面的
list._timer[kOnTimeout] = listOnTimeout;
。這邊找到的對象是個 Function
,
後面忽略 domains 異常處理等,就是簡單的調用 Function 對象的 Call 方法, 並且傳人上文提到的 Context 和參數。
Local<Value> ret = callback->Call(recv, argc, argv);
這就實現了 C++ 對 JS 函數的調用。