ILRuntime使用讲解

ILRuntime使用讲解ILRuntime 使用讲解快速入门 ILRuntime 的作用为什么要用到 ILRuntimeILR 使用环境部署生成 Unity Model dll 文件和 Unity HotFix dll 文件加载 ILRuntime 的作用用于 unity 游戏的热更新 其语言是由 C 写的 所以很受 unity 工程师的喜爱 这样就不再用 xlua 脚本进行热更新 ILRuntime 官方讲解为什么要用到 ILRuntime 我们知道他的目的后 它主要是用来进行游戏的更新操作 但是更新的流程是玩家运行游戏查看使用的游戏版本和我们上传

ILRuntime的作用

为什么要用到ILRuntime

我们知道他的目的后,它主要是用来进行游戏的更新操作,但是更新的流程是玩家运行游戏查看使用的游戏版本和我们上传到服务器版本是否相同,不同则进行更新操作。所以这里我们就用到了程序之间的跨域使用。而ILRuntime就是充当中介的作用实现跨域操作。

ILRuntime的实现原理

ILRuntime借助Mono.Cecil库来读取DLL的PE信息,以及当中类型的所有信息,最终得到方法的IL汇编码,然后通过内置的IL解译执行虚拟机来执行DLL中的代码。

ILRuntime使用

环境部署

.在Assetts中创建HotFix文件夹,Model文件夹两个文件夹,

<linker> <assembly fullname="Unity.Model" preserve="all"/> <assembly fullname="Unity.ThirdParty" preserve="all"/> <assembly fullname="unityEngine" preserve="all"/> <assembly fullname="System" preserve="all"/> </linker> 

将文件中的内容替换成该内容

很好,现在我们的环境搭建完毕。

生成Unity.Model.dll文件和Unity.HotFix.dll文件

加载unityHotFix.dll和Unity.HotFix.pdb文件

我们HotFix问价夹存放的都是我们需要用到热更新的代码,而Modle使我们初始化打包的脚本文件夹,简而言之,我们所有的代码大部分存放在HotFix,Mondle,ThirdParty文件夹中,除非有些脚本要放在Edtior问价夹中。因为这样我们可以很方便的管理脚本。

为什么加载unityHotFix.dll和Unity.HotFix.pdb文件

因为这是跨域加载,要读取热更新里面的脚本我们就要在主工程项目加载到这两个文件然后才能通过ILRuntime进行访问。

开始加载

脚本内容

using System.Collections; using System.Collections.Generic; using System.IO; using UnityEditor; using UnityEngine; //unity提供的特性 每次编译后都会执行 编辑器模式下 [InitializeOnLoad] public class BuildHotFixEditor { 
    private const string scriptAssembliesDir = "Library/ScriptAssemblies";//加载路径 private const string codeDir = "Assets/Res/Code/";//生成的dll和pdb文件夹存放的位置 private const string hotfixDll="Unity.HotFix.dll";//加载文件夹名称 private const string hotfixPdb = "Unity.HotFix.pdb";//加载文件夹名称 static BuildHotFixEditor() { 
    //编译后将原有的文件覆盖掉 File.Copy(Path.Combine(scriptAssembliesDir,hotfixDll), Path.Combine(codeDir,hotfixDll+".bytes"),true); File.Copy(Path.Combine(scriptAssembliesDir,hotfixPdb), Path.Combine(codeDir,hotfixPdb+".bytes"),true); Debug.Log("复制hotfix文件成功"); } } 

在这里插入图片描述
这样子我们每次编译脚本后他都会自动重新修改刷新这两个文件夹的内容,保证实时性

HotFixManager脚本

下面的脚本很大我们耐心一点,不要一次性全部看完,我们慢慢来一步一步的讲解
在Model文件夹创建HotFixManager脚本,作为全局变量加载HotFix文件夹中脚本内容

