Unity与C#的跨平台原理

目录

目录

一、.Net相关知识

1、.Net是什么

微软的.Net既不是编程语言也不是框架,是类似于互联网时代、次时代、信息时代之类的宣传口号

它是一个包含编程语言、框架、IDE 等产品的系列产品统称。 包含:

  • 框架体系:.Net Framework、.Net Core、Mono 等等

  • 开发语言:C#、VB、F# 等等(C# 是.Net 平台主推的开发语言)

  • 开发工具:Visual Studio、Visual Studio Code 等等

2、微软做.Net平台的目的

  • 跨语言

只要是面向.NET平台的编程语言(C#、VB、C++、F# 等等),用其中一种语言编写的内容可以无缝地用在另一种语言编写的应用程序中。

  • 跨平台

一次编译,不需要任何代码修改,应用程序就可以运行在任意有.NET框架实现的操作系统上,代码不依赖于操作系统,也不依赖硬件环境。

3、如何实现跨语言

  • CLS(Common Language Specification)公共语言规范
    • .Net专门参考每种语言并找出了语言间的共性,定义了一组规则,即语言互操作的标准规范,确保用不同 .NET 编程语言编写的代码可以相互操作。不同的 .Net 开发语言会被翻译生成对应 .Net 语法。
    • **关系:**CLS公共语言规范是CTS的一个子集
  • CTS(Common Type System)公共类型系统
    • 当你需要设计面向 .Net 的语言时需要遵循一个体系,这个体系就是 CTS。它确保 CLR 能够识别和处理的类型,所有 .Net 开发语言中的类型最终都会被编译成 CLR 能够识别的CTS类型。
  • CLI(Common Language Infrastructure)公共语言基础结构
    • 是微软将CTS等内容提交给ECMA(国际组织计算机制造联合会)的一个工业标准。
    • **关系:**它包括了公共类型系统CTS,中间语言CIL、底部文件格式和元数据格式等

4、如何实现跨平台

(1).Net Framework

.Net Framework 在 2002 年推出 1.0 版本,是一个可以快速开发、部署网站服务及应用程序的开发框架,主要用于开发 Windows 下应用程序。包括CLR、虚拟执行系统、.NET Framework 类库等。不过,.Net Framework用于跨语言开发Windows系统下的应用程序,本身并不支持跨平台

.Net Framework体系结构如下图所示,主要做了两件事情:

image-20260320174942406

  • 制作应用程序(图片中CLR上层部分)

    • 流程:编程语言 + 调用各种类库 进行开发 → 生成程序集
    • 其中BCL(基础类库)FCL(框架类库) 是这个框架体系中为我们实现好的各种 API。
    • 程序集的表现就是后缀为 .dll或者.exe格式的文件,其中包含的最关键信息:
  • 让应用程序在操作系统上运行(CLR及下层部分)

    • CLR (Common Language Runtime)公共语言运行时: .Net Framework 的基础,所有的 .Net 技术都建立在此之上。它提供内存管理、线程管理等等核心服务,就好像一个小型的操作系统一样,所以也称为 “.Net虚拟机”。CLR使用**即时编译器(JIT)中间语言(IL)**代码转换成本机机器码,最终在操作系统上运行。
    • 如果想要应用程序在目标操作系统上能够运行,就必须依靠 .Net 提供的 CLR 环境来支持,那就必须在操作系统上安装 .Net Framework。

(2).Net Core

.Net Core 是 2016 年推出的 .Net Framework 的新一代版本,它基于 .Net Framework 来进行设计,主要目的就是跨平台,它是 .Net 技术框架组成在 Windows、MacOS、类 Linux 系统下的具体实现。

它的原理就是为不同的操作系统实现对应的 CLR,这样就可以在不同的平台上,将 IL(中间代码)翻译成机器码,最终在操作系统上运行。

image-20260320183304072

(3)Mono

从 .Net FrameWork 问世到 .Net Core 的诞生,中间 .Net 平台有 14 年的并不跨平台的空窗期。而这段时间内,跨平台是由 Mono 实现的。Mono 的 1.0 版本出现在 2004 年,也就是在 .Net Core 出现之前,Mono 是 .Net 平台实现跨平台的不二之选。

Mono 是一个由 Xamarin 公司(已被微软收购)所赞助的开源项目。它基于 .Net 的 CLI 结构,提供了微软 .Net FrameWork 的另一种实现。它相对 .Net FrameWork 最大的区别就是具备跨平台的能力

Mono 基于 .Net 平台制定的 CLI 公共语言基础结构规则,在各种操作系统上实现了对应的 CLR ,这样不同语言编写的逻辑就能够在指定操作系统上运行了,它的这一套规则是在 .Net Framework 规则上进行的修改和添加。

(4)总结

  • .Net Framework(2002 年发布):部分开源,用于开发 Windows 平台下应用,包含 Windows 平台的所有特性
  • .Net Core(2016 年发布):完全开源,针对多个平台开发应用,包含 .Net Framework 部分特性
  • Mono(2004 年发布):完全开源,早期乃至现在也是 .Net 的跨平台解决方案

二、Unity跨平台(Mono)

1、Unity和Mono的关系

学习了.NET的跨平台原理后,我们知道 Mono 是基于 .Net 的跨平台方案,同时具备 .Net 平台的跨语言特点,Mono 的第一个版本在 2004 年发布。

而 Unity 公司于 2004 年成立,Unity 底层由 C/C++ 实现,虽然 C++ 本身跨平台,但作为上层开发语言学习门槛高、选择少。为了降低开发者门槛、提升易用性,Mono 成为当时的不二之选。Mono 的跨平台跨语言两大特性,完美匹配 Unity 的需求。

2、Unity 跨平台的必备概念

  • Unity Engine(引擎)

    • 提供 UnityEngine.dll 动态库,各平台版本不同
    • 由 C/C++ 编写,包含平台相关代码、图形 API、物理引擎、灯光等所有游戏引擎底层内容
  • Unity Editor(编辑器)

    • 提供 UnityEditor.dll 动态库,大部分由 C# 编写

    • 早期用户脚本可使用 C#、JavaScript、Boo 语言编写,项目代码最终由 Mono 编译

3、Mono

  • Mono跨平台原理

Mono 利用 .Net 平台制定的 CLI(公共语言基础结构) 规则,把多种语言编译成通用规范的 CIL(公共中间语言),再通过 CLR(公共语言运行时),将 CIL 转换为对应操作系统的原生代码,这样不同语言编写的逻辑就能在指定操作系统上运行

  • Mono 主要构成部分
    • C# 编译器(mcs)
    • Mono Runtime(类似 CLR 公共语言运行时,即 .Net 虚拟机),包含 JIT(即时编译器)、AOT(提前编译器)、GC(垃圾回收)、类库加载器等核心模块
    • BCL(基础类库)
    • Mono 类库,提供很多超出 .Net 标准的额外功能,主要用于构建各种操作系统的应用程序

4、Unity跨平台原理(Mono)

image-20260320193915577

在 Unity 中,无论使用哪种语言编写逻辑代码,在发布时都会被编译成 IL 中间代码

最终这些中间代码会在对应操作系统上,通过 Mono VM(虚拟机) 翻译成机器码来执行。

C#代码 → Mono C#编译器 → IL中间代码 → Mono VM → 操作系统原生代码

image-20260320200306492

5、Mono优缺点

  • 优点

    • 只要在不同操作系统上实现对应的 Mono VM(虚拟机),就能支持几乎 “无限” 多的平台。
  • 缺点

    • **维护工作耗时耗力:**Unity 版本更新时,Mono VM 也需要同步维护和更新,面对大量平台时工作量极大。
    • **版本兼容性问题:**低版本 Mono 无法支持新版本 C# 的强大新特性。

三、Unity跨平台(IL2CPP)

1、概念

IL2CPP 是在 Unity 4.6.1 p5 之后版本中加入的脚本后处理方式,是继 Mono 之后的一种跨平台解决方案。它的核心作用是:将 IL 中间代码转译为 C++ 代码。(IL To Cpp)

2、Unity跨平台原理跨平台原理(IL2CPP)

流程

  • C#/UnityScript 等脚本和一些库文件 → 编译为 IL 中间代码
  • IL 中间代码 → 通过 il2cpp.exe 转译为 C++ 代码
  • C++ 代码再由各平台优化过的 C++ 编译器编译为对应平台的目标代码

image-20260320200803897

与 Mono 的核心区别

  • Mono:生成 IL 后,直接由 Mono VM(虚拟机)在运行时转译执行
  • IL2CPP:生成 IL 后,先转译为 C++ 代码,再由平台 C++ 编译器直接编译为可执行的原生汇编代码。运行时不再需要 Mono 虚拟机,而是 IL2CPP VM(虚拟机),它主要负责完成 GC 管理、线程创建 等运行时服务工作。这是因为虽然中间代码被转译为了 C++,但内存管理仍遵循 C# 中的 GC方式。

C#代码 → Mono C#编译器 → IL中间代码 → IL2CPP → C++代码 → C++编译器 → 原生汇编代码 → IL2CPP VM

image-20260320201249278

3、Mono 和 IL2CPP 的区别

  • Mono 特点

    • 构建速度快**(最终打包时)**
    • 采用 JIT 即时编译,边编译边执行
    • 代码必须发布为托管程序集(.dll 文件)
    • Mono VM 平台维护麻烦,部分平台(如 WebGL)不支持
    • 受 Mono 版本授权限制,很多 C# 新特性无法使用
    • IOS 支持 Mono,但不允许 32 位 Mono 应用提交到应用商店
  • IL2CPP 特点

    • 构建速度比 Mono 慢**(最终打包时)**
    • 仅支持 AOT 提前编译
    • 可启用引擎代码剥离,剥离未使用的代码,减小包体积
    • 程序运行效率更高、速度更快
    • 多平台移植更方便(C++跨平台方便)
  • 最大区别

    IL2CPP 无法在运行时动态生成代码和类型,必须在编译时完全确定所有用到的类型。

    举例:List<A>List<B> 必须在代码中显式调用过,IL2CPP 才会保留这两个泛型类型;如果热更新时调用未显式使用过的 List<C>,就会报错。

    本质原因:Mono 是 JIT(运行时编译),IL2CPP 是 AOT(提前编译)。

实际开发中推荐使用IL2CPP,因为运行效率高

四、IL2CPP相关问题处理

1、使用

可以在Player Settings的这个位置进行切换,使用IL2CPP时,需要在UnityHub中安装IL2CPP的模块

image-20260320201902067

2、代码剥离问题

问题描述

IL2CPP 在打包时会自动对 Unity 工程的 DLL 进行裁剪,将代码中没有引用到的类型裁剪掉,以达到减小发布后包的尺寸的目的。

然而在实际使用过程中,很多类型有可能会被意外剪裁掉,造成运行时抛出找不到某个类型的异常。

解决方案

  • 方式一:IL2CPP 处理模式时,将PlayerSetting->Other Setting->Managed Stripping Level(代码剥离)设置为合适的等级。Managed Stripping Level各个等级如下:

    • Disable只有 Mono 模式下才能设置,不删除任何代码
    • Minimal:IL2CPP 默认级别,只裁剪系统库,不裁剪用户代码,最安全,几乎不会出问题
    • Low:默认低级别,保守的删除代码,最大程度减少剥离实际使用的代码的可能性
    • Medium:中等级别,不如低级别剥离谨慎,也不会达到高级别的极端
    • High:高级别,尽可能多的删除无法访问的代码,有限优化尺寸减小。如果选择该模式一般需要配合link.xml使用
  • 方式二:通过 Unity 提供的 link.xml 方式来告诉 Unity 引擎,哪些类型是不能够被剪裁掉的

3、泛型使用问题

问题描述

我们之前提到了 IL2CPP 和 Mono 最大的区别是IL2CPP不能在运行时动态生成代码和类型。

就是说,如果你在打包生成前没有把要用的泛型类型显式使用一次,后面使用这个没有被编译的类型,就会出现找不到类型的报错。

举例:List<A>List<B>中 A 和 B 是我们自定义的类,我们必须在代码中显式地调用过,IL2CPP 才能保留List<A>List<B>两个类型。如果在热更新时我们调用List<C>,但是它之前并没有在代码中显式调用过,那么这时就会出现报错。主要就是因为JITAOT两个编译模式的不同造成的。

解决方案

  • 只需要代码中显示调用即可,在预编译之前让 IL2CPP 知道我们需要使用这个内容
    • 泛型类:声明一个类,然后在这个类中声明一些 public 的泛型类变量
    • 泛型方法:写一个静态方法,在其中将泛型方法调用一下。这个静态方法无需被调用

五、Unity和C#版本的关系

1、Unity 版本支持的 C# 版本

Unity不同版本支持的C#版本不同,具体可以查看官方文档:Unity - Manual: C# compiler

以Unity2021为例,支持的版本是C# 9.0

为什么会有不同的C#版本呢?因为随着 Unity 的更新,一般会采用较新的编译器和运行时版本。比如Unity 2020.3 使用的脚本运行时版本等效于 .Net 4.6,编译器为 Roslyn。

2、Unity 的.Net API 兼容级别

PlayerSetting->Other Setting->Api Compatibility Level中,我们可以设置.Net API的兼容级别,主要有两种选择:

  • .NET Framework(.Net 4.x)(特殊需求时)

    • 具备较为完整的.Net API,甚至包含了一些无法跨平台的 API

    • 适合 Windows 平台专用、需使用 .NET Standard 2.1 以外功能的场景

  • .Net Standard 2.1(默认/建议使用)

    • 是一个.Net 标准 API 集合,相对.Net 4.x 包含更少的内容,可以减小最终可执行文件大小

    • 它具有更好的跨平台支持

    • .Net Standard 2.0 配置文件大小是.Net 4.x 配置文件的一半