Unity C# 跨平台技术一览
从 ILRuntime 官网上的定义可知,ILRuntime 实现了 IL 解释执行虚拟机和自己的 IL 托管栈来模拟代码的执行,在 ILRuntime 解释执行期间,所有的对象都是用 StackObject 表示的,没有新类型的生成,所以不存在运行时编译的情况,由此可以实现热更新的动态加载。它能够访问更多的类库和函数,同时提供了更好的兼容性,尤其是在跨平台项目中。参考文档:https://our
1. JIT、AOT区别
JIT:Just In Time,动态编译,边运行边编译,即时编译;
AOT:Ahead Of Time,指运行前编译,静态编译;
AOT代表:C/C++开发的应用,它们必须在执行前编译成机器码;
JIT代表:JavaScript、Python等;
注意:JIT和AOT指的是程序运行方式,和编程语言并非强关联的,有些语言 JIT、AOT运行方式都可以运行,如Java、Python,它们可以在第一次执行时编译成中间字节码、然后在之后执行时可以直接执行字节码。
CLI(Common Language Infrastructure):公共语言基础结构
CIL(Common Intermidiate Language):也叫IL 通用中间语言
CLR(Common Language Runtime):通用语言运行平台
.NET Standard 实现标准 ( 算不算 CLI ?)
2016年的.NET Core 1.0同时发布
.NET Standard 1.0...、.NET Standard 2.0、.NET Standard 2.1 每个版本实现的.NET APIs 逐步完善,.NET Standard 2.1实现了 全部37,118个APIs
.NET Framework、.NET Core、Mono 实现了这个标准。(CLR)
参考网站:
https://learn.microsoft.com/en-us/dotnet/standard/net-standard?tabs=net-standard-1-0
https://dotnet.microsoft.com/zh-cn/platform/support/policy/dotnet-core
https://blog.csdn.net/wangnaisheng/article/details/137456802
https://www.cnblogs.com/daxnet/p/18299758
2.MONO、IL2CPP

MONO VM 可以理解成 Java VM

IL2CPP VM:虽然通过IL2CPP以后代码变成了静态的C++,但是内存管理这块还是遵循C#的方式,这也是为什么最后还要有一个 IL2CPP VM的原因:它负责提供诸如GC管理,线程创建这类的服务性工作。但是由于去除了IL加载和动态解析的工作,使得IL2CPP VM可以做的很小,并且使得游戏载入时间缩短。