using System; using System.Collections; using System.Collections.Generic; using System.IO; using System.Reflection; using ILRuntime.CLR.Method; using ILRuntime.CLR.TypeSystem; using ILRuntime.Mono.Cecil.Pdb; using ILRuntime.Runtime.Intepreter; using ILRuntime.Runtime.Stack; using Unity.Model; using UnityEditor; using UnityEditor.Compilation; using UnityEngine; using UnityEngine.Events; using AppDomain = ILRuntime.Runtime.Enviorment.AppDomain; public class HotFixManager : MonoBehaviour { 
    public GameObject code;//code预制体,它身上带有Unity.HotFix.dll和Unity.HotFix.pdb文件 private MemoryStream dllStream; private MemoryStream pdbStream; private AppDomain appDomain;//全局变量,建议使用时用单例模式将HotManager做成单例 private string namespaceName = "HotFix";//我们加载热更新脚本的命名空间名字 private string className = "Test";//加载类的名字 private string name = "";//加载文件总名称 // Start is called before the first frame update void Start() { 
    name = namespaceName + "." + className; Load(); 热更新加载函数(); } void Load() { 
    //1.获取带两个bytes文件 CodeReference cr = code.GetComponent<CodeReference>(); byte[] assBytes = cr.hotFixDll.bytes; byte[] pdbBytes = cr.hotFixPdb.bytes; #if ILRuntime //2.在ILRuntime模式下 把这两个文件加载到内存流里面 dllStream = new MemoryStream(assBytes); pdbStream = new MemoryStream(pdbBytes); //3.构造AppDomain对象 通过它的LoadAssembly来进行加载 appDomain = new AppDomain(); appDomain.LoadAssembly(dllStream,pdbStream,new PdbReaderProvider()); 委托适配器(); 跨域继承适配器注册(); CLR重定向方法(); Debug.Log("ILRuntime模式加载成功"); #else Assembly.Load(assBytes,pdbBytes); #endif } void 跨域继承适配器注册() { 
    appDomain.RegisterCrossBindingAdaptor(new UIBaseAdapter()); } void 热更新加载函数() { 
    #region 1.加载静态无返回函数 //第一个参数:命名空间.类名 //第二个参数:方法名 //第三个参数:类的实例(静态函数不用写实例,动态的要添加实例参数 //第四个参数:参数类型 appDomain.Invoke(name, "无参函数", null,null); appDomain.Invoke(name,"有一参数函数", null, "皮学渣"); appDomain.Invoke(name, "有多参函数", null, new string[] { 
   "皮学渣", "学渣皮"}); appDomain.Invoke(name, "多个不同参数类型无返回函数", null, new object[] { 
   "皮学渣", 211}); #endregion #region 2.加载静态有返回函数 object m1 = appDomain.Invoke(name, "无参函数有返回值", null, null); Debug.Log(m1); object m2 = appDomain.Invoke(name, "有一参数函数有返回", null, "皮学渣"); Debug.Log(m2); object m3 = appDomain.Invoke(name, "有多参函数有返回", null, new string[]{ 
   "皮学渣","学渣皮"}); Debug.Log(m3); object m4 = appDomain.Invoke(name, "多个不同参数类型无返回函数有返回", null, new object[]{ 
   "皮学渣",}); Debug.Log(m4); #endregion #region 3.加载动态无返回函数 InstantiateTest(); #endregion #region 4.加载重载函数 //1.第一种和上面记载的方法一样,指定方法名,实例,参数个数,具体实现就不再重写,自己照着上面就可以了 //2.List 
   
     泛型加载参数形式找到对应的函数 
    IType type1 = appDomain.LoadedTypes[name];//这里将指定类的所有类型都加载出来 Debug.Log(type1); IMethod method1 = type1.GetMethod("Log", 0); appDomain.Invoke(method1, null, null);//这个对应的无参Log函数 IType type2 = appDomain.LoadedTypes[name];//这里将指定类的所有类型都加载出来 List<IType> param2 = new List<IType>(); param2.Add(appDomain.GetType(typeof(string)));//这里对应的函数参数什么类型,有几个都这样添加进来 IMethod method2 = type2.GetMethod("Log", param2, null); appDomain.Invoke(method2, null, "皮学渣"); IType type3 = appDomain.LoadedTypes[name];//这里将指定类的所有类型都加载出来 List<IType> param3 = new List<IType>(); param3.Add(appDomain.GetType(typeof(string))); param3.Add(appDomain.GetType(typeof(int))); IMethod method3 = type2.GetMethod("Log", param3, null); appDomain.Invoke(method3, null, new object[]{ 
   "皮学渣",}); //该形式也可和前面的函数类型进行调用,只是那样直接调用的比较方便 #endregion #region 5.加载成员变量 调用变量成员(); #endregion #region 6.加载泛型函数 调用泛型(); #endregion #region 7.加载委托 调用委托(); #endregion #region 跨域继承的调用 调用继承类函数(); #endregion #region CLR重定向 #endregion } ///  /// 实例化类,从而达到调用动态函数 ///  void InstantiateTest() { 
    ILTypeInstance test= appDomain.Instantiate(name, null); appDomain.Invoke(name, "动态无参", test, null); appDomain.Invoke(name, "动态有一参函数", test, "皮学渣"); object x1=appDomain.Invoke(name, "动态无参有返回值", test, null); Debug.Log(x1); object x2=appDomain.Invoke(name, "动态有一参函数有返回值", test, "皮学渣"); Debug.Log(x2) ; } ///  /// 这个方法是调用热更新dll文件中的类的成员 /// 注意这里的调用热更新文件变量必须是属性 /// 采用get_Name获取和set_Name设置赋值 ///  void 调用变量成员() { 
    //通过实例化,我们去访问成员变量,但是对应的成员变量是字段属性,我们还是类似调用的方法一样才能得到变量 //get_ID set_ID都是我们在给变量设置为属性时自动生成的 ILTypeInstance test = appDomain.Instantiate(name); int id1 = (int) appDomain.Invoke(name, "get_ID", test, null); Debug.Log(id1); appDomain.Invoke(name, "set_ID", test, ); int id2 = (int) appDomain.Invoke(name, "get_ID", test, null); Debug.Log(id2); } ///  /// 这个方法是调用热更新Dll文件中类的泛型方法,我们只需要给 /// 该方法声明类型即可使用 采用appDomain.GetType(type(string....)注册 ///  void 调用泛型() { 
    ILTypeInstance test = appDomain.Instantiate(name); appDomain.InvokeGenericMethod(name, "泛型函数", new IType[] { 
   appDomain.GetType(typeof(string))}, test, "皮学渣"); } void 调用委托() { 
    //注意:我们的ILRuntime只支持Action以及Func,delegate委托的使用 //而在unity’中的委托调用是UnityAction,所以我们咋这里用ILRuntime无法直接调用该方法 //所以我们这里就用到了我们的委托适配器,我们要在生成appDomain变量是进行注册委托适配器 ILTypeInstance test = appDomain.Instantiate(name); appDomain.Invoke(name, "ButtonClick", test, null); appDomain.Invoke(name, "调用这些委托", test, null); } void 委托适配器() { 
    //普通委托注册 appDomain.DelegateManager.RegisterMethodDelegate<int>();//这是给Action类型添加委托进行适配,<>里可以写任意个参数类型,要和dll里面的匹配 appDomain.DelegateManager.RegisterFunctionDelegate<int,int>();//这是给Action类型添加委托进行适配,<>里可以写任意个参数类型,要和dll里面的匹配 //这里是给非Action Func 委托类型进行注册 appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction> ( (act) => { 
    return new UnityAction(()=> ((Action) act)() ); } ); } void 调用继承类函数() { 
    string pName = "HotFix.继承unity主程序中的类"; UIBase uibase = appDomain.Instantiate<UIBase>(pName); int id = (int)appDomain.Invoke(pName, "get_MyID", uibase, null); Debug.Log(id); appDomain.Invoke(pName, "HandleEvent", uibase, 50); appDomain.Invoke(pName, "Open", uibase, "皮学渣"); } unsafe void CLR重定向方法() { 
    MethodInfo method= typeof(Debug).GetMethod("Log",new System.Type[] { 
    typeof(object) }); appDomain.RegisterCLRMethodRedirection(method, DLog); } public unsafe static StackObject* DLog(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj) { 
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; StackObject* ptr_of_this_method; //只有一个参数,所以返回指针就是当前栈指针ESP - 1 StackObject* __ret = ILIntepreter.Minus(__esp, 1); //第一个参数为ESP -1, 第二个参数为ESP - 2,以此类推 ptr_of_this_method = ILIntepreter.Minus(__esp, 1); //获取参数message的值 object message = StackObject.ToObject(ptr_of_this_method, __domain, __mStack); //需要清理堆栈 __intp.Free(ptr_of_this_method); //如果参数类型是基础类型,例如int,可以直接通过int param = ptr_of_this_method->Value获取值, //关于具体原理和其他基础类型如何获取,请参考ILRuntime实现原理的文档。 //通过ILRuntime的Debug接口获取调用热更DLL的堆栈 string stackTrace = __domain.DebugService.GetStackTrace(__intp); Debug.Log(string.Format("{0}\n----------------\n{1}", message, stackTrace)); return __ret; } } 

Load函数

void Load() { 
    //1.获取带两个bytes文件 CodeReference cr = code.GetComponent<CodeReference>(); byte[] assBytes = cr.hotFixDll.bytes; byte[] pdbBytes = cr.hotFixPdb.bytes; #if ILRuntime //2.在ILRuntime模式下 把这两个文件加载到内存流里面 dllStream = new MemoryStream(assBytes); pdbStream = new MemoryStream(pdbBytes); //3.构造AppDomain对象 通过它的LoadAssembly来进行加载 appDomain = new AppDomain(); appDomain.LoadAssembly(dllStream,pdbStream,new PdbReaderProvider()); 委托适配器(); 跨域继承适配器注册(); CLR重定向方法(); Debug.Log("ILRuntime模式加载成功"); #else Assembly.Load(assBytes,pdbBytes); #endif } 

HotFix文件夹Test脚本

在这里插入图片描述

脚本内容

using System; using System.Collections; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; namespace HotFix//这里就是我们要给访问的命名空间 { 
    public class Test { 
    #region 函数 public Test() { 
    Debug.Log("我是无参构造函数"); } public Test(string text) { 
    Debug.Log($"我是有参构造函数string text: { 
     text}"); } public static void 无参函数() { 
    Debug.Log("我是静态无参函数无返回函数"); } public static void 有一参数函数(string text) { 
    Debug.Log("我是静态有一个string参数函数无返回函数: "+text); } public static void 有多参函数(string text1, string text2) { 
    Debug.Log($"我是静态有多个参数无返回函数" + $"string text1: { 
     text1} string text2: { 
     text2}"); } public static void 多个不同参数类型无返回函数(string text, int m) { 
    Debug.Log($"我是静态有多个不同参数类型无返回函数 " + $" string text: { 
     text} int m: { 
     m}"); } public static void Log() { 
    Debug.Log("无参无返回Log函数"); } public static void Log(string text) { 
    Debug.Log($"有一个string参数无返回Log函数 text: { 
     text}"); } public static void Log(string text, int m) { 
    Debug.Log($"有一个string参数一个int参乎上无返回Log函数 text: { 
     text} m: { 
     m}"); } public static int 无参函数有返回值() { 
    Debug.Log("我是静态无参函数有返回函数"); return 1; } public static int 有一参数函数有返回(string text) { 
    Debug.Log("我是静态有一个string参数函数有返回函数: "+text); return 2; } public static int 有多参函数有返回(string text1, string text2) { 
    Debug.Log($"我是静态有多个参数有返回函数" + $"string text1: { 
     text1} string text2: { 
     text2}"); return 3; } public static int 多个不同参数类型无返回函数有返回(string text, int m) { 
    Debug.Log($"我是静态有多个不同参数类型有返回函数 " + $" string text: { 
     text} int m: { 
     m}"); return 4; } public void 动态无参() { 
    Debug.Log("我是动态无参无返回函数"); } public void 动态有一参函数(string text) { 
    Debug.Log("我是动态有一参数函数 string text: "+text); } public int 动态无参有返回值() { 
    Debug.Log("我是动态无参有返回函数"); return 1; } public int 动态有一参函数有返回值(string text) { 
    Debug.Log("我是动态有一参数有返回值函数 string text: "+text); return 2; } #endregion #region 字段变量 private int id=5000; public int ID { 
    get { 
    return id; } set { 
    id = value; } } #endregion #region 泛型函数 public void 泛型函数<T>(T t) { 
    Debug.Log($"我是泛型函数参数是:{ 
     t}"); } #endregion #region UnityAction委托调用 public void ButtonClick() { 
    Button button = GameObject.Find("Canvas/Test").GetComponent<Button>(); button.onClick.AddListener(OnClike);//这里的注册添加的是UnityAction委托类型 } private void OnClike() { 
    Debug.Log("点击了Test按钮"); } #endregion #region 其他委托delegate Func Action public delegate void Delegate委托(); public Action<int> action委托; public Func<int, int> func委托; public void 注册delegate委托() { 
    Debug.Log("使用了delegate委托"); } public void 注册Action委托(int n) { 
    Debug.Log($"使用了Action委托,参数数n: { 
     n}"); } public int 注册Func委托(int m) { 
    Debug.Log($"使用了func委托,参数数m:{ 
     m}"); return m; } public void 调用这些委托() { 
    //首先给这些委托进行注册 Delegate委托 delegate委托 = 注册delegate委托; action委托 = 注册Action委托; func委托 = 注册Func委托; //调用这些委托 delegate委托(); action委托(985); int m = func委托(); Debug.Log(m); } #endregion } } 

现在开始使用

跨域访问函数

 #region 1.加载静态无返回函数 //第一个参数:命名空间.类名 //第二个参数:方法名 //第三个参数:类的实例(静态函数不用写实例,动态的要添加实例参数 //第四个参数:参数类型 appDomain.Invoke(name, "无参函数", null,null); appDomain.Invoke(name,"有一参数函数", null, "皮学渣"); appDomain.Invoke(name, "有多参函数", null, new string[] { 
   "皮学渣", "学渣皮"}); appDomain.Invoke(name, "多个不同参数类型无返回函数", null, new object[] { 
   "皮学渣", 211});//不同参数类型用object数组 #endregion #region 2.加载静态有返回函数 //返回值我们用object接受,当然我们可以将返回值进行转化成我们想要的类型 // int m1 = (int)appDomain.Invoke(name, "无参函数有返回值", null, null);这样也可以 object m1 = appDomain.Invoke(name, "无参函数有返回值", null, null); Debug.Log(m1); object m2 = appDomain.Invoke(name, "有一参数函数有返回", null, "皮学渣"); Debug.Log(m2); object m3 = appDomain.Invoke(name, "有多参函数有返回", null, new string[]{ 
   "皮学渣","学渣皮"}); Debug.Log(m3); object m4 = appDomain.Invoke(name, "多个不同参数类型无返回函数有返回", null, new object[]{ 
   "皮学渣",}); Debug.Log(m4); #endregion #region 3.加载动态无返回函数 InstantiateTest(); #endregion #region 4.加载重载函数 //1.第一种和上面记载的方法一样,指定方法名,实例,参数个数,具体实现就不再重写,自己照着上面就可以了 //2.List 
   
     泛型加载参数形式找到对应的函数 
    IType type1 = appDomain.LoadedTypes[name];//这里将指定类的所有类型都加载出来 Debug.Log(type1); IMethod method1 = type1.GetMethod("Log", 0); appDomain.Invoke(method1, null, null);//这个对应的无参Log函数 IType type2 = appDomain.LoadedTypes[name];//这里将指定类的所有类型都加载出来 List<IType> param2 = new List<IType>(); param2.Add(appDomain.GetType(typeof(string)));//这里对应的函数参数什么类型,有几个都这样添加进来 IMethod method2 = type2.GetMethod("Log", param2, null); appDomain.Invoke(method2, null, "皮学渣"); IType type3 = appDomain.LoadedTypes[name];//这里将指定类的所有类型都加载出来 List<IType> param3 = new List<IType>(); param3.Add(appDomain.GetType(typeof(string))); param3.Add(appDomain.GetType(typeof(int))); IMethod method3 = type2.GetMethod("Log", param3, null); appDomain.Invoke(method3, null, new object[]{ 
   "皮学渣",}); //该形式也可和前面的函数类型进行调用,只是那样直接调用的比较方便 #endregion 
 ///  /// 实例化类,从而达到调用动态函数,动态函数字段都要有实例才能进行访问 ///  void InstantiateTest() { 
    ILTypeInstance test= appDomain.Instantiate(name, null);//生成实例,第一个参数名字是实例的类名,第二个是参数null代表是用无参构造函数进行实例化,有参数就是有参构造创建 appDomain.Invoke(name, "动态无参", test, null); appDomain.Invoke(name, "动态有一参函数", test, "皮学渣"); object x1=appDomain.Invoke(name, "动态无参有返回值", test, null); Debug.Log(x1); object x2=appDomain.Invoke(name, "动态有一参函数有返回值", test, "皮学渣"); Debug.Log(x2) ; } 

