上篇 V8 代码编译过之后,接下来就是把 Hello World 跑起来。我们之前已经编译出单个的 V8 静态库:libv8_monolith.a,然后再配合 V8 的头文件,创建一个 main.cc
文件,编译我们的第一个 Hello World。
目录结构如下:
v8-demo
|-- include
| |-- v8.h
| |-- ...
|-- libs
| |-- libv8_monolith.a
|-- src
| |-- main.cc
|-- CMakeLists.txt
CMakeLists.txt 内容:
cmake_minimum_required(VERSION 3.11)
project (v8_demo)
set(CMAKE_CXX_STANDARD 14)
add_definitions(-DV8_COMPRESS_POINTERS)
link_directories(${CMAKE_SOURCE_DIR}/libs)
include_directories(include)
add_executable(v8_demo src/main.cc)
target_link_libraries(v8_demo v8_monolith)
main.cc
内容:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "include/libplatform/libplatform.h"
#include "include/v8.h"
int main(int argc, char* argv[]) {
// Initialize V8.
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
// Create a new Isolate and make it the current one.
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
{
v8::Isolate::Scope isolate_scope(isolate);
// Create a stack-allocated handle scope.
v8::HandleScope handle_scope(isolate);
// Create a new context.
v8::Local<v8::Context> context = v8::Context::New(isolate);
// Enter the context for compiling and running the hello world script.
v8::Context::Scope context_scope(context);
// Create a string containing the JavaScript source code.
v8::Local<v8::String> source =
v8::String::NewFromUtf8(isolate, "'Hello' + ', World!'",
v8::NewStringType::kNormal)
.ToLocalChecked();
// Compile the source code.
v8::Local<v8::Script> script =
v8::Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
v8::String::Utf8Value utf8(isolate, result);
printf("%s\n", *utf8);
}
// Dispose the isolate and tear down V8.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete create_params.array_buffer_allocator;
return 0;
}
编译,执行:
$ mkdir -p build
$ cd build
$ cmake ..
$ make
$ ./v8-demo
Hello, World!
可以看到,使用 V8 主要分为几步:
- 初始化 V8
- 创建 v8::Isolate
- 创建 v8::Context
- 执行 JS
- 环境清理
1. 初始化 V8
V8 的初始化在一个进程内,仅且仅可初始化一次。由于我们之前编译的时候选择了:
v8_enable_i18n_support = false
v8_use_external_startup_data = false
所以,初始化的时候,不需要指定 ICU 多语言文件以及外部传入的 Snapshot 的 StartupData 文件。所以,初始化的必要步骤仅下面两步:
std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform();
v8::V8::InitializePlatform(platform.get());
v8::V8::Initialize();
第一步是初始化传入 v8::Platform。啥是 Platform 呢?
概念1:v8::Platform
v8::Platform 可以定制诸如内存页分配(PageAllocator),任务调度(NumberOfWorkerThreads、CallOnWorkerThread、IdleTasks、PostJob)等规则。详细可查看 v8-platform.h 的接口内容。通常情况下,这些都不需要定制,只使用默认的设置的话,可以通过 v8::platform::NewDefaultPlatform()
创建一个默认的配置。
/**
* Returns a new instance of the default v8::Platform implementation.
*
* The caller will take ownership of the returned pointer. |thread_pool_size|
* is the number of worker threads to allocate for background jobs. If a value
* of zero is passed, a suitable default based on the current number of
* processors online will be chosen.
* If |idle_task_support| is enabled then the platform will accept idle
* tasks (IdleTasksEnabled will return true) and will rely on the embedder
* calling v8::platform::RunIdleTasks to process the idle tasks.
* If |tracing_controller| is nullptr, the default platform will create a
* v8::platform::TracingController instance and use it.
*/
V8_PLATFORM_EXPORT std::unique_ptr<v8::Platform> NewDefaultPlatform(
int thread_pool_size = 0,
IdleTaskSupport idle_task_support = IdleTaskSupport::kDisabled,
InProcessStackDumping in_process_stack_dumping =
InProcessStackDumping::kDisabled,
std::unique_ptr<v8::TracingController> tracing_controller = {});
从上面的函数定义及注释可以看出,即使用默认的 Platform,也可以定制线程池大小(thread_pool_size),空闲任务(idle_task_suuport),TracingController 等。
2. 创建 v8::Isolate
v8::Isolate::CreateParams create_params;
create_params.array_buffer_allocator =
v8::ArrayBuffer::Allocator::NewDefaultAllocator();
v8::Isolate* isolate = v8::Isolate::New(create_params);
创建 v8::Isolate 时,需传入 CreateParams,可以指定 JavaScript 里的 ArrayBuffer 的内存分配器,这样可以对 JavaScript 的 ArrayBuffer 内存进行灵活的管理。当然也可以使用默认的分配器,如上面代码的 NewDefaultAllocator
。
概念2:v8::Isolate
Isolate
字面意思是隔离的意思,在 V8 中表示的是一个隔离的运行时环境,拥有自己的堆内存。多个不同的 Isolate 运行时可以并行
执行,互不干扰,数据也完全隔离不可互相访问,就像多个沙箱的运行环境。
通常,在一个线程中只会创建一个 Isolate 实例。创建 Isolate 后,需要进入
Isolate 才可对 Isolate 做进一步操作,在使用完毕后,需要再退出
Isolate。为了方便进入和自动退出,可以使用 v8::Isolate::Scope
进入和自动退出 Isolate。
3. 创建 v8::Context
v8::HandleScope handle_scope(isolate);
// Create a new context.
v8::Local<v8::Context> context = v8::Context::New(isolate);
// Enter the context for compiling and running the hello world script.
v8::Context::Scope context_scope(context);
概念3:v8::Context
可以从具体某个 Isolate 实例中创建一个新的 Context。 v8::Isolate 主要负责堆内存的隔离和管理,有点类似进程的概念。我们知道,需要执行代码的话,还需要有线程的概念。当然 v8 的 Context 和线程也不完全一样。v8::Context 包含了 JavaScript 代码执行所需的上下文信息,包括全局变量、函数等。可以这样简单理解,如果需要执行 JavaScript 代码,则需要有一个 Context 环境。
- 要点1:一个 Isolate 中可以创建多个 Context。
- 要点2:同个 Isolate 内的多个 Context 如果要在不同线程执行,需加
v8::Locker
锁 - 要点3:
尽量让 Context 在同个线程中执行
,如果需要在不同线程执行,也需要加v8::Locker
锁
和 Isolate 一样,Context 也需要进入才能正常操作,并在使用完毕后退出,可以使用 v8::Context::Scope
进入和自动退出。
我们注意到,v8::Context::New(isolate)
返回的类型是 v8::Local<v8::Context>
。为什么不是返回 v8::Context*
? 这和 V8 的内存对象管理有关。一方面,V8 不希望使用者直接访问 V8 对象的裸的指针地址。因为如果这些对象回收后,可能导致内存的非法访问。另一方面,也不利于 V8 在进行垃圾回收时,对 V8 对象在内存中进行移动(compact)。
因此,V8 返回的对象都通过 v8::Handle 来包一层,有点类似智能指针。v8::Local 是当前栈上的临时 handle,退出栈之后将不可再使用。为了让所有临时 handle 能自动回收,需要使用 v8::HandleScope 再包裹一层。
如果某个函数需要把某个 v8::Local 对象作为返回值返回出去,可以使用 v8::EscapableHandleScope
避免返回对象被自动回收:
Local<Array> NewPointArray(int x, int y, int z) {
v8::Isolate* isolate = v8::Isolate::GetCurrent();
// We will be creating temporary handles so we use a handle scope.
v8::EscapableHandleScope handle_scope(isolate);
// Create a new empty array.
v8::Local<v8::Array> array = v8::Array::New(isolate, 3);
// Return an empty result if there was an error creating the array.
if (array.IsEmpty())
return v8::Local<v8::Array>();
// Fill out the values
array->Set(0, Integer::New(isolate, x));
array->Set(1, Integer::New(isolate, y));
array->Set(2, Integer::New(isolate, z));
// Return the value through Escape.
return handle_scope.Escape(array);
}
概念4:v8 handle
前面已经讲了 v8::Local 这种临时 handle,除此之外,还包含其他类型的 handle:
- Local handle: v8::Local
- Persistent handle: v8::UniquePersistent 和 v8::Persistent
- v8::Eternal
当某个对象需要持有住,在后来的某个实际再去使用,可以使用 Persistent handle。v8::UniquePersistent 和 v8::Persistent 都是不可复制的。唯一的区别是,v8::UniquePersistent 会在析构时自动解除对该 JS 对象的引用。v8::Persistent 则需要开发者手动调用 Persistent::Reset 解除。
v8::Eternal 使用的很少,用于创建永远不会被 GC 的对象,直到环境被销毁。
4. 执行 JS
v8::Local<v8::String> source =
v8::String::NewFromUtf8(isolate, "'Hello' + ', World!'",
v8::NewStringType::kNormal)
.ToLocalChecked();
// Compile the source code.
v8::Local<v8::Script> script =
v8::Script::Compile(context, source).ToLocalChecked();
// Run the script to get the result.
v8::Local<v8::Value> result = script->Run(context).ToLocalChecked();
// Convert the result to an UTF8 string and print it.
v8::String::Utf8Value utf8(isolate, result);
printf("%s\n", *utf8);
将 C/C++ 字符串转成 JS 的字符串,需要使用 v8::String::NewFromUtf8。然后通过 v8::Script::Compile 对 JS 源码字符串进行编译,得到 v8::Script 对象,然后执行 Run 方法,执行该代码并拿到 v8::Value 结果。最后,将 v8::Value 转回 v8::String::Utf8Value 字符串对象,并打印。
这里涉及到了 C/C++ 中的基础类型和 V8 JS 对象的互相转换。举例如下:
// bool
v8::Local<v8::Boolean> js_bool_value = v8::Boolean::New(isolate, true);
bool bool_value = js_bool_value->BooleanValue(isolate);
// int
v8::Local<v8::Number> js_number = v8::Integer::New(isolate, 12);
int number = js_number->Int32Value(isolate);
// float
v8::Local<v8::Number> js_float_number = v8::Number::New(isolate, 12.25);
float float_number = static_cast<float>(js_float_number->NumberValue(isolate));
// string
v8::Local<v8::String> js_string_value = v8::String::NewFromUtf8(isolate, "str", v8::NewStringType::kNormal);
v8::String::Utf8Value const utf8(isolate, js_string_value);
std::string string_value(*utf8, utf8.length());
涉及到更复杂的数据结构转换的话,比如 list, map, ArrayBuffer,以及对象映射,到后面再介绍。
5. 环境清理
// Dispose the isolate and tear down V8.
isolate->Dispose();
v8::V8::Dispose();
v8::V8::ShutdownPlatform();
delete create_params.array_buffer_allocator;
需对 isolate 执行 Dispose 清理,以及整个 V8 环境进行清理。(Context 不需要主动清理,因为 Context 是包含在 isolate 内的)
总结
代码示例见:https://github.com/coderzh/v8-demo
本篇介绍了 V8 的基本使用方法以及几个基本概念,可以此为基础快速了解 V8 引擎的基本使用。后面将逐步深入到 V8 内部,尽请期待。
作者:CoderZh
微信关注:hacker-thinking (代码随想)
本文出处:https://blog.coderzh.com/2021/08/22/v8-4/
文章版权归本人所有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。