IL2CPP为什么替换MONO
-
Mono VM在各个平台移植,维护非常耗时,有时甚至不可能完成
-
MONO 版本授权限制
-
效率问题
3.NET Standard、.NET Framework、 .NET Core、.Net 5...
3.1.NET Standard、.NET Framework、.NET Core、.Net 5...
.NET Standard:是一套正式的 API 规范,定义了 .NET 平台上一组通用的基础类库(BCL)和功能接口。 .NET Standard 不包含 .NET Framework 或 .NET Core。它是一个抽象的规范集合,不是一个具体的实现框架。各个 .NET 实现(包括 .NET Framework 和 .NET Core)需按照 .NET Standard 规范来实现相应的 API,从而使得符合该标准的类库能在这些实现上运行。
.NET Framework:是一个专为 Windows 平台设计和优化的开发框架,提供了丰富的类库、运行时环境(CLR)、开发工具和应用程序模型(如 WinForms、WPF、ASP.NET 等)。(注:2016年后微软转向.NET Core跨平台战略)
.NET Core:是一个开放源代码、跨平台的 .NET 实现,最初设计目标是提供轻量级、模块化且高性能的开发框架,适用于云、微服务、容器化和物联网场景。自2016年起,.NET Core在发布1.0-3.1版本。
.NET 5: 于 2020 年发布,随后微软相继推出了 .NET 6、7、8、9 和 10 等后续版本。
3.2 Unity 里面的 .NET Standard 2.1、.NET Framework
Project Settings -> Player -> Other Settings:API Compatibility Level
.NET Standard 2.1
特点: 这是一个更现代的 .NET 标准,支持更多的 API,接近于完整的 .NET Framework。它能够访问更多的类库和函数,同时提供了更好的兼容性,尤其是在跨平台项目中。
优点:
支持更多的 API 和类库,适合使用较新的 .NET 特性和第三方库。
更好地支持现代 .NET 标准,适合跨平台开发(如移动、桌面、WebGL 等)。
缺点:
某些平台可能不完全支持所有的 .NET Standard 2.1 功能,特别是在非常低端的设备上,可能会出现兼容性问题。
生成的代码可能会稍大一些,运行时内存占用也可能更高。
.NET Framework
特点: 这个选项与老版本的 Mono/.NET Framework 4.x 相对应,提供更广泛的兼容性,但缺少一些较新的 .NET API 和功能。
优点:
兼容性更好,尤其是对老旧设备或平台(如某些移动平台或 WebGL)更加友好。
适合开发针对低性能设备或需要极小内存占用的项目。
缺点:
不支持最新的 .NET 特性,某些现代的第三方库可能无法使用。
开发体验相对较弱,API 不够丰富。
参考文档:
https://blog.csdn.net/tealcwu/article/details/142092599
https://learn.microsoft.com/zh-cn/visualstudio/gamedev/unity/unity-scripting-upgrade
https://unity.com/cn/blog/engine-platform/unity-and-net-whats-next
https://developer.unity.cn/projects/62bbc040edbc2a7848d45ae8
4.ILRuntime
使用方法
Unity 项目的 Packages/manifest.json
在这个文件的dependencies节点前增加以下代码,
"scopedRegistries": [
{
"name": "ILRuntime",
"url": "https://registry.npmjs.org",
"scopes": [
"com.ourpalm"
]
}
],
参考文档:https://ourpalm.github.io/ILRuntime/public/v1/guide/tutorial.html
注意: PlayerSettings中勾选Allow unsafe code + Api Compatibility Level 改为 .NET Framework 解决编译问题。
基本原理
ILRuntime 的使用是需要区分两个 VS 工程,一个是 Unity 生成的主工程,另一个就是生成 DLL 文件的热更工程,ILRuntime 通过解析 DLL 文件实现热更新。
根据 ILRuntime 官网上的介绍:"ILRuntime 借助 Mono.Cecil 库来读取 DLL 的 PE 信息,以及当中类型的所有信息,最终得到方法的 IL 汇编码,然后通过内置的 IL 解译执行虚拟机来执行 DLL 中的代码。"
DLL 的内容就是 IL 指令,CIL 类似一个面向对象的组合语言,并且它是完全基于堆栈的,它运行在虚拟机上。从 ILRuntime 官网上的定义可知,ILRuntime 实现了 IL 解释执行虚拟机和自己的 IL 托管栈来模拟代码的执行,在 ILRuntime 解释执行期间,所有的对象都是用 StackObject 表示的,没有新类型的生成,所以不存在运行时编译的情况,由此可以实现热更新的动态加载。
参考文档:https://lzhenhong.github.io/post/unity-re-geng-ilruntime-ji-ben-yuan-li/
// 热更新代码 int a = 1; ILIntepreter.cs Execute 做了什么?
Execute:Nop // 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。
Execute:Ldc_I4_1 // 将整数值 1 作为 int32 推送到计算堆栈上
Execute:Stloc_0 // 从计算堆栈的顶部弹出当前值并将其存储到索引 0 处的局部变量列表中。
Execute:Ret // 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。
// 热更新代码 HelloWorld.Test(); ILIntepreter.cs Execute做了什么?
Execute:Nop // 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。
Execute:Call // 通过调用约定描述的参数调用在计算堆栈上指示的方法(作为指向入口点的指针)。
Execute:Nop // 如果修补操作码,则填充空间。尽管可能消耗处理周期,但未执行任何有意义的操作。
Execute:Ret // 从当前方法返回,并将返回值(如果存在)从调用方的计算堆栈推送到被调用方的计算堆栈上。
参考文档:https://www.cnblogs.com/revoid/p/11254798.html
4.1委托
参考文档:https://ourpalm.github.io/ILRuntime/public/v1/guide/delegate.html
4.1.1直接使用
如果只在热更新的DLL项目中使用的委托,是不需要任何额外操作的,就跟在通常的C#里那样使用即可。
用Action或者Func当作委托类型的话,可以避免写转换器,所以项目中在不必要的情况下尽量只用Action和Func。
4.1.2委托适配器-DelegateAdapter
如果你需要将委托实例传给ILRuntime外部使用,那则根据情况,你需要额外添加适配器或者转换器。
appDomain.DelegateManager.RegisterMethodDelegate //注册仅需要注册不同的参数搭配即可
appDomain.DelegateManager.RegisterFunctionDelegate //带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
appDomain.DelegateManager.RegisterMethodDelegate //Action<string> 的参数为一个string
4.1.3 委托转换器-DelegateConvertor
如果你需要将委托实例传给ILRuntime外部使用,那则根据情况,你需要额外添加适配器或者转换器。
appdomain.DelegateManager.RegisterDelegateConvertor <TestDelegateMethod> //ILRuntime内部是用Action和Func这两个系统内置的委托类型来创建实例的,所以其他的委托类型都需要写转换器
4.2 跨域继承
如果在热更新项目中,继承一个Unity主工程里的类、实现一个主工程里的接口。
TypeLoadException: Cannot find Adaptor for:TestClassBasePrivate
using UnityEngine;
public abstract class TestClassBasePrivate
{
public virtual int Value
{
get
{
return 0;
}
set
{
}
}
public virtual void Log(string str)
{
Debug.Log("!! TestClassBasePrivate.Log, str = " + str);
}
}
// 下面是 使用 GenerateCrossBindingAdapterCode 自动生成的跨域继承代码
using System;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
#if DEBUG && !DISABLE_ILRUNTIME_DEBUG
using AutoList = System.Collections.Generic.List<object>;
#else
using AutoList = ILRuntime.Other.UncheckedList<object>;
#endif
namespace ILRuntimeDemo
{
public class TestClassBasePrivateAdapter : CrossBindingAdaptor
{
public override Type BaseCLRType
{
get
{
return typeof(global::TestClassBasePrivate);
}
}
public override Type AdaptorType
{
get
{
return typeof(Adapter);
}
}
public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
return new Adapter(appdomain, instance);
}
public class Adapter : global::TestClassBasePrivate, CrossBindingAdaptorType
{
CrossBindingFunctionInfo<System.Int32> mget_Value_0 = new CrossBindingFunctionInfo<System.Int32>("get_Value");
CrossBindingMethodInfo<System.Int32> mset_Value_1 = new CrossBindingMethodInfo<System.Int32>("set_Value");
CrossBindingMethodInfo<System.String> mLog_2 = new CrossBindingMethodInfo<System.String>("Log");
bool isInvokingToString;
ILTypeInstance instance;
ILRuntime.Runtime.Enviorment.AppDomain appdomain;
public Adapter()
{
}
public Adapter(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance)
{
this.appdomain = appdomain;
this.instance = instance;
}
public ILTypeInstance ILInstance { get { return instance; } }
public override void Log(System.String str)
{
if (mLog_2.CheckShouldInvokeBase(this.instance))
base.Log(str);
else
mLog_2.Invoke(this.instance, str);
}
public override System.Int32 Value
{
get
{
if (mget_Value_0.CheckShouldInvokeBase(this.instance))
return base.Value;
else
return mget_Value_0.Invoke(this.instance);
}
set
{
if (mset_Value_1.CheckShouldInvokeBase(this.instance))
base.Value = value;
else
mset_Value_1.Invoke(this.instance, value);
}
}
public override string ToString()
{
IMethod m = appdomain.ObjectType.GetMethod("ToString", 0);
m = instance.Type.GetVirtualMethod(m);
if (m == null || m is ILMethod)
{
if (!isInvokingToString)
{
isInvokingToString = true;
string res = instance.ToString();
isInvokingToString = false;
return res;
}
else
return instance.Type.FullName;
}
else
return instance.Type.FullName;
}
}
}
}
4.3 反射
通过反射创建实例
壳子代码:
ILRManager -> CreateHotBehaviour -> ILRBehaviour : MonoBehaviour -> Awake -> ILRManager.Instance.CreateHotBehaviour
热更新代码
Main -> CreateBehaviour -> BhvManager.CreateBehaviour -> Activator.CreateInstance
4.4 CLR重定向
参考文档:https://ourpalm.github.io/ILRuntime/public/v1/guide/redirection.html
参考例子:
var mi = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) });
appdomain.RegisterCLRMethodRedirection(mi, Log_11);
// 源码解析 注册
// appdomain.RegisterCLRMethodRedirection
// redirectMap[mi] = func; redirectMap => RedirectMap;
// 源码解析 调用
var redirect = cm.Redirection -> RedirectMap.TryGetValue -> esp = redirect(...)
4.5 CLR绑定
参考官方文档即可:
参见文档:https://ourpalm.github.io/ILRuntime/public/v1/guide/bind.html
4.6 断点调试
需要开启:启动调试代码
appdomain.DebugService.StartDebugService(56000);
建议使用Rider:
Mac 可以调试的版本为:2023.1
Window 版本 应该不限制 参考文档即可
https://plugins.jetbrains.com/plugin/19528-ilruntime-debugger
这里是一个专注于游戏开发的社区,我们致力于为广大游戏爱好者提供一个良好的学习和交流平台。我们的专区包含了各大流行引擎的技术博文,涵盖了从入门到进阶的各个阶段,无论你是初学者还是资深开发者,都能在这里找到适合自己的内容。除此之外,我们还会不定期举办游戏开发相关的活动,让大家更好地交流互动。加入我们,一起探索游戏开发的奥秘吧!
更多推荐
所有评论(0)