跨域加载成员变量

HotFix.Test脚本中

#region 字段变量 private int id=5000; public int ID//我们要给他一个属性才能进行访问 { 
    get { 
    return id; } set { 
    id = value; } } #endregion 

Modle.HotFixManager脚本
热更新加载函数():

#region 5.加载成员变量 调用变量成员(); #endregion 
 ///  /// 这个方法是调用热更新dll文件中的类的成员 /// 注意这里的调用热更新文件变量必须是属性 /// 采用get_Name获取和set_Name设置赋值 ///  void 调用变量成员() { 
    //通过实例化,我们去访问成员变量,但是对应的成员变量是字段属性,我们还是类似调用的方法一样才能得到变量 //get_ID set_ID都是我们在给变量设置为属性时自动生成的 ILTypeInstance test = appDomain.Instantiate(name); int id1 = (int) appDomain.Invoke(name, "get_ID", test, null); Debug.Log(id1); appDomain.Invoke(name, "set_ID", test, ); int id2 = (int) appDomain.Invoke(name, "get_ID", test, null); Debug.Log(id2); } 

跨域委托

HotFix.Test

 #region UnityAction委托调用 public void ButtonClick() { 
    Button button = GameObject.Find("Canvas/Test").GetComponent<Button>(); button.onClick.AddListener(OnClick);//这里的注册添加的是UnityAction委托类型 } private void OnClick() { 
    Debug.Log("点击了Test按钮"); } #endregion #region 其他委托delegate Func Action public delegate void Delegate委托(); public Action<int> action委托; public Func<int, int> func委托; public void 注册delegate委托() { 
    Debug.Log("使用了delegate委托"); } public void 注册Action委托(int n) { 
    Debug.Log($"使用了Action委托,参数数n: { 
     n}"); } public int 注册Func委托(int m) { 
    Debug.Log($"使用了func委托,参数数m:{ 
     m}"); return m; } public void 调用这些委托() { 
    //首先给这些委托进行注册 Delegate委托 delegate委托 = 注册delegate委托; action委托 = 注册Action委托; func委托 = 注册Func委托; //调用这些委托 delegate委托(); action委托(985); int m = func委托(); Debug.Log(m); } #endregion 

HotFixManager

 void 委托适配器() { 
    //普通委托注册 appDomain.DelegateManager.RegisterMethodDelegate<int>();//这是给Action类型添加委托进行适配,<>里可以写任意个参数类型,要和dll里面的匹配 appDomain.DelegateManager.RegisterFunctionDelegate<int,int>();//这是给Action类型添加委托进行适配,<>里可以写任意个参数类型,要和dll里面的匹配 //这里是给非Action Func 委托类型进行注册 appDomain.DelegateManager.RegisterDelegateConvertor<UnityAction> ( (act) => { 
    return new UnityAction(()=> ((Action) act)() ); } ); } 

我们进行注册后,在load函数初始化的后面进行调用

void 调用委托() { 
    //注意:我们的ILRuntime只支持Action以及Func,delegate委托的使用 //而在unity’中的委托调用是UnityAction,所以我们咋这里用ILRuntime无法直接调用该方法 //所以我们这里就用到了我们的委托适配器,我们要在生成appDomain变量是进行注册委托适配器 ILTypeInstance test = appDomain.Instantiate(name); appDomain.Invoke(name, "ButtonClick", test, null); appDomain.Invoke(name, "调用这些委托", test, null); } 

这个函数就是调用我们在Test脚本中的委托

跨域继承适配

当我们在热更新中的脚本要继承主工程中的类时,我们要对被继承的类进行适配,这样我们调用hotfix中派生类才能成功。

我们在Model文件夹创建一个抽象基类UIBase

UIase

using System.Collections; using System.Collections.Generic; using UnityEngine; namespace Unity.Model { 
    public abstract class UIBase { 
    public virtual int MyID { 
    get { 
    return 100; } } public virtual void Open(string text) { 
    Debug.Log("UIBase中的Open方法"); } public abstract void HandleEvent(int id); } } 

接下来我们要根据官方文档来进行适配器的编写

using ILRuntime.CLR.Method; using ILRuntime.Runtime.Enviorment; using ILRuntime.Runtime.Intepreter; using System; using System.Collections; using System.Collections.Generic; using Unity.Model; using UnityEngine; public class UIBaseAdapter : CrossBindingAdaptor//这是一个接口,我们要实现里面的方法即可 { 
    public override Type BaseCLRType { 
    get { 
    return typeof(UIBase);//这里的参数填写基类名字(哪个类被热更新问价继承就填哪个) } } public override Type[] BaseCLRTypes => base.BaseCLRTypes; public override Type AdaptorType { 
    get { 
    return typeof(Adapter); } } public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { 
    return new Adapter(appdomain, instance); } class Adapter : UIBase, CrossBindingAdaptorType//继承基类,在继承这个接口,重写基类的方法,和实现接口方法,一定要有一个无参构造函数,下面的两个参数一个AppDomain和ILTyoeInstance的构造函数 { 
    private ILRuntime.Runtime.Enviorment.AppDomain appdomain; private ILTypeInstance instance; public Adapter() { 
    } public Adapter(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { 
    this.appdomain = appdomain; this.instance = instance; } public ILTypeInstance ILInstance { 
    get { 
    return instance; } } //到这里我们都是按照上面的进行 //下面开始进行适配方法和字段 bool mGetMyIDGot = false;//定义一个标识未 标识是否已经缓存了热更里的方法get_xxx IMethod mGetMyID;//缓存获取到的方法 bool isGetMyIDInvoking = false;//判断是否运行该方法 public override int MyID { 
    get { 
    if(mGetMyIDGot==false) { 
    //字段获取的话就是根据属性一样获取get__xxx方法 mGetMyID = instance.Type.GetMethod("get_MyID", 0); mGetMyIDGot = true; } if(mGetMyID!=null&& isGetMyIDInvoking==false) { 
    isGetMyIDInvoking = true; int m=(int)appdomain.Invoke(mGetMyID, instance, null); isGetMyIDInvoking=false; return m; } return base.MyID; } } IMethod mHandleEvent; bool isHandleEventCalled = false; object[] parame1=new object[1];//参数列表 public override void HandleEvent(int id) { 
    if(mHandleEvent==null) { 
    mHandleEvent = instance.Type.GetMethod("HandleEvent", 1); } if(mHandleEvent!=null) { 
    parame1[0] = id; appdomain.Invoke(mHandleEvent, instance, parame1); } } bool mOpenGot=false; IMethod mOpen; bool isOpenCalled = false; object[] paream2=new object[1]; public override void Open(string text) { 
    if(mOpenGot==false) { 
    mOpen = instance.Type.GetMethod("Open", 1); } if(mOpen!=null&&isOpenCalled==false) { 
    isOpenCalled = true; paream2[0]=text; appdomain.Invoke(mOpen, instance, paream2); isOpenCalled=false; } else { 
    base.Open(text); } } } } 

