嵌入V8的核心概念

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

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

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

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

下面主要通過(guò)shell.cc的代碼來(lái)講解概念。

啟動(dòng)

我們看一下啟動(dòng)代碼

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是一個(gè)國(guó)際化的庫(kù),我們不是特別關(guān)心,所以略過(guò)。


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

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

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用來(lái)管理isolate,確定他是在后臺(tái)線程還是前臺(tái)線程運(yùn)行,管理線程池等。


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

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

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

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

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


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

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

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

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

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


context

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

handle

  • v8::Context::Scope context_scope(context);
    Stack-allocated class which sets the execution context for all
    operations executed within a local scope. 這個(gè)不翻譯了,因?yàn)闆]有仔細(xì)進(jìn)去看代碼,怕解釋錯(cuò)了。

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


    print函數(shù)

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

增加內(nèi)置對(duì)象

// 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下面掛了幾個(gè)c++函數(shù)。有幾個(gè)概念我們要搞清楚。

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

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

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


好了,shell.cc的主要代碼就解說(shuō)完了,但是還有很多核心概念沒有接觸到,比如Function templates。我們?cè)谶^(guò)一下其他例子,看看有什么新的知識(shí)。


推薦一個(gè)對(duì)官方文檔翻譯的文章V8_Embedder's_Guide_CHS

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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