zoukankan      html  css  js  c++  java
  • 深入出不来nodejs源码-内置模块引入再探

      我发现每次细看源码都能发现我之前写的一些东西是错误的,去改掉吧,又很不协调,不改吧,看着又脑阔疼……

      所以,这一节再探,是对之前一些说法的纠正,另外再缝缝补补一些新的内容。

      错误在哪呢?在之前的初探中,有这么一块代码:

      // The bootstrapper scripts are lib/internal/bootstrap/loaders.js and
      // lib/internal/bootstrap/node.js, each included as a static C string
      // defined in node_javascript.h, generated in node_javascript.cc by
      // node_js2c.
      Local<String> loaders_name =
          FIXED_ONE_BYTE_STRING(env->isolate(), "internal/bootstrap/loaders.js");
      Local<Function> loaders_bootstrapper =
          GetBootstrapper(env, LoadersBootstrapperSource(env), loaders_name);

      当时,我的理解是这样的:

     辅助函数则是加载了internal/bootstrap中的两个JS文件,加载的时候参数传入了C++代码生成的特殊对象。

      但是在我调试这块代码的时候,发现根本没有任何readFile的痕迹,才发现事情并没有那么简单,也就是说这个地方压根就没有加载对应的JS文件。

      那么问题来了,既然没有加载这个JS文件,那这个文件有什么意义?何处加载的?

      第一个问题,我猜大概是开发者想让我们直观的了解到加载了什么东西,所以以文件的形式保留在文件夹中方便查看。

      第二个问题,根据注释,可以很快的知道答案,但是当时哪里注意那么多哟。

      简单讲,这个文件的内容以静态的字符串的形式定义在node_javascript.h中,内容则在node_javascript.cc中,并使用node_js2c进行JS代码到C++代码的转换。

      

      问题的答案很简单,探索过程对我来说还是挺心酸的,这里一共有两行代码,首先看第一行。

      FIXED_ONE_BYTE_STRING是一个宏,这里暂不讨论内部实现,根据参数和返回类型可以简单判断这是一个转换函数,可以将const char*类型转换成Local<String>类型,至于Local是什么,可以参考我上一节内容,或者查阅其他的资料。

      对于第二行代码,需要关注的是LoaderBootstrapperSource这个方法,进去之后会发现又是一个调用:

    v8::Local<v8::String> LoadersBootstrapperSource(Environment* env) {
      return internal_bootstrap_loaders_value.ToStringChecked(env->isolate());
    }

      这个internal_bootstrap_loaders_value是一个结构体,形式比较简单(C++代码结构都很简单),源码如下:

    static struct : public v8::String::ExternalOneByteStringResource {
        // 重写父类函数
        // 强制进行const unsigned char[] => const char*的类型转换
        const char* data() const override {
            return reinterpret_cast<const char*>(raw_internal_bootstrap_loaders_value);
        }
        // 数组长度
        size_t length() const override { return arraysize(raw_internal_bootstrap_loaders_value); }
        // 默认delete函数
        void Dispose() override { /* Default calls `delete this`. */ }
        // const char* => Local<String>的类型转换
        v8::Local<v8::String> ToStringChecked(v8::Isolate* isolate) {
            return v8::String::NewExternalOneByte(isolate, this).ToLocalChecked();
        }
    } internal_bootstrap_loaders_value;

      几个内部的属性作用非常明朗,注释都有写。

      暂时不去深入了解ToStringChecked方法的内部实现,从返回类型来看,最终也是生成一个Local<String>类型的方法,而这个String的源头,就是上面另外一个变量raw_internal_bootstrap_loaders_value。

      这个东西是这么定义的:

    static const uint8_t raw_internal_bootstrap_loaders_value[] = { 47,47,32,84,104,105,115,32,102,105,108,101,32,99,114,101,97,116,101,115,... }

      很长很长的一个数组,uint8_t是unsigned char的别名,也就是说,这是一个超长的char数组。

      根据C++的基础知识,数组的名字本质上是一个指针,指向数组的第一个值,而数组的值又恰好是char类型的,所以说,对该值进行reinterpret_cast<const char*>的转换是不会有问题的。

      那么另外一个问题是,const char*更为熟知的类型是string字符串,这里一个数字数组是怎么变成字符串的?

      干想果然是浪费时间的,我把这个大数组弄到本地自己打印了一下,发现输出的内容竟然是:

      怎么感觉这么熟悉,翻开JS文件,果然……

      简单思考后,原来这里是因为ASCII表转换,把对应的一个个字符转换成了数字保存在字符数组中,真的是恶心啊。

      那么问题就解决了,加载的辅助JS文件内容其实是以字符数组保存在C++中的,获取完整内容后通过对JS到C++的转换,然后执行对应的代码

      既然完成了JS文件内容、文件名的内容获取,下一步就是构建对应的函数体,方法就是GetBootstrapper,源码简化后如下:

    // 静态公共方法 专门负责生成初始化辅助函数体
    // env => 上下文环境
    // source => JS格式的函数字符串
    // script_name => 资源名
    static Local<Function> GetBootstrapper(Environment* env, Local<String> source,
        Local<String> script_name) {
        EscapableHandleScope scope(env->isolate());
        // ...
        // 解析JS字符串并转换成Local<Value>类型
        Local<Value> bootstrapper_v = ExecuteString(env, source, script_name);
        // 检测返回的数据类型是否是函数并进行强制类型转换
        CHECK(bootstrapper_v->IsFunction());
        Local<Function> bootstrapper = Local<Function>::Cast(bootstrapper_v);
        
        return scope.Escape(bootstrapper);
    }

      这里省略了一些无关的错误处理,比较关键的几步可以看注释描述,有几点需要特殊说明一下:

    1、关于EscapableHandleScope,正常情况下都是使用的HandleScope来管理作用域的Local,但是如果函数需要返回临时创建的Local,在返回前Local已经被V8的GC进行了处理,这里必须使用EscapableHandleScope类创建一个特殊的scope,并在最后调用Escape方法将指定的Local返回。

    2、返回类型的Local<Value>,如果有看过上一节对于V8引擎一些基本概念的讲解,应该会发现Value是所有数据类型的根类,在对类型进行CHECK后再强制转换可以保证类型安全。

      最后看一眼ExecuteString方法:

    static Local<Value> ExecuteString(Environment* env,
        Local<String> source,
        Local<String> filename) {
        EscapableHandleScope scope(env->isolate());
        // ...
        // 编译解析一条龙
        ScriptOrigin origin(filename);
        MaybeLocal<v8::Script> script =
            v8::Script::Compile(env->context(), source, &origin);
        MaybeLocal<Value> result = script.ToLocalChecked()->Run(env->context());
        // 返回Local<Value>类型的C++代码
        return scope.Escape(result.ToLocalChecked());
    }

      这里同样省略一些无关代码,可以发现,处理过程非常直白,直接利用Script类对字符串进行编译解析,然后返回执行完后生成的函数体。

      对于"internal/bootstrap/node.js"的加载过程也类似,就不再重复了。

      目前得到了函数体,但是并没有执行,后面再分析这块内容。

  • 相关阅读:
    BF算法和KMP算法
    Python课程笔记 (五)
    0268. Missing Number (E)
    0009. Palindrome Number (E)
    0008. String to Integer (atoi) (M)
    0213. House Robber II (M)
    0198. House Robber (E)
    0187. Repeated DNA Sequences (M)
    0007. Reverse Integer (E)
    0006. ZigZag Conversion (M)
  • 原文地址:https://www.cnblogs.com/QH-Jimmy/p/9243859.html
Copyright © 2011-2022 走看看