学习本文的收益: 1.掌握如何使用V8引擎 2.对于小程序V8 Worker的理解更深刻
3.了解JsApi的注入和调用原理 本文学习V8 Worker层 模仿my.call (jsapi)
环境
1.xcode 2.git 下载
1 git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
添加到PATH
1 export PATH=/path/to/depot_tools:$PATH
编译
1.fetch 使用depot_tools提供的fetch,本地会生成.gclient,有点类似repo,mkit工具
1 2 3 mkdir ~/v8 cd ~/v8 fetch v8
fetch v8 会自动触发gclient sync,如果我们要切换分支,我们应该终止gclient sync,然后切换分支,最后手动gclient sync
.gclient文件,如果是fetch [android | ios] 这里将会有一些差异
1 2 3 4 5 6 7 8 9 solutions = [ { "name" : "v8" , "url" : "https://chromium.googlesource.com/v8/v8.git" , "deps_file" : "DEPS" , "managed" : False, "custom_deps" : {}, }, ]
.gclient_entries文件 ,多个git仓库本地映射
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 entries = { 'v8' : 'https://chromium.googlesource.com/v8/v8.git' , 'v8/base/trace_event/common' : 'https://chromium.googlesource.com/chromium/src/base/trace_event/common.git@cfe8887fa6ac3170e23a68949930e28d4705a16f' , 'v8/build' : 'https://chromium.googlesource.com/chromium/src/build.git@1e5d7d692f816af8136c738b79fe9e8dde8057f6' , 'v8/buildtools' : 'https://chromium.googlesource.com/chromium/src/buildtools.git@67b293ca1316d06f7f00160ce35c92b8849a9dc9' , 'v8/buildtools/clang_format/script' : 'https://chromium.googlesource.com/chromium/llvm-project/cfe/tools/clang-format.git@96636aa0e9f047f17447f2d45a094d0b59ed7917' , 'v8/buildtools/mac:gn/gn/mac-amd64' : 'https://chrome-infra-packages.appspot.com/gn/gn/mac-amd64@git_revision:972ed755f8e6d31cae9ba15fcd08136ae1a7886f' , 'v8/buildtools/third_party/libc++/trunk' : 'https://chromium.googlesource.com/chromium/llvm-project/libcxx.git@5938e0582bac570a41edb3d6a2217c299adc1bc6' , 'v8/buildtools/third_party/libc++abi/trunk' : 'https://chromium.googlesource.com/chromium/llvm-project/libcxxabi.git@0d529660e32d77d9111912d73f2c74fc5fa2a858' , 'v8/buildtools/third_party/libunwind/trunk' : 'https://chromium.googlesource.com/external/llvm.org/libunwind.git@69d9b84cca8354117b9fe9705a4430d789ee599b' , 'v8/test/benchmarks/data' : 'https://chromium.googlesource.com/v8/deps/third_party/benchmarks.git@05d7188267b4560491ff9155c5ee13e207ecd65f' , 'v8/test/mozilla/data' : 'https://chromium.googlesource.com/v8/deps/third_party/mozilla-tests.git@f6c578a10ea707b1a8ab0b88943fe5115ce2b9be' , 'v8/test/test262/data' : 'https://chromium.googlesource.com/external/github.com/tc39/test262.git@26a2268436f28f64c4539d9aab9ebd0f0b7c99c5' , 'v8/test/test262/harness' : 'https://chromium.googlesource.com/external/github.com/test262-utils/test262-harness-py.git@4555345a943d0c99a9461182705543fb171dda4b' , 'v8/test/wasm-js/data' : 'https://chromium.googlesource.com/external/github.com/WebAssembly/spec.git@1a411f713d9850ce7da24719aba5bb80c535f562' , 'v8/third_party/depot_tools' : 'https://chromium.googlesource.com/chromium/tools/depot_tools.git@ee7b9dda90e409fb92031d511151debe5db7db9f' , 'v8/third_party/googletest/src' : 'https://chromium.googlesource.com/external/github.com/google/googletest.git@6077f444da944d96d311d358d761164261f1cdd0' , 'v8/third_party/icu' : 'https://chromium.googlesource.com/chromium/deps/icu.git@fd97d4326fac6da84452b2d5fe75ff0949368dab' , 'v8/third_party/instrumented_libraries' : 'https://chromium.googlesource.com/chromium/src/third_party/instrumented_libraries.git@b1c3ca20848c117eb935b02c25d441f03e6fbc5e' , 'v8/third_party/jinja2' : 'https://chromium.googlesource.com/chromium/src/third_party/jinja2.git@b41863e42637544c2941b574c7877d3e1f663e25' , 'v8/third_party/markupsafe' : 'https://chromium.googlesource.com/chromium/src/third_party/markupsafe.git@8f45f5cfa0009d2a70589bcda0349b8cb2b72783' , 'v8/third_party/perfetto' : 'https://android.googlesource.com/platform/external/perfetto.git@0e8281399fd854de13461f2c1c9f2fb0b8e9c3ae' , 'v8/third_party/protobuf' : 'https://chromium.googlesource.com/external/github.com/google/protobuf@b68a347f56137b4b1a746e8c7438495a6ac1bd91' , 'v8/tools/clang' : 'https://chromium.googlesource.com/chromium/src/tools/clang.git@f485a21a9cb05494161d97d545c3b29447610ffb' , 'v8/tools/clang/dsymutil:chromium/llvm-build-tools/dsymutil' : 'https://chrome-infra-packages.appspot.com/chromium/llvm-build-tools/dsymutil@OWlhXkmj18li3yhJk59Kmjbc5KdgLh56TwCd1qBdzlIC' , 'v8/tools/luci-go:infra/tools/luci/isolate/${platform}' : 'https://chrome-infra-packages.appspot.com/infra/tools/luci/isolate/${platform}@git_revision:7d11fd9e66407c49cb6c8546a2ae45ea993a240c' , 'v8/tools/luci-go:infra/tools/luci/isolated/${platform}' : 'https://chrome-infra-packages.appspot.com/infra/tools/luci/isolated/${platform}@git_revision:7d11fd9e66407c49cb6c8546a2ae45ea993a240c' , 'v8/tools/luci-go:infra/tools/luci/swarming/${platform}' : 'https://chrome-infra-packages.appspot.com/infra/tools/luci/swarming/${platform}@git_revision:7d11fd9e66407c49cb6c8546a2ae45ea993a240c' , 'v8/tools/swarming_client' : 'https://chromium.googlesource.com/infra/luci/client-py.git@96f125709acfd0b48fc1e5dae7d6ea42291726ac' , }
2.下载
3.编译 3.1 使用gm构建编译 (简单)
1 tools/dev/gm.py x64.release //如果编辑后要立即运行测试使用 x64.release.check
3.2 使用 gn 手动编译
1 2 3 gn gen out.gn/x64.release ninja -C out.gn/x64.release
3.3使用v8gen 手动编译
1 2 3 4 5 tools/dev/v8gen.py x64.release ninja -C out.gn/x64.release ninja -C out/x64.release d8 //构建特定目标
编译嵌入c++的v8
编译流程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 # 下载 mkdir ~/v8 cd ~/v8 fetch v8 //这里我们终止 ctrl + c cd v8 git checkout refs/tags/7.1.11 -b sample -t cd .. gclient sync # 编译 cd v8 tools/dev/v8gen x64.release.sample ninja -C out.gn/x64.release.sample v8_monolith
这里我们选择了第三种编译方式v8gen,但是略有不同的是我们直接将所有的动态库链接起来,直接生成了v8_monolith.a
静态库:/Users/juneleo/v8/v8/out.gn/x64.release.sample/obj
接下来我们可以进行简单的测试,使用官方提供的方式
1 2 3 g++ -I. -Iinclude samples/hello-world.cc -o hello_world -lv8_monolith -Lout.gn/x64.release.sample/obj/ -pthread -std=c++14 -DV8_COMPRESS_POINTERS ./hello_world
hello_world.cpp (官方demo)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 #include <stdio.h> #include <stdlib.h> #include <string.h> #include "include/libplatform/libplatform.h" #include "include/v8.h" int main (int argc, char * argv[]) { v8::V8::InitializeICUDefaultLocation (argv[0 ]); v8::V8::InitializeExternalStartupData (argv[0 ]); std::unique_ptr<v8::Platform> platform = v8::platform::NewDefaultPlatform (); v8::V8::InitializePlatform (platform.get ()); v8::V8::Initialize (); 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) ; v8::HandleScope handle_scope (isolate) ; v8::Local<v8::Context> context = v8::Context::New (isolate); { v8::Context::Scope context_scope (context) ; v8::Local<v8::String> source = v8::String::NewFromUtf8 (isolate, "'Hello' + ', World!'" , v8::NewStringType::kNormal).ToLocalChecked (); v8::Local<v8::Script> script = v8::Script::Compile (context, source).ToLocalChecked (); v8::Local<v8::Value> result = script->Run (context).ToLocalChecked (); v8::String::Utf8Value utf8 (isolate, result) ; printf ("%s\n" , *utf8); } } isolate->Dispose (); v8::V8::Dispose (); v8::V8::ShutdownPlatform (); delete create_params.array_buffer_allocator; return 0 ; }
v8引擎使用
初始化V8
创建一个新的隔离区(isolate),并将这个隔离区置为当前使用
创建一个栈分配的句柄范围 (handle_scope)
创建一个上下文 (Context)
进入上下文编译和运行脚本 (Compile 和 Run)
销毁isolate以及使用过的buffer,并关掉进程 (Dispose)
嵌入C++ 1.使用CLion新建一个C++项目,构建工具选择cmake
2.复制v8/sample目录资源到C++项目
3.CMakeLists.txt
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 cmake_minimum_required (VERSION 3.2 )project (v8-demo)include_directories (/Users/juneleo/v8/v8/include)include_directories (/Users/juneleo/v8/v8)link_directories ( /Users/juneleo/v8/v8/out.gn/x64.release.sample/obj ) link_libraries ( v8_monolith ) set (CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++0x -pthread" )add_executable (HelloWorld ./helloworld.cc)add_executable (Process ./process.cc)add_executable (Shell ./shell.cc)
4.运行
4.1运行HelloWorld
4.2运行Shell
4.3 通过查看shell.cc,支持传入js文件
4.4 ObjectTemplate
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 v8::Local<v8::Context> CreateShellContext (v8::Isolate* isolate) { v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New (isolate); global->Set ( v8::String::NewFromUtf8 (isolate, "print" , v8::NewStringType::kNormal) .ToLocalChecked (), v8::FunctionTemplate::New (isolate, Print)); global->Set (v8::String::NewFromUtf8 ( isolate, "read" , v8::NewStringType::kNormal).ToLocalChecked (), v8::FunctionTemplate::New (isolate, Read)); global->Set (v8::String::NewFromUtf8 ( isolate, "load" , v8::NewStringType::kNormal).ToLocalChecked (), v8::FunctionTemplate::New (isolate, Load)); global->Set (v8::String::NewFromUtf8 ( isolate, "quit" , v8::NewStringType::kNormal).ToLocalChecked (), v8::FunctionTemplate::New (isolate, Quit)); global->Set ( v8::String::NewFromUtf8 (isolate, "version" , v8::NewStringType::kNormal) .ToLocalChecked (), v8::FunctionTemplate::New (isolate, Version)); return v8::Context::New (isolate, NULL , global); }
运行
4.5 模仿小程序my.call
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 v8::Local<v8::Context> CreateShellContext (v8::Isolate* isolate) { v8::Local<v8::ObjectTemplate> global = v8::ObjectTemplate::New (isolate); ... v8::Local<v8::ObjectTemplate> my = v8::ObjectTemplate::New (isolate); my ->Set ( v8::String::NewFromUtf8 (isolate, "call" , v8::NewStringType::kNormal).ToLocalChecked (), v8::FunctionTemplate::New (isolate, Call)); global -> Set ( v8::String::NewFromUtf8 (isolate, "my" , v8::NewStringType::kNormal).ToLocalChecked (), my ); return v8::Context::New (isolate, NULL , global); } void Call (const v8::FunctionCallbackInfo<v8::Value>& args) { printf ("调用了my.call" ); }
运行 my.call 函数
通过对象模版和方法模版,我们可以联想到小程序中的my.xxxx,这些jsapi可能都是通过方法模版进行注入的,java层/object-c层,通过调用c层对应的方法进行注入,这些注入的方法将被js所调用。
4.6 访问动态变量和设置拦截器( 部分代码 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 v8::Local<v8::Context> CreateShellContext (v8::Isolate *isolate) { ... v8::Local<v8::FunctionTemplate> point_templ = v8::FunctionTemplate::New (isolate, constructPoint); global->Set (v8::String::NewFromUtf8 ( isolate, "Point" , v8::NewStringType::kNormal).ToLocalChecked (), point_templ); v8::Local<v8::ObjectTemplate> point_proto = point_templ->PrototypeTemplate (); point_proto->Set (v8::String::NewFromUtf8 ( isolate, "multi" , v8::NewStringType::kNormal).ToLocalChecked (), v8::FunctionTemplate::New (isolate, PointMulti)); v8::Local<v8::ObjectTemplate> point_inst = point_templ->InstanceTemplate (); point_inst->SetInternalFieldCount (1 ); point_inst->SetAccessor (v8::String::NewFromUtf8 (isolate, "x" ).ToLocalChecked (), GetPointX, SetPointX); point_inst->SetAccessor (v8::String::NewFromUtf8 (isolate, "y" ).ToLocalChecked (), GetPointY, SetPointY); point_inst->SetHandler (v8::NamedPropertyHandlerConfiguration (PointGet,PointSet)); const v8::Local<v8::Context> context = v8::Context::New (isolate, NULL , global); return context; }
运行
4.7 访问令牌
SetSecurityToken
1 2 3 4 5 6 7 8 9 10 v8::Local<v8::Value> foo = v8::String::NewFromUtf8 (v8::Isolate::GetCurrent (), "foo" , v8::NewStringType::kNormal).ToLocalChecked (); context->SetSecurityToken (foo); env1->SetSecurityToken (foo); env1->Global ()->Set (env1, v8::String::NewFromUtf8 (v8::Isolate::GetCurrent (), "env1" , v8::NewStringType::kNormal).ToLocalChecked (), context->Global ());
SetAccessCheckCallback
1 2 3 4 5 6 7 global->SetAccessCheckCallback (SecurityTestCallback); bool SecurityTestCallback (v8::Local<v8::Context> accessing_context, v8::Local<v8::Object> accessed_object, v8::Local<v8::Value> data) { printf ("校验权限\n" ); return true ; }
首先校验令牌,令牌校验不通过才会回调SetAccessCheckCallback进行校验
Demo1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 v8::Context::Scope env1_scope(env1); v8::Local<v8::Value> args[] = {v8_str("song" )}; v8::Local<v8::Object> myObj = my->NewInstance(env1).ToLocalChecked(); v8::Local<v8::Value> myCall = (myObj->Get(env1, v8_str("call" ))).ToLocalChecked(); myCall.As<v8::Function>()->CallAsFunction(env1,myObj,1 ,args); v8::Context::Scope env2_scope(env2); env2->Global()->Set(env2, v8_str("env4" ),myObj); CompileRun("env4.call(\"haha1\");" ); env2->Global()->Set(env2, v8_str("env3" ),env1->Global()); CompileRun("env3.my.call(\"haha2\");" );
不设置令牌1 2 env1->SetSecurityToken(foo); env2->SetSecurityToken(foo);
设置令牌
更多代码参考:http://gitlab.alibaba-inc.com/amap_mini/v8-example
参考 v8编译:https://v8.dev/docs/embed v8 worker: https://yuque.antfin.com/kuanggu.wx/ya8liq/ks5mof