You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
然而,这些资料大多年代久远,V8 的 API 也发生了变化,因此,其中的代码很难直接运行起来。后来我在 V8 的源码中直接找到了例子,参考这里。仔细观察 shell.cc ,我们能够发现注入全局变量的“三步走”方法:
声明函数
voidPrint(const v8::FunctionCallbackInfo<v8::Value>& args); // line 54
定义函数
// 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.voidPrint(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]);
constchar* cstr = ToCString(str);
printf("%s", cstr);
}
printf("\n");
fflush(stdout);
}
注入函数
// Creates a new execution environment containing the built-in// functions.
v8::Local<v8::Context> CreateShellContext(v8::Isolate* isolate) {
// Create a template for the global object.
v8::Local<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::NewStringType::kNormal)
.ToLocalChecked(),
v8::FunctionTemplate::New(isolate, Print));
returnv8::Context::New(isolate, NULL, global);
}
前言
最近花了一些时间研究 V8 引擎,收获良多。今天,我们一起来探索一番。
注:阅读本文需要一定 C++ 基础。
V8 与 d8
问题:V8 引擎是一个很复杂的东西,对它的研究,应该从哪里开始着手呢?
答案:从运行它开始。
那么,如何运行 V8 呢?这里有一些参考资料:
这些资料讲得都很完备,我就不赘述了。直接给出运行结果示意图。
至此,我们已经把 V8 的 Demo d8 跑起来,并且可以让其执行任意的 JS 代码。
但是,我们仔细想想:V8 和 d8 是一个概念吗?
不是的,V8 和 d8 不是一个概念。V8 是一个 C++ 库,d8 是一个 C++ 应用,其中内嵌了 V8 库,所以,d8 才能执行 JS 代码(因为它本质上将输入的 JS 代码交给 V8 处理了)。
那么,我们能不能模仿 d8,自己写一个 C++ 应用,来执行指定的 JS 代码呢?
内嵌 V8
官方给出了一个内嵌 V8 的 demo,按照该文档进行操作,便可以自己实现这样的一个 C++ 应用。
请注意,之前我看这文档的时候还是对应 V8 的 4.8 版本,目前该文档已经升级到 5.8 版本,操作步骤有些不同。我在这里当初当时我操作 4.8 版本的步骤,仅供参考。(本文后面所有的探索都是基于 4.8 版本)
clang++ -stdlib=libstdc++ -std=c++11 -I. hello_world.cpp -o hello_world out/x64.release/libv8_base.a out/x64.release/libv8_libbase.a out/x64.release/libicudata.a out/x64.release/libicuuc.a out/x64.release/libicui18n.a out/x64.release/libv8_base.a out/x64.release/libv8_external_snapshot.a out/x64.release/libv8_libplatform.a
cp out/x64.release/*.bin .
./hello_world
,屏幕会打印出 “Hello, World!" 字样为什么屏幕会输出 ”Hello, World!" 呢?
因为在此 demo 中,给定执行的 JS 语句为 'Hello' + ' , World!'(如下面代码所示) ,这是一个表达式,此表达式执行返回的结果就是一个字符串。
ok,你可能会觉得这样的表达式太简单了,不足以证明其能够正确运行 JS 代码。
好,那我们尝试用复杂的原型链作为例子,如下所示。
把上述压缩成一行的字符串,放入上面的例子中,重新编译,执行结果如下图所示。
由此,我们已经证明:此 C++ 应用 hello_world 已经能够执行任意给定的 JS 代码。
到底是谁的 console
然而,当我想运行 console 语句的时候,意外的情况发生了。如下所示,给定 JS 代码为输出一个字符串。
执行结果如下图所示:
为什么程序无法识别 console?
不是说好的 V8 引擎能够执行 JS 代码?难道 console 不属于 ES 规范?
答案:console 还真不是 ES 规范中定义的,准确地说,console 不属于任何的规范,详见这里。
由此,我有以下两点思考:
带着这个疑问,我进行了以下的尝试:
从上图我们可以看出,hello_world、d8 和 NodeJS 的表现各不相同,为什么呢?
这个问题非常困扰我,直到我发现了这个概念:C++和JS 交互。
由此,我发现 hello_world、d8、NodeJS 这三者与 v8 真正的关系,如下图所示(点击查看大图):
由此我们可以得出结论:hello_world、d8、NodeJS和浏览器内核,都是一个 C++ 应用,其中内嵌 V8 引擎,用于执行 JS 代码。但是,它们会 V8 在外边包裹一层 Bridge,通过这一层 Bridge,实现 JS 和 C++ 之间的相互调用,以达到扩展 JS 的目的。
举个例子:为什么 d8 能够运行语句
print("哈哈哈");
呢?因为 d8 里面有一个 C++ 方法 Print,通过某种方式,将此方法注入到 V8 的全局环境中,对应到全局变量 print上。所以,当 V8 在执行该 “JS” 代码 print 的时候,其实本质上是在调用 Print 这个 C++ 方法。下面我们具体来看看注入的代码。
注入全局变量
关于如何注入,网上也有一些参考资料:
然而,这些资料大多年代久远,V8 的 API 也发生了变化,因此,其中的代码很难直接运行起来。后来我在 V8 的源码中直接找到了例子,参考这里。仔细观察 shell.cc ,我们能够发现注入全局变量的“三步走”方法:
至此,我们终于能搞明白如何注入全局变量了。
为了方便后续的调试,我提前编译好了 V8(4.8版本的),并且将一些所需要的头文件和中间过程生成的 .a 文件拷贝到一个新的仓库 fake-node 中,按照上面的步骤,便可以随意注入其他全局变量了。
后话
对 V8 的探索甚是消耗时间,主要有两个难点要克服。
----------- EOF --------------
The text was updated successfully, but these errors were encountered: