V8概念以及编程入门

V8概念以及编程入门

仓库地址:215566435/Fz-node


v8 知识梳理以及编程入门


学习 v8 编程的好处就是能够知道 node 是如何通过写JavaScript 进行调用系统 API 的,如果你有 c/c++ 又或者是比较底层的语言的基础,研究明白 v8 是如何调用系统底层的,而系统是如何提供函数给 v8 调用的,那么我们对 node 的理解层度就会更上一层楼。


本节主要折腾的几个事情


  • 如何通过 JS 调用 c/c++ 层
  • 如何通过 c/c++ 层调用 JS 层
  • 如何通过 JS 调用 c++ 的类

当我们实现以上的 JS-C/C++ 层的调用时,我们距离自己造一个 node.js 已经不远了


V8概念梳理

v8 执行代码的过程主要是:


  • JavaScript源码输入
  • 转换成AST(抽象语法树)
  • JIT(just in time)
  • NativeCode

这对于我们编程有了最初的印象,接下来,我们介绍一下各个内部的概念。


v8::Isolate

Isolate 的概念给大家来看一定非常陌生,其英文原意是:隔离。在操作系统中,我们有一个概念和之类似:进程。进程是完全相互隔离的,一个进程里有多个线程,同时各个进程之间并不相互共享资源。Isolate 也是一样,Isolate1 和 Isolate2 两个拥有各自堆栈的虚拟机实例,且相互完全隔离。


An isolate is a VM instance with its own heap. It represents an isolated instance of the V8 engine. V8 isolates have completely separate states. Objects from one isolate must not be used in other isolates.


v8::handle(v8::Local和v8:Persistent)

在新的版本中,v8::handle 拆成了更为形象的两个类:v8::Local 和 v8:Persistent 。用一个更形象的比喻,那么 v8::Local 更像是 JavaScript 中的 let 。在 V8 中,内存的分配都交付给了 V8,那么我们就最好不要使用自己的 new 方法来创建对象,而是使用 v8::Local 里的各种方法来创建一个对象。由 v8::Local 创建的对象,能够被 v8 自动进行管理,也就是传说中的GC (垃圾清理机制)。

Persistent 代表的是持久的意思,更类似全局变量,申请和释放一定要记得使用:Persistent::New, Persistent::Dispose 这两个方法,否则会内存侧漏。

Scope

Scope是一个比较小范围的GC单元,分别有v8::HandleScopev8::Context::Scope

v8::HandleScope一般情况下,HandleScope 会在一个函数的开头进行声明,然后用于管理这整个函数所创建的Handle,而v8::Context::Scope也类似,只不过他是直接管理context对象的。

void F(){
    //一开头放一个
    v8::HandleScope handle_scope(isolate);
    v8::Local<v8::String> source1 =.......
    v8::Local<v8::String> source2 =......

    //开头放一个
    v8::Local<v8::Context> context = Context::New(isolate);
    v8::Context::Scope context_scope(context);
}

这样一来,整个函数的handle和context就会被这两个scope分别管理起来,函数跑完了,那么就会自动释放掉了。

Context

Context的概念我更喜欢把比喻成闭包,虽然不是,但是很像。在V8里面,Context主要是用于创建一个JavaScript的运行上下文环境。更形象生动的说法是Html的iFrame,一个网页中可以有多个iFrame,每个iFrame又有不同的运行环境。


V8编译与安装

  • 平台MAC
  • IDE:Xcode

得益于 IDE 的良好提示,在本次 V8 研究之旅中减少了很多折腾,主要是各种函数的跳转,断点调试,查看函数定义,等等,其他平台的玩家,我就不班门弄斧了,简单说说V8的安装和编译。


文档:v8/v8


根据文档提示,我们得先下载 depot_tools

git clone https://chromium.googlesource.com/chromium/tools/depot_tools.git
export PATH=$PATH:/Path/To/depot_tools
fetch v8
cd v8
gclient sync
gn args out.gn/x64.release
ninja -C out.gn/x64.release
  • git clone命令执行的是下载操作,不多说
  • 第二个是在.zshrc或者.bashrc中配置以下工具的环境变量,一般这两个玩意儿都在你的cd目录下可以找到
  • fetch v8:自然说的就是获取v8的最新可编译版本
  • gclient sync:是下载所有的依赖
  • gn args out.gn/x64.release:在正常情况下会弹出一个编辑器,是vim,如果你想迁移v8到xcode上,你必须编译出静态库.a文件,然后复制到xcode里面,才可以,因此你需要在编辑器里添加一行参数:v8_static_library=true
  • ninja -C out.gn/x64.release:进行编译

经过漫长的编译,我们之后可以在 out.gn/x64.release 文件中找到所有编译出来的v8文件。创建一个xcode工程,把v8的include文件丢进去,引入刚刚编译的静态库,这7个全都要

这几个一个不能少
工程目录

正式编程之旅:hello world

// Copyright 2015 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#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::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();
  // 创建一个isolate
  v8::Isolate::CreateParams create_params;
  create_params.array_buffer_allocator =
      v8::ArrayBuffer::Allocator::NewDefaultAllocator();
  v8::Isolate* isolate = v8::Isolate::New(create_params);
//构建好isolate开始编程
  {
    v8::Isolate::Scope isolate_scope(isolate);
    // 构建一个handle_scope管理即将分配的各种变量
    v8::HandleScope handle_scope(isolate);
    // 构建一个上下文context
    v8::Local<v8::Context> context = v8::Context::New(isolate);
    // 将context放进scope中v8会进行管理
    v8::Context::Scope context_scope(context);
    // 构建一段string类型的handle,这一段内容就是javascript了
    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();
    //转换成utf8的变量
    v8::String::Utf8Value utf8(isolate, result);
    //c语言经典打印函数
    printf("%s\n", *utf8);
  }
  // 关闭v8并且清理内存
  isolate->Dispose();
  v8::V8::Dispose();
  v8::V8::ShutdownPlatform();
  delete create_params.array_buffer_allocator;
  return 0;
}


上述过程对于没有 C 语言基础的同学看起来会非常的吃力,不过不要怕,跟着我的注释走,很快你就能了解。


构建Global对象

虽然说是构建 global 对象,但是其实这一步并不需要我们去做,因为v8已经做好了。在我们创建Context的时候,v8会为我们的context注入一个 global对象,这个global对象就是这个context对象里全局的一个对象。


想要操作他也很简单:

只需要拿到我们刚刚创建的 Context (指针),就可以拿到。我们之后所有的操作都会围绕global这个变量进行。因为对其进行添加变量,添加函数,添加方法等等,在我们之后进行编译JavaScript时,我们都可以在js中拿到,比如,我现在注入了一个fangzheng变量,他对应的就是global对象。在Js中,我们可以在任意地方使用:

var global = fangzheng;
//do anything


C++调用JS函数

由于网上大部分的文章都已经过期,我只能从node.js中探寻如何使用C++调用JS函数,在之前研究深入底层:Node.js启动和模块加载的时候,就已经知道了,无论是什么函数,我们都会被包囊成:

(function(){})

这样的形式,这样的形式告诉V8,编译出来的东西就是函数表达式,可以被调用,接下去就比较简单了。


上面这段代码,最终会输出 V8 字样,也就是我们在JS中定义的var i = 'v8'.


JS调用C++函数

这一方面网上的教程也是比较少的了,大部分已经过期,来看看最新版的v8调用吧,只需要几个步骤就可以JS调用C++了。

void print(const FunctionCallbackInfo<Value>& info){
    printf("我是函数调用\n");
    
    info.Data();
    info.GetReturnValue().Set(134);
};
  • 构建一个print函数
  • FunctionCallbackInfo:这个类代表的是当JS层调用C++层以后传入的参数,我们所有的操作都必须通过这个类返回给JS层
  • info.Data():这个指的是JS层的调用时传入的参数,可能是JS的任何值。
  • info.GetReturnValue().Set(134):这个就是函数的返回值了,当我们通过C++的计算或者是什么其他乱七八糟的操作时,返回结果给JS,这个就是返回值,同样,你可以返回任何C++的值给JS。


注入Global

global->Set(v8::String::NewFromUtf8(isolate, "print"), function);
//在js中调用
 v8::Local<v8::String> source =
        v8::String::NewFromUtf8(isolate, "(function(){var i = 'v8';print(1);return i;})",
                                v8::NewStringType::kNormal)
        .ToLocalChecked();

很简单


简单总结

我们已经实现了javascript和c++层的简单调用,理解那几个v8概念是最主要的,就像我们说的,我们距离自己山寨一个 Nodejs 其实不远了。

编辑于 2018-04-07

文章被以下专栏收录

    这是一个新手的专栏!至于为什么是哑铃呢?因为我还是一个健身减脂小能手,我的梦想是做最强壮的程序员。