【学习笔记】Unity跨平台的基本原理

一、.Net相关知识

1、基本信息

.Net是由微软开发的一整套技术体系的统称,其中包含:

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

开发语言:C#、VB、F#等

开发工具:VisualStudio、VisualStudioCode等

2、微软开发.Net的目的

(1)实现跨语言

        对于面向.Net平台的编程语言,可以无缝穿插在另一种面向.Net平台的编程语言开发的应用程序中

(2)实现跨平台

        一次编译后,程序可以运行在任何有.Net框架实现的操作系统中

(3)实现公司市场垄断地位

3、.Net实现跨语言的方式

相关概念:

(1)CLS(Common Language Specification)公共语言规范

        由.Net定义的一组语言互操作的标准规范,只要开发者遵循规范编码,那么代码就能被任意.Net平台支持的语言通用

(2)CTS(Common Type System)公共类型系统

        当你需要设计面向.Net的语言时需要遵循一个体系,这个体系就是CTS,CLS公共语言规范就是是CTS公共类型系统的子级, 一个编程语言,如果 它能够支持CTS,那么我们就称它为面向.NET平台的语言
(3)CLI(Common Language Infrastructure) 公共语言基础结构
        是微软将CTS等内容提交给国际组织计算机制造联合会ECMA的一个工业标准

4、.Net实现跨平台的方式

相关概念:

(1).Net Framework(仅支持跨语言)

简介:

        一套用于开发Windows平台下网站服务和应用程序的框架,其中包括公共语言运行时(Common Language Runtime,CLR),虚拟执行系统,.NET Framework类库等

体系结构:

