node中和C++有什麼關聯

參考文章-寫得不錯 都說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 函數的調用。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章