HotFixManager 脚本

void 跨域继承适配器注册() { 
    appDomain.RegisterCrossBindingAdaptor(new UIBaseAdapter()); } 

HotFix文件夹创建 继承unity主程序中的类脚本

using System.Collections; using System.Collections.Generic; using Unity.Model; using UnityEngine; namespace HotFix { 
    public class 继承unity主程序中的类 : UIBase { 
    public override int MyID => ; public override void HandleEvent(int id) { 
    Debug.Log("现在是调用了重写的HandleEvent方法参数t是:" + id); } public override void Open(string text) { 
    Debug.Log($"现在是调用了重写的Open方法参数text:{ 
     text}"); } } } 

HotFixManager

 void 调用继承类函数() { 
    string pName = "HotFix.继承unity主程序中的类";//命名空间.类名 UIBase uibase = appDomain.Instantiate<UIBase>(pName); int id = (int)appDomain.Invoke(pName, "get_MyID", uibase, null); Debug.Log(id); appDomain.Invoke(pName, "HandleEvent", uibase, 50); appDomain.Invoke(pName, "Open", uibase, "皮学渣"); } 

CLR重定向

HotFixManager

 unsafe void CLR重定向方法() { 
    // 类名 方法名 参数列表 MethodInfo method= typeof(Debug).GetMethod("Log",new System.Type[] { 
    typeof(object) }); appDomain.RegisterCLRMethodRedirection(method, DLog); } public unsafe static StackObject* DLog(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj) { 
    ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; StackObject* ptr_of_this_method; //只有一个参数,所以返回指针就是当前栈指针ESP - 1 StackObject* __ret = ILIntepreter.Minus(__esp, 1); //第一个参数为ESP -1, 第二个参数为ESP - 2,以此类推 ptr_of_this_method = ILIntepreter.Minus(__esp, 1); //获取参数message的值 object message = StackObject.ToObject(ptr_of_this_method, __domain, __mStack); //需要清理堆栈 __intp.Free(ptr_of_this_method); //如果参数类型是基础类型,例如int,可以直接通过int param = ptr_of_this_method->Value获取值, //关于具体原理和其他基础类型如何获取,请参考ILRuntime实现原理的文档。 //通过ILRuntime的Debug接口获取调用热更DLL的堆栈 string stackTrace = __domain.DebugService.GetStackTrace(__intp); Debug.Log(string.Format("{0}\n----------------\n{1}", message, stackTrace)); return __ret; } 

然后在Load函数是下面添加该函数

运行各种的结果

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
点击Button按钮
在这里插入图片描述








大总结

希望我这篇文章能够让你快速入门

版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请联系我们举报,一经查实,本站将立刻删除。

发布者:全栈程序员-站长,转载请注明出处:https://javaforall.net/218266.html原文链接:https://javaforall.net

(0)
上一篇 2026年3月18日 上午7:40
下一篇 2026年3月18日 上午7:40


相关推荐

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

关注全栈程序员社区公众号