基本流程:

        使用基于CLS公共语言规范的语言在IDE中进行开发,其中会用到框架提供的基类库,在进行打包编译成程序集的过程中,会将这些语言(C#、VB等)翻译成CIL(通用中间代码),最后通过CLR再次编译产生机器码在操作系统最终运行。

        CLR(Common Language Runtime)它是一个在执行时管理代码的代码,提供内存管理、 线程管理等等核心服务,就好像一个小型的操作系统一样,所以形象的把它称为“.Net虚拟机”

(2).Net Core(支持跨语言跨平台)

简介:

        2016年推出的,在.Net Framework基础上开发实现的,它的原理就是为不同的操作系统实现对应的 CLR公共语言运行时(.Net虚拟机),这样就可以在不同的平台上将IL(中间代码)翻译成机器码最终在操作系统上运行了。

(3)Mono(实现跨语言和跨平台)(.Net Core出现之前的主流跨平台实现方式)

原理(类似与.Net Core):

Mono利用 .Net平台制定的 CLI公共语言基础结构规则, 利用它 把很多种语言编译成通用规范的CIL公共中间语言, 利用CLR公共语言运行时 ,将这些 CIL公共中间语言转换为操作系统的原生代码(主要做的就是在各种操作系统上实现了对应的CLR内容), 这样 用各种不同语言编写的逻辑就能够在指定操作系统上运行了。

二、Unity跨平台的基本原理(Mono)

1、Unity和Mono的关系

        在Unity引擎开发初期,只有Mono作为既能跨语言也能跨平台的框架体系,十分契合Unity开发者的目的,借助Mono框架Unity开发者可以轻松实现一次开发支持多个平台的目的。

2、Unity跨平台的必备概念

Unity主要包括两个部分:

Unity Engine(引擎):
提供UnityEngine.dll动态库,各平台不同,C/C++编写,包含平台相关代码、
图形API、物理引擎、灯光等等所有游戏引擎底层内容
Unity Editor(编辑器):
提供UnityEditor.dll动态库,大部分由C#编写,用户脚本最初可以使用C#、
JavaScript、Boo语言编写,项目代码最后由Mono编译
Mono的主要构成部分:
1.C#编译器(mcs)
2.Mono Runtime 类似CLR公共语言运行时(虚拟机)
包括JIT(Just in time)即时编译器、AOT(Ahead of time)提前编译器、
GC、类库加载器 等等
3.BCL基础类库
4.Mono类库
提供很多超出.Net的一些额外功能,主要用于构建各种操作系统上的应用

3、Unity跨平台的基本原理(Mono)

        在Unity中使用C#进行开发,发布时C#被编译成IL中间代码,最终这些代码在对应的操作系统上通过Mono VM(虚拟机)翻译成机器码,最终在对应的操作系统中运行起来。

4、基于Mono跨平台的优缺点

优点:只要在不同操作系统上实现Mono VM,就可以支持无限多的平台

缺点:维护工作耗时耗力,Unity版本更新时,Mono VM也需要针对所有平台进行维护和更新,而且低版本的Mono无法支持新版本C#的强大新特性

三、Unity跨平台的基本原理(IL2CPP)

1、IL2CPP基本概念和原理

        继Mono之后的一种跨平台解决方案,通过将编译好的IL中间代码转译为C++代码,再利用各平台优化过的编译器编译为对应平台的目标代码。

        IL2CPP和Mono的区别就在于,当生成了IL中间代码后,Mono是直接通过虚拟机转译运行,而IL2CPP的步骤多了一些会 将IL中间代码转译为C++代码,再通过各平台的C++编译器直接编译
为可执行的原生汇编代码。
注意:
虽然中间代码变为了C++ 但是内存管理还是遵循C#中GC的方式 ,这也是为什么有一个IL2CPP VM(虚拟机)存在的原因,它主要是用来完成GC管理,线程创建等服务工作。

2、Mono和IL2CPP的区别

Mono:

(1)构建(最终打包时)速度快

(2)Mono编译机制是JIT即时编译,所以支持更多类库

(3)必须将代码发布为托管程序集(.dll文件)

(4)Mono VM虚拟机平台维护麻烦,且部分平台不支持(比如WebGL)

(5)由于Mono版本授权原因,C#很多新特性无法使用

(6)IOS支持Mono,但不在允许32位的Mono应用提交到应用商店

IL2CPP:

(1)相对Mono构建(最终打包时)速度慢

(2)只支持AOT提前编译

(3)可以启用引擎代码剥离来减少代码的大小

(4)程序的运行效率比Mono高,运行速度快

(5)多平台移植更加方便

最大的区别:

        IL2CPP不能在运行时动态生成代码和类型,所以必须在编译时就完全确定需要用到的类型

四、两种跨平台方式的选择

        更推荐使用IL2CPP,其打包出来的代码运行效率能有明显的提升,并且包体更小。

五、IL2CPP打包工具的使用方式

1、IL2CPP打包工具的安装和使用

(1)安装方式:

UnityHub => Installs => 对应Unity版本右侧的齿轮 => Add modules => Windows Build Support(IL2CPP) 

(2)使用方式:

开启方式:

Unity => Edit => Project Settings => Player => Configuration => Scripting Backend => IL2CPP

重要参数:

Optimization => Managed Stripping Level 代码剥离强度,通常开启High模式,然后通过link.xml配置不希望被剥离的类。

<?xml version="1.0" encoding="UTF-8"?>
  
  <!--保存整个程序集-->
  <assembly fullname="UnityEngine" preserve="all"/>
  <!--没有“preserve”属性,也没有指定类型意味着保留所有-->
  <assembly fullname="UnityEngine"/>

  <!--完全限定程序集名称-->
  <assembly fullname="Assembly-CSharp, Version=0.0.0.0, Culture=neutral, PublicKeyToken=null">
    <type fullname="Assembly-CSharp.Foo" preserve="all"/>
  </assembly>

  <!--在程序集中保留类型和成员-->
  <assembly fullname="Assembly-CSharp">
    <!--保留整个类型-->
    <type fullname="MyGame.A" preserve="all"/>
    <!--没有“保留”属性,也没有指定成员 意味着保留所有成员-->
    <type fullname="MyGame.B"/>
    <!--保留类型上的所有字段-->
    <type fullname="MyGame.C" preserve="fields"/>
    <!--保留类型上的所有方法-->
    <type fullname="MyGame.D" preserve="methods"/>
    <!--只保留类型-->
    <type fullname="MyGame.E" preserve="nothing"/>
    <!--仅保留类型的特定成员-->
    <type fullname="MyGame.F">
      <!--类型和名称保留-->
      <field signature="System.Int32 field1" />
      <!--按名称而不是签名保留字段-->
      <field name="field2" />
      <!--方法-->
      <method signature="System.Void Method1()" />
      <!--保留带有参数的方法-->
      <method signature="System.Void Method2(System.Int32,System.String)" />
      <!--按名称保留方法-->
      <method name="Method3" />

      <!--属性-->
      <!--保留属性-->
      <property signature="System.Int32 Property1" />
      <property signature="System.Int32 Property2" accessors="all" />
      <!--保留属性、其支持字段(如果存在)和getter方法-->
      <property signature="System.Int32 Property3" accessors="get" />
      <!--保留属性、其支持字段(如果存在)和setter方法-->
      <property signature="System.Int32 Property4" accessors="set" />
      <!--按名称保留属性-->
      <property name="Property5" />

      <!--事件-->
      <!--保存事件及其支持字段(如果存在),添加和删除方法-->
      <event signature="System.EventHandler Event1" />
      <!--根据名字保留事件-->
      <event name="Event2" />
    </type>

    <!--泛型相关保留-->
    <type fullname="MyGame.G`1">
      <!--保留带有泛型的字段-->
      <field signature="System.Collections.Generic.List`1&lt;System.Int32&gt; field1" />
      <field signature="System.Collections.Generic.List`1&lt;T&gt; field2" />

      <!--保留带有泛型的方法-->
      <method signature="System.Void Method1(System.Collections.Generic.List`1&lt;System.Int32&gt;)" />
      <!--保留带有泛型的事件-->
      <event signature="System.EventHandler`1&lt;System.EventArgs&gt; Event1" />
    </type>


    <!--如果使用类型,则保留该类型的所有字段。如果类型不是用过的话会被移除-->
    <type fullname="MyGame.I" preserve="fields" required="0"/>

    <!--如果使用某个类型,则保留该类型的所有方法。如果未使用该类型,则会将其删除-->
    <type fullname="MyGame.J" preserve="methods" required="0"/>

    <!--保留命名空间中的所有类型-->
    <type fullname="MyGame.SomeNamespace*" />

    <!--保留名称中带有公共前缀的所有类型-->
    <type fullname="Prefix*" />

  </assembly>
  
  

</linker>

2、针对IL2CPP打包方式中存在的问题的解决方式

using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace MyGame
{
    public class Test
    {

    }

    public class A
    { }

}

public class A
{

}

public class B
{

}

public class C
{

}

public class IL2CPP_Info
{
    public List<A> list;
    public List<B> list2;
    public List<C> list3;

    public Dictionary<int, string> dic = new Dictionary<int, string>();

    public void Test<T>(T info)
    {

    }

    public static void Test()
    {
        IL2CPP_Info info = new IL2CPP_Info();
        info.Test<int>(1);
        info.Test<float>(1);
        info.Test<bool>(true);
    }

}



public class Lesson1 : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        #region 知识点一 —— 安装Unity IL2CPP打包工具
        //在Unityhub中下载 IL2CPP打包相关工具
        #endregion

        #region 知识点二 —— IL2CPP打包存在的问题——类型裁剪
        //IL2CPP在打包时会自动对Unity工程的DLL进行裁剪,将代码中没有引用到的类型裁剪掉,
        //以达到减小发布后包的尺寸的目的。
        //然而在实际使用过程中,很多类型有可能会被意外剪裁掉,
        //造成运行时抛出找不到某个类型的异常。
        //特别是通过反射等方式在编译时无法得知的函数调用,在运行时都很有可能遇到问题

        //解决方案:
        //1.IL2CPP处理模式时,将PlayerSetting->Other Setting->Managed Stripping Level(代码剥离)设置为Low
        // Disable:Mono模式下才能设置为不删除任何代码
        // Low:默认低级别,保守的删除代码,删除大多数无法访问的代码,同时也最大程度减少剥离实际使用的代码的可能性
        // Medium:中等级别,不如低级别剥离谨慎,也不会达到高级别的极端
        // Hight:高级别,尽可能多的删除无法访问的代码,有限优化尺寸减小。如果选择该模式一般需要配合link.xml使用

        //2.通过Unity提供的link.xml方式来告诉Unity引擎,哪些类型是不能够被剪裁掉的
        //  在Unity工程的Assets目录中(或其任何子目录中)建立一个叫link.xml的XML文件
        #endregion

        #region 知识点三 —— IL2CPP打包存在的问题——泛型问题
        //我们上节课提到了IL2CPP和Mono最大的区别是 不能在运行时动态生成代码和类型
        //就是说 泛型相关的内容,如果你在打包生成前没有把之后想要使用的泛型类型显示使用一次
        //那么之后如果使用没有被编译的类型,就会出现找不到类型的报错

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

        //解决方案:
        //泛型类:声明一个类,然后在这个类中声明一些public的泛型类变量
        //泛型方法:随便写一个静态方法,在将这个泛型方法在其中调用一下。这个静态方法无需被调用
        //这样做的目的其实就是在预言编译之前让IL2CPP知道我们需要使用这个内容
        #endregion

        #region 总结
        //对于我们目前开发的新项目
        //都建议大家使用IL2CPP脚本后处理模式来进行打包
        //主要原因是因为它的效率相对Mono较高,同时由于它自带裁剪功能,包的大小也会小一些
        //但是如果在测试时出现 类型无法识别等问题
        //需要用到我们这节课学习的知识点来解决这些问题
        #endregion
    }

    // Update is called once per frame
    void Update()
    {
        
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值