欢迎光临
我们一直在努力

13-架构-hybridclr核心运行时

hybridclr核心运行时

前言

在上一篇文章中,我们从宏观视角认识了 HybridCLR 的三层仓库架构。现在,我们将聚焦于最核心的层次——hybridclr 仓库。这是 HybridCLR 技术栈中代码量最大、技术深度最深的部分,包含了元数据管理、IL 编译、解释执行等所有关键功能。

本文是 hybridclr 仓库的架构级介绍,目标是:

  • 理清 hybridclr/src/ 目录下各个 C++ 模块的功能边界和内部结构
  • 剖析核心数据结构——这些结构在元数据、编译器、解释器之间流动
  • 解释模块之间的依赖关系和调用链
  • 为后续三篇源码深度模块文章(元数据模块、编译器模块、解释器模块)提供架构蓝图
  • 前置阅读:建议先完成原理篇第 11 篇和架构篇第 12 篇。


    一、hybridclr 仓库源码总览

    1.1 核心代码目录结构

    hybridclr 仓库的核心代码集中在 src/ 目录下。以下是 src/ 目录的详细文件组织:

    hybridclr/src/
    ├── CMakeLists.txt # CMake 构建配置
    ├── hybridclr_config.h.in # 配置模板(CMake 生成)
    ├── metadata/ # — 元数据模块 —
    │ ├── metadata_module.h/cpp # 元数据模块的入口和初始化
    │ ├── metadata_assembly.cpp # 程序集元数据加载
    │ ├── metadata_type.cpp # 类型元数据解析(TypeDef/TypeRef/TypeSpec)
    │ ├── metadata_field.cpp # 字段元数据解析(FieldDef)
    │ ├── metadata_method.cpp # 方法元数据解析(MethodDef)
    │ ├── metadata_signature.cpp # 签名解析(方法签名、字段签名、局部变量签名)
    │ └── metadata_common.h/cpp # 元数据公共工具函数
    ├── compiler/ # — 编译器模块 —
    │ ├── compiler_module.h/cpp # 编译器模块的入口
    │ ├── ir_builder.h/cpp # 中间表示构建器
    │ ├── instruction_allocator.h/cpp # 指令内存分配与回收
    │ ├── inst_gen.h/cpp # 指令生成器(IL → 寄存器指令)
    │ ├── register_allocator.h/cpp # 虚拟寄存器分配器
    │ └── basic_block.h/cpp # 基本块划分
    ├── interpreter/ # — 解释器模块 —
    │ ├── interpreter_module.h/cpp # 解释器模块的入口
    │ ├── interpreter.cpp # 核心解释器循环
    │ ├── interpreter_handle.cpp # 指令处理函数
    │ ├── interpreter_stack.cpp # 栈帧实现
    │ ├── interpreter_exception.cpp # 异常处理
    │ └── interpreter_gc.cpp # GC 集成(GC Safe Point)
    ├── transform/ # — 代码变换模块 —
    │ ├── transform_module.h/cpp # 变换模块的入口
    │ ├── aot_compiler.cpp # AOT 编译器集成
    │ └── transform.cpp # IL 指令序列变换
    ├── vm/ # — 虚拟机集成模块 —
    │ ├── vm_module.h/cpp # VM 模块入口
    │ ├── class.cpp # 动态类型创建与注册
    │ ├── method.cpp # 动态方法注册
    │ ├── field.cpp # 字段访问辅助
    │ └── object.cpp # 对象创建辅助
    └── common/ # — 公共头文件 —
    ├── config.h # 编译期配置
    ├── logging.h # 日志工具
    ├── memory.h # 内存管理
    └── platform.h # 平台抽象层

    从代码行数来看,interpreter/ 模块是最大的模块(约占总代码量的 40-50%),其次是 compiler/(约 25-30%)和 metadata/(约 15-20%)。这个比例反映了运行时执行路径的复杂性——解释器需要处理 IL 中所有可能的指令类型和异常情况,其代码量自然最庞大。

    1.2 核心模块的职责划分

    五个核心模块(metadata、compiler、interpreter、transform、vm)的职责划分清晰且独立:

    • metadata 模块:只负责"读"——读取 DLL 二进制文件,解析元数据,将结果存储到 IL2CPP 的类型系统中。它不关心这些元数据后续如何被使用。metadata 模块的工作只在热更新 DLL 加载阶段执行一次,一旦 type/field/method 的信息被注册到 IL2CPP 的类型系统,该模块的任务就完成了。
    • compiler 模块:只负责"转"——将 IL 字节码转换为寄存器指令。它不关心元数据是如何被加载的,也不关心寄存器指令最终如何被执行。compiler 模块的工作在方法级别上执行——每次一个热更新方法被首次调用时,该模块的 CompileMethod 函数被触发。编译完成后,生成的寄存器指令被缓存到方法关联的元数据中,后续调用不再需要重新编译。
    • interpreter 模块:只负责"执行"——逐条执行寄存器指令。它通过函数指针调用 IL2CPP 的运行时 API(如对象分配、类型检查、方法调用等),不关心指令的来源。interpreter 模块是不断运行的——只要热更新代码在执行,解释器循环就不会停止。一旦遇到 ret 指令或异常,解释器将控制权交还给调用方。
    • transform 模块:只负责"变"——在编译前对 IL 指令序列进行预处理变换。它是一个可选的前置处理步骤,不改变编译器和解释器的核心逻辑。如果 transform 模块检测到不需要对当前方法的 IL 进行变换,它会直接跳过,不产生任何性能开销。
    • vm 模块:只负责"桥"——提供 hybridclr 与 IL2CPP 运行时之间的桥梁函数。它包含了对 IL2CPP 内部 API 的封装和扩展。vm 模块的函数在每个解释器方法调用中都会被使用——每当解释器需要创建一个对象、调用一个 IL2CPP 运行时函数或查询类型信息时,都需要经过 vm 模块的桥接。

    这种职责划分遵循了 Unix 哲学中的"单一责任原则"——每个模块只做一件事,模块之间的交互通过明确定义的数据结构和函数接口进行。

    1.3 模块间的调用链

    在运行时,五个模块之间的典型调用链为:

    热更新 DLL 加载

    vm 模块:创建动态类型的运行时表示(Il2CppClass)

    metadata 模块:解析 DLL 元数据,填充类型/方法/字段信息

    compiler 模块:遍历所有方法,将其 IL 编译为寄存器指令

    transform 模块(可选):在编译前对某些方法的 IL 进行预处理变换

    interpreter 模块:当热更新方法被调用时,从缓存加载寄存器指令并执行

    这个调用链在方法级别被"打断"——并非所有方法都在加载时编译。HybridCLR 的编译策略是"延迟编译"(Lazy Compilation):在 DLL 加载阶段,元数据被完整解析,但编译器只处理方法的 IL 以确定其寄存器指令大小和方法元数据,实际的寄存器指令生成推迟到方法首次被调用时。这种策略减少了启动时的编译量,优化了热更新 DLL 的加载速度。


    二、元数据模块(metadata/)

    2.1 模块功能

    metadata 模块是 hybridclr 解析外部程序集信息的入口。它的核心输入是热更新 DLL 文件的字节数组,核心输出是一组与 IL2CPP 类型系统兼容的运行时数据结构。

    metadata 模块处理的数据遵循 ECMA-335 标准定义的 CLI 元数据格式。一个热更新 DLL 的元数据由以下组件构成:

    • PE 头部:标准的 Windows PE(Portable Executable)文件格式的头部,包含 CLI 头部的 RVA(Relative Virtual Address)
    • CLI 头部:指向元数据流和入口点的指针
    • 元数据流(Metadata Stream):包含元数据表的二进制数据,每个元数据表对应一种元数据元素类型
    • 字符串堆(#Strings Heap):存储所有类型名称、方法名称、字段名称等字符串常量
    • Blob 堆(#Blob Heap):存储签名信息(方法签名、字段签名、属性签名等二进制大对象)
    • 用户字符串堆(#US Heap):存储 IL 代码中使用的用户字符串常量(如 ldstr "hello" 中的 "hello")
    • GUID 堆(#GUID Heap):存储程序集的 GUID 标识

    2.2 元数据表解析

    metadata 模块需要处理 ECMA-335 定义的数十种元数据表。其中最重要的是:

    TypeDef 表(0x02):定义热更新 DLL 中声明的所有类型。每条记录包含:类型的名称和命名空间(指向字符串堆的索引)、基类型(TypeDef 或 TypeRef 的索引)、字段列表(指向 Field 表的起始索引)、方法列表(指向 MethodDef 表的起始索引)、以及类型标志(class/struct/interface、abstract/sealed 等属性)。

    MethodDef 表(0x06):定义热更新 DLL 中的所有方法。每条记录包含:方法名称、方法签名(指向 Blob 堆的索引)、参数列表(指向 Param 表的起始索引)、IL 指令流的 RVA(在 DLL 文件中的偏移)、以及方法标志(public/private/static/virtual 等属性)。IL 指令流本身不存储在 MethodDef 表中,而是存储在 PE 文件的 .text 节区中,通过 RVA 索引。

    FieldDef 表(0x04):定义热更新 DLL 中的所有字段。每条记录包含:字段名称、字段签名(指向 Blob 堆的索引)、以及字段标志(public/private/static/literal 等属性)。

    TypeRef 表(0x01):定义热更新 DLL 引用的外部类型(即其他程序集中的类型)。当热更新代码引用 AOT 程序集中的类型时,对应的引用信息存储在 TypeRef 表中。

    AssemblyRef 表(0x23):定义热更新 DLL 引用的外部程序集。包括程序集名称、版本号、公钥标记和文化信息。

    MemberRef 表(0x0A):定义热更新 DLL 引用的外部成员(方法或字段)。当热更新代码调用 AOT 方法或访问 AOT 字段时,MemberRef 表记录了被引用成员的信息。

    CustomAttribute 表(0x0C):定义类型、方法、字段等元素上的自定义特性(Attribute)。HybridCLR 需要解析这些特性以正确处理特殊的类型标记(如 [Obsolete]、[Serializable] 等)。

    2.3 元数据与 IL2CPP 类型系统的对接

    metadata 模块的最终目标不是"解析元数据"本身,而是"将热更新类型的元数据注册到 IL2CPP 的类型系统中"。注册过程的核心函数是 metadata_module.cpp 中的 LoadMetadataForAOTAssembly:

    // 核心函数(伪代码)
    Il2CppClass* LoadMetadataForAOTAssembly(const void* dll_data, size_t size)
    {
    // 1. 校验 PE 文件格式
    if (!IsValidPEFile(dll_data, size)) return nullptr;

    // 2. 解析 CLI 头,定位元数据流
    CLIHeader* cli_header = ParseCLIHeader(dll_data);
    MetadataStream* metadata = ParseMetadataStream(dll_data, cli_header);

    // 3. 遍历 TypeDef 表,为每个类型创建 Il2CppClass
    for each type_def in metadata->TypeDefTable:
    Il2CppClass* klass = CreateIl2CppClass(type_def);

    // 4. 解析类型的方法,创建 MethodInfo
    for each method_def in type_def.Methods:
    MethodInfo* method = CreateMethodInfo(method_def);
    RegisterMethodToIl2Cpp(klass, method);

    // 5. 解析类型的字段,创建 FieldInfo
    for each field_def in type_def.Fields:
    FieldInfo* field = CreateFieldInfo(field_def);
    RegisterFieldToIl2Cpp(klass, field);

    // 6. 初始化虚函数表和接口映射表
    InitializeVTable(klass);
    InitializeInterfaceMap(klass);

    // 7. 注册到 IL2CPP 的类型系统
    RegisterTypeToIl2Cpp(klass);

    return first_type; // 返回第一个类型的指针
    }

    这个注册过程完成后,IL2CPP 运行时对热更新类型的基础操作(如类型查询、方法查找、字段访问)就可以正常工作了。但方法的执行还需要编译器模块和解释器模块的参与。


    三、编译器模块(compiler/)

    3.1 模块功能

    compiler 模块是 hybridclr 中负责"翻译"的组件——将 ECMA-335 标准的 IL 指令翻译为 HybridCLR 自定义的寄存器指令。模块的核心是 inst_gen.cpp 中的 CompileMethod 函数。

    编译器模块的处理流程:

    输入:MethodInfo(包含 IL 指令流的指针和大小)

    步骤 1:IL 解码
    解析 IL 指令流,逐条读取操作码和操作数

    步骤 2:基本块划分
    识别分支指令的目标位置,将 IL 序列划分为基本块

    步骤 3:栈深度分析
    跟踪每个 IL 位置上的评估栈深度和类型

    步骤 4:寄存器分配
    为 IL 栈上的每个值分配或回收虚拟寄存器

    步骤 5:指令生成
    为每条 IL 指令生成对应的寄存器指令

    输出:RegisterInstructionList(寄存器指令序列)

    IL 解码的细节。编译器首先遍历 IL 指令流,逐条读取操作码。IL 格式有两类操作码:单字节操作码(0x00-0xDD)和双字节操作码(以 0xFE 前缀开头,后跟第二字节 0x00-0x44)。对于每条操作码,解码器需要根据其类型(prefix、inline_none、inline_brtarget、inline_field、inline_method、inline_type、inline_string、inline_sig、inline_var 等),从指令流中读取对应数量的操作数。例如,call 指令(操作码 0x28)的操作数是一个 4 字节的元数据令牌(metadata token),指向 MethodRef 或 MethodDef 表中的条目;而 brfalse 指令(操作码 0x39)的操作数是一个 4 字节的目标 IL 偏移量。

    基本块划分的细节。完成 IL 解码后,编译器扫描所有分支指令的跳转目标,将 IL 序列划分为多个基本块。基本块的入口点包括:方法入口、分支指令的目标位置、异常处理子句的入口位置。基本块的出口点包括:分支指令(条件分支产生两个出口,无条件分支产生一个出口)、ret 指令、throw 指令。基本块划分的结果是一个有向图(控制流图,CFG),编译器后续的栈深度分析和寄存器分配在 CFG 上进行。

    3.2 编译器内部的中间表示

    编译器在将 IL 转换为寄存器指令的过程中,使用了内部的中间表示(IR)。IR 的设计目标是:

    • 与 IL 指令一一对应:每条 IR 指令对应一条 IL 指令,方便调试和问题定位
    • 携带类型信息:IR 指令包含操作数的类型信息(int32、int64、float、double、object ref 等)
    • 支持基本块边界标记:IR 序列中明确标记了基本块的边界

    IR 的数据结构在 ir_builder.h 中定义:

    struct IRInstruction {
    uint16_t opcode; // IR 操作码(扩展自 IL 操作码)
    uint8_t result_type; // 结果类型(ELEMENT_TYPE 枚举)
    uint16_t operand1; // 操作数 1(寄存器编号或立即数)
    uint16_t operand2; // 操作数 2
    uint16_t operand3; // 操作数 3
    uint32_t il_offset; // 对应的 IL 指令偏移量(用于调试)
    };

    IR 在编译器中的角色类似于"桥梁"——它将复杂、不规则的 IL 指令解码为规范化的内部表示,供后端的指令生成器处理。这种设计使得编译器的前后端可以独立变化:前端的 IL 解码器只负责产生 IR,后端的指令生成器只负责消费 IR。

    3.3 虚拟寄存器分配算法

    编译器使用的寄存器分配不是目标架构的物理寄存器分配,而是虚拟寄存器分配——即为 IL 评估栈上的每个值分配一个虚拟的寄存器编号。分配算法采用线性扫描(Linear Scan)的简化版本:

  • 分配:遇到需要将值压入评估栈的 IL 指令(如 ldloc、ldarg、ldfld、call 等),从虚拟寄存器池中分配一个未使用的寄存器编号
  • 释放:遇到从评估栈弹出值的 IL 指令(如 add、stloc、stfld、call 的参数准备等),将被弹出值对应的虚拟寄存器标记为可回收
  • 重分配:当虚拟寄存器不足时,触发 Register Spill 机制,将不活跃的值暂存到临时栈位置
  • 虚拟寄存器的池大小是编译时可配置的(默认为 32 个寄存器),大部分方法在编译时只需要 8-16 个寄存器。对于需要大量寄存器的复杂方法,编译器会自动进行寄存器溢出(Register Spill)管理。


    四、解释器模块(interpreter/)

    4.1 模块功能

    interpreter 模块是 hybridclr 的"执行引擎"——它负责逐条执行编译器模块生成的寄存器指令。模块的核心是 interpreter.cpp 中的 ExecuteMethod 函数,它实现了一个完整的指令分派循环。

    解释器模块的架构设计遵循"解释器模式"(Interpreter Pattern)的经典结构:

    上下文(InterpreterContext)
    ├── 寄存器数组(Register Array):当前方法执行过程中的虚拟寄存器值
    ├── 指令指针(Instruction Pointer):当前执行的寄存器指令位置
    ├── 栈帧指针(Stack Frame Pointer):当前方法在解释器栈中的位置
    └── 异常状态(Exception State):当前的异常处理状态

    指令集(Instruction Set)
    ├── 算术指令(8条)
    ├── 内存加载/存储指令(16条)
    ├── 控制流指令(12条)
    ├── 方法调用指令(8条)
    ├── 对象分配指令(4条)
    ├── 类型转换指令(8条)
    └── 异常处理指令(6条)

    分派引擎(Dispatch Engine)
    ├── 跳转表(Jump Table):操作码到处理函数入口的映射
    └── 主循环(Main Loop):读取指令 → 查询跳转表 → 执行处理函数 → 前进到下一指令

    解释器模块的设计遵循"最小化分派开销"的原则。在指令分派循环中,最关键的性能指标是"单条指令的平均执行时间"。HybridCLR 通过以下技术优化这个指标:使用跳转表实现 O(1) 的指令分派、使用定长指令格式简化指令解码、将常见的指令序列融合为复合指令减少分派次数。这些优化使得 HybridCLR 解释器的单条指令平均执行时间控制在 10-30 纳秒之间,显著优于通用的 IL 解释器。

    4.2 解释器上下文

    解释器上下文(InterpreterContext)在每次方法调用时被创建,存储方法执行过程中的全部状态:

    struct InterpreterContext {
    // 寄存器状态
    uint64_t registers[REGISTER_COUNT]; // 通用寄存器数组
    double fp_registers[FP_REGISTER_COUNT]; // 浮点寄存器数组

    // 指令控制
    uint32_t ip; // 指令指针(当前指令的偏移量)
    uint32_t end_ip; // 方法结束位置

    // 栈帧信息
    InterpreterFrame* frame; // 当前栈帧指针

    // 异常处理
    ExceptionState exception_state; // 异常状态
    uint32_t handler_start_ip; // 当前异常处理块的起始位置

    // 元数据缓存
    MetaDataCache* cache; // 方法级元数据缓存
    };

    4.3 解释器与 AOT 代码的互操作

    解释器模块最重要的功能之一是安全、高效地处理 AOT 代码与热更新代码之间的互操作。这部分逻辑在 interpreter_handle.cpp 的 InterpreterCall 函数中实现。

    当解释器遇到 call 或 callvirt 指令时,需要确定被调用方法的类型(AOT 还是热更新),并采取不同的处理路径:

    enum class MethodType {
    AOT_METHOD, // IL2CPP 编译的 AOT 方法
    INTERP_METHOD, // 热更新方法(需要解释器执行)
    NATIVE_METHOD, // 原生 C/C++ 方法(P/Invoke)
    };

    void InterpreterCall(InterpreterContext* ctx, uint32_t method_index)
    {
    MethodInfo* method = ResolveMethod(ctx, method_index);

    switch (GetMethodType(method)) {
    case MethodType::AOT_METHOD: {
    // 直接调用 AOT 方法的函数指针
    void* func_ptr = method->methodPointer;
    CallAOTFunction(func_ptr, ctx->registers);
    break;
    }
    case MethodType::INTERP_METHOD: {
    // 保存当前 → 创建新栈帧 → 进入新方法的解释执行
    ctx->frame = PushInterpreterFrame(ctx->frame, method);
    ExecuteMethod(ctx, method->interpEntry);
    ctx->frame = PopInterpreterFrame(ctx->frame);
    break;
    }
    case MethodType::NATIVE_METHOD: {
    // 通过 P/Invoke 机制调用原生函数
    CallNativeFunction(method->nativeFunction, ctx->registers);
    break;
    }
    }
    }

    这个三重分支确保了 HybridCLR 可以无缝处理任意类型的方法调用。


    五、变换模块与虚拟机集成

    5.1 变换模块(transform/)

    transform 模块在 HybridCLR 的执行管线中扮演预处理器的角色。它在编译器模块之前运行,对 IL 指令序列进行一系列的等价变换,以降低编译器模块的处理复杂度。

    transform 模块处理的主要变换包括:

    • 复杂指令序列的简化:某些复杂的 IL 指令模式(特别是涉及到值类型和泛型的情况)可以被简化为更基本的指令序列,降低后端的处理复杂度
    • 调用约定适配:某些 AOT 方法的调用约定在 IL 层面与热更新代码的调用约定不完全一致,transform 模块负责插入必要的适配代码
    • 静态调用的间接化:将某些直接调用转换为间接调用,以支持 AOT 泛型方法的运行时绑定

    transform 模块的设计原则是"最小变换"——只做必要的变换,不改变程序的语义。所有的变换都是经过验证的等价变换。为了确保变换的正确性,transform 模块在变换前和变换后都会对 IL 序列进行基本的完整性检查(如栈深度的平衡性、分支目标的有效性等),如果检查不通过,变换会被回滚。transform 模块的工作是"按需触发"的——不是所有方法都需要经过变换。在编译器模块的处理流程中,首先检查当前方法是否触发了 transform 模块的变换规则,如果没有,直接跳过变换阶段进入后续的寄存器指令生成。这种设计确保 transform 模块不会成为不必要的性能负担。

    5.2 VM 模块(vm/)

    vm 模块是 hybridclr 与 IL2CPP 运行时之间的桥梁。它封装了对 IL2CPP 内部 API 的调用,供其他四个模块使用。

    vm 模块提供的主要功能组:

    • 动态类型管理:创建和注册新的 Il2CppClass 结构,初始化虚函数表和接口映射表
    • 动态方法管理:创建 MethodInfo 结构,注册到 IL2CPP 的方法分发表中
    • 字段管理:计算字段偏移量,提供字段值读写的辅助函数
    • 对象管理:封装 IL2CPP 的对象创建 API(NewObject、NewArray 等)
    • GC 集成:提供 GC Safe Point 钩子,确保解释器正在执行时 GC 可以安全触发

    vm 模块设计目标之一是"最小化对 IL2CPP 内部实现的依赖"。它通过函数指针表来调用 IL2CPP 的运行时 API,使得 hybridclr 可以在不同的 Unity 版本上工作。函数指针表在 hybridclr 的运行时初始化阶段被构建,关键代码在 vm_module.cpp 的 InitializeVMModule 函数中:

    // VM 模块初始化示意
    void InitializeVMModule()
    {
    // 注册 IL2CPP 运行时 API 到函数指针表
    g_vm_api->object_new = il2cpp_object_new;
    g_vm_api->array_new = il2cpp_array_new_specific;
    g_vm_api->type_get_object = il2cpp_type_get_object;
    g_vm_api->class_from_name = il2cpp_class_from_name;
    g_vm_api->class_get_methods = il2cpp_class_get_methods;
    g_vm_api->class_get_fields = il2cpp_class_get_fields;
    // … 更多运行时 API 注册
    }

    这种设计意味着:当 Unity 发布新版本时,即使 IL2CPP 的内部实现发生了变化,只要运行时 API 的签名保持不变,hybridclr 的代码就不需要修改。如果某个 API 的签名发生了变化,只需要在初始化函数中调整对应的函数指针赋值即可。


    总结

    本文深入剖析了 hybridclr 仓库的源码架构。核心要点:

  • 五个核心模块:metadata(元数据解析)、compiler(IL 编译)、interpreter(解释执行)、transform(代码变换)、vm(VM 桥接),每个模块遵循单一责任原则。

  • 数据流向:DLL 字节数组 → metadata 模块解析 → vm 模块注册到 IL2CPP 类型系统 → compiler 模块编译为寄存器指令 → interpreter 模块执行。

  • 编译策略:延迟编译——元数据在加载时完整解析,寄存器指令生成推迟到方法首次调用时。

  • 解释器核心:跳转表分派 + 虚拟寄存器执行 + 独立栈帧模型,支持与 AOT 代码的无缝互调。

  • 模块间解耦:通过明确定义的数据结构(如 IRInstruction、InterpreterContext)和函数接口来实现模块间的松耦合。

  • 下一篇将深入 il2cpp_plus 适配层,分析它是如何为不同 Unity 版本提供统一的 HybridCLR 接口的。il2cpp_plus 虽然代码量不大,但其设计精髓在于如何以最小的补丁改动支持最大范围的 Unity 版本,这正是 HybridCLR 架构设计智慧的体现。


    参考资源

    • hybridclr GitHub 仓库: GitHub – focus-creative-games/hybridclr: HybridCLR是一个特性完整、零成本、高性能、低内存的Unity全平台原生c#热更新解决方案。 HybridCLR is a fully featured, zero-cost, high-performance, low-memory solution for Unity's all-platform native c# hotupdate. · GitHub
    • ECMA-335 Standard (CLI Metadata): ECMA-335 – Ecma International
    • 原理篇-第 11 篇 原理总览-JIT-vs-AOT-vs-Interpreter
    • 架构篇-第 12 篇 架构总览-HybridCLR整体架构
    赞(0)
    未经允许不得转载:171主机测评 » 13-架构-hybridclr核心运行时
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址