嵌入V8的核心概念

V8 是一個獨立運行的虛擬機, 其中有些關(guān)鍵性概念比如: handles, scopes 和 contexts. 本文將深入討論這些概念。

本文是為那些想要將 V8 Javascript 引擎嵌入到 C++ 程序中的工程師而撰寫。在升入理解Node.js之前,閱讀相關(guān)文章是很有必要的。

本文整理自Embedder's Guide
V8 嵌入指南(中文)
代碼來自于v8源碼中的例子

v8源碼中有三個例子。最簡單的是打印“hello world”。shell程序是用來演示如何暴露native function到JavaScript。process則演示C++如何調(diào)用JavaScript函數(shù)。我們通過例子來講解一下v8的核心概念。我使用的v8源碼版本是3.29.88.

下面主要通過shell.cc的代碼來講解概念。

啟動

我們看一下啟動代碼

int main(int argc, char* argv[]) {
  v8::V8::InitializeICU();
  v8::Platform* platform = v8::platform::CreateDefaultPlatform();
  v8::V8::InitializePlatform(platform);
  v8::V8::Initialize();
  v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
  ShellArrayBufferAllocator array_buffer_allocator;
  v8::V8::SetArrayBufferAllocator(&array_buffer_allocator);
  v8::Isolate* isolate = v8::Isolate::New();
...

  • v8::V8::InitializeICU();

What is ICU?
ICU is a cross-platform Unicode based globalization library. It includes support for locale-sensitive string comparison, date/time/number/currency/message formatting, text boundary detection, character set conversion and so on.You can build V8 without ICU library with build option i18nsupport=off. In this case you need to initialize builtin ICU:
v8::V8::InitializeICU();

ICU是一個國際化的庫,我們不是特別關(guān)心,所以略過。


  • v8::Platform* platform = v8::platform::CreateDefaultPlatform();

然后就要創(chuàng)建一個Platform,這個東西從注釋來看是一個是對整個嵌入v8的程序的一個抽象概念,看看他的私有變量我們可以大概知道他是干嘛的

static const int kMaxThreadPoolSize;

  base::Mutex lock_;
  bool initialized_;
  int thread_pool_size_;
  std::vector<WorkerThread*> thread_pool_;
  TaskQueue queue_;
  std::map<v8::Isolate*, std::queue<Task*> > main_thread_queue_;

在看看接口

/**
 * V8 Platform abstraction layer.
 *
 * The embedder has to provide an implementation of this interface before
 * initializing the rest of V8.
 */
class Platform {
 public:
  /**
   * This enum is used to indicate whether a task is potentially long running,
   * or causes a long wait. The embedder might want to use this hint to decide
   * whether to execute the task on a dedicated thread.
   */
  enum ExpectedRuntime {
    kShortRunningTask,
    kLongRunningTask
  };

  virtual ~Platform() {}

  /**
   * Schedules a task to be invoked on a background thread. |expected_runtime|
   * indicates that the task will run a long time. The Platform implementation
   * takes ownership of |task|. There is no guarantee about order of execution
   * of tasks wrt order of scheduling, nor is there a guarantee about the
   * thread the task will be run on.
   */
  virtual void CallOnBackgroundThread(Task* task,
                                      ExpectedRuntime expected_runtime) = 0;

  /**
   * Schedules a task to be invoked on a foreground thread wrt a specific
   * |isolate|. Tasks posted for the same isolate should be execute in order of
   * scheduling. The definition of "foreground" is opaque to V8.
   */
  virtual void CallOnForegroundThread(Isolate* isolate, Task* task) = 0;
};

我們可以了解到,Platform用來管理isolate,確定他是在后臺線程還是前臺線程運行,管理線程池等。


  • v8::V8::Initialize();
    從代碼里面看做了很多事情,目前還沒有看升入,以后再補上吧。

  • v8::V8::SetFlagsFromCommandLine(&argc, argv, true);
    這個用來接收命令行參數(shù),比如shell --help,可以從這里學(xué)習(xí)到如何編寫支持命令行參數(shù)的程序。
    命令行

  • ShellArrayBufferAllocator array_buffer_allocator;
    v8::V8::SetArrayBufferAllocator(&array_buffer_allocator);
    設(shè)置數(shù)組的分配器,所有的isolate都可以使用,分配數(shù)組的時候我們可以指定一個自己實現(xiàn)的高效的分配器。

  • v8::Isolate* isolate = v8::Isolate::New();

表示一個單獨的v8引擎實例,Isolate有完全獨立的狀態(tài),對象在isolate之間不能共享。我們可以創(chuàng)建多個isolate,然后再不同的線程中使用。isolate在一個時刻只能由一個線程執(zhí)行,多線程時必須加鎖保證同步。


  • v8::Isolate::Scope isolate_scope(isolate);
    isolate的一個范圍,先不糾結(jié)這是干嘛的。從概念上說是用來給每個引擎設(shè)置一個單獨執(zhí)行的環(huán)境。該對象只能在棧上分配。

  • v8::HandleScope handle_scope(isolate);
    管理本地Handle,下面我們來說handle是啥。

  • v8::Handle<v8::Context> context = CreateShellContext(isolate);

在v8中,context是有自己內(nèi)置的函數(shù)和對象的一個執(zhí)行環(huán)境,這里context被handle管理了,handle被上面說的handlescope管理。為什么要有context呢,因為JavaScript是非常動態(tài)的,每個JavaScript代碼執(zhí)行的全局對象和默認環(huán)境都可能不一樣,比如一個程序修改一個全局對象,那么如果沒有context的抽象,所以得JavaScript都得執(zhí)行在全局對象被修改的環(huán)境中了。

當(dāng)你創(chuàng)建了一個context后,你可以使用它很多次,當(dāng)你在contextA的時候,你可以進入contextB,意思就是context是一個嵌套結(jié)構(gòu)或者說是一個棧結(jié)構(gòu),退出ciontextB的時候又恢復(fù)成contextA。可以看下圖。


context

為什么要有handle呢,我們可以直接用指針持有context,原因是context中的對象和函數(shù)是由v8來管理的,而且v8可以移動他們,所以指針的地址會變,所以用handle來管理他們,增加了一層抽象,我們就不用管v8如何處理這些內(nèi)存了。當(dāng)handle釋放的時候,v8可以自己幫我們銷毀js對象。

handle

  • v8::Context::Scope context_scope(context);
    Stack-allocated class which sets the execution context for all
    operations executed within a local scope. 這個不翻譯了,因為沒有仔細進去看代碼,怕解釋錯了。

  • result = RunMain(isolate, argc, argv);
    if (run_shell) RunShell(context);
    RunMain處理用戶輸入的參數(shù),前面雖然處理過了,這里還有一些額外的要處理。
    RunShell運行命令行,等待用戶輸入js代碼。
    我們運行看看,發(fā)現(xiàn)js里面沒有的函數(shù)print現(xiàn)在盡然有了,O(∩_∩)O~。


    print函數(shù)

我們下面看看如何給context增加對象,代碼就在CreateShellContext中。

增加內(nèi)置對象

// Creates a new execution environment containing the built-in
// functions.
v8::Handle<v8::Context> CreateShellContext(v8::Isolate* isolate) {
  // Create a template for the global object.
  v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
  // Bind the global 'print' function to the C++ Print callback.
  global->Set(v8::String::NewFromUtf8(isolate, "print"),
              v8::FunctionTemplate::New(isolate, Print));
  // Bind the global 'read' function to the C++ Read callback.
  global->Set(v8::String::NewFromUtf8(isolate, "read"),
              v8::FunctionTemplate::New(isolate, Read));
  // Bind the global 'load' function to the C++ Load callback.
  global->Set(v8::String::NewFromUtf8(isolate, "load"),
              v8::FunctionTemplate::New(isolate, Load));
  // Bind the 'quit' function
  global->Set(v8::String::NewFromUtf8(isolate, "quit"),
              v8::FunctionTemplate::New(isolate, Quit));
  // Bind the 'version' function
  global->Set(v8::String::NewFromUtf8(isolate, "version"),
              v8::FunctionTemplate::New(isolate, Version));

  return v8::Context::New(isolate, NULL, global);
}


// The callback that is invoked by v8 whenever the JavaScript 'print'
// function is called.  Prints its arguments on stdout separated by
// spaces and ending with a newline.
void Print(const v8::FunctionCallbackInfo<v8::Value>& args) {
  bool first = true;
  for (int i = 0; i < args.Length(); i++) {
    v8::HandleScope handle_scope(args.GetIsolate());
    if (first) {
      first = false;
    } else {
      printf(" ");
    }
    v8::String::Utf8Value str(args[i]);
    const char* cstr = ToCString(str);
    printf("%s", cstr);
  }
  printf("\n");
  fflush(stdout);
}

我們可以看到上面的代碼在global下面掛了幾個c++函數(shù)。有幾個概念我們要搞清楚。

  • v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New(isolate);
    ObjectTemplate是用來在runtime中創(chuàng)建對象的。

  • global->Set(v8::String::NewFromUtf8(isolate, "print"), v8::FunctionTemplate::New(isolate, Print));
    在全局對象上放print函數(shù)

  • return v8::Context::New(isolate, NULL, global);
    返回執(zhí)行上下文


好了,shell.cc的主要代碼就解說完了,但是還有很多核心概念沒有接觸到,比如Function templates。我們在過一下其他例子,看看有什么新的知識。


推薦一個對官方文檔翻譯的文章V8_Embedder's_Guide_CHS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,030評論 6 531
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 98,310評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,951評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,796評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 71,566評論 6 407
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,055評論 1 322
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,142評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 42,303評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 48,799評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 40,683評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,899評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,409評論 5 358
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,135評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,520評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,757評論 1 282
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,528評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 47,844評論 2 372

推薦閱讀更多精彩內(nèi)容