ILRuntime: https://github.com/Ourpalm/ILRuntime
Demo: https://github.com/Ourpalm/ILRuntimeU3D
中文在线文档: https://ourpalm.github.io/ILRuntime/public/v1/guide/index.html
Mono: https://www.mono-project.com/
主工程: void OnHotFixLoaded() { appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunText", null, null); } 热更工程: namespace HotFix_Project { public class InstanceClass { public InstanceClass() { UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass()"); } } }
主工程 void OnHotFixLoaded() { Debug.Log("调用无参数静态方法"); // 调用无参数静态方法, appdomain.Invoke("类名“, "方法名", 对象引用, 参数列表); appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null); Debug.Log("调用带参数的静态方法"); // 调用带参数的静态方法 appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123); Debug.Log("通过IMethod调用方法"); // 预先获得IMethod, 可以减低每次调用查找方法耗用的时间 IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"]; // 根据方法名称和参数个数获取方法 IMethod method = type.GetMethod("StaticFunTes", 0); appdomain.Invoke(method, null, null); Debug.Log("指定参数类型来获得IMethod"); IType intType = appdomain.GetType(typeof(int)); // 参数类型列表 ListparamList = new List (); paramList.Add(intType); // 根据方法名称和参数类型列表获取方法 method = type.GetMethod("StaticFunTest2", paramList, null); appdomain.Invoke(method, null, 456); Debug.Log("实例化热更里的类"); object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 } ); // 第二种方式 object obj2 = ((ILType)type).Instantiate(); Debug.Log("调用成员方法"); int id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj, null); Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id); id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj2, null); Debug.Log("!! HotFix_Project.InstanceClass.ID = " + id); Debug.Log("调用泛型方法"); IType stringType = appdomain.GetType(typeof(string)); IType[] genericArguments = new IType[] { stringType }; appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod", genericArguments, null, "TestString"); Debug.Log("获取泛型方法的IMethod"); paramList.Clear(); paramList.Add(intType); genericArguments = new IType[] { intType }; method = type.GetMethod("GenericMethod", paramList, genericArguments); appdomain.Invoke(method, null, 33333); } 热更新工程 namespace HotFix_Project { public class InstanceClass { private int id; public InstanceClass() { UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass()"); this.id = 0; } public InstanceClass(int id) { UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass() id = " + id); this.id = id; } public int ID { get { return id; } } public static void StaticFunTest() { UnityEngine.Debug.Log("!!! InstanceClass.StaticFunTest()"); } public static void StaticFunTest2(int a) { UnityEngine.Debug.Log("!!! InstanceClass.StaticFunTest2(), a=" + a); } public static void GenericMethod (T a) { UnityEngine.Debug.Log("!!! InstanceClass.GenericMethod(), a=" + a); } } }
主工程 public delegate void TestDelegateMethod(int a); public delegate string TestDelegateFunction(int a); public class DelegateDemo: MonoBehaviour { public static TestDelegateMethod TestMethodDelegate; public static TestDelegateFunction TestFunctionDelegate; public static System.Action<string> TestActionDelegate; // 这个方法仅仅是为了演示,强制删除缓存的委托适配器,实际项目不要这么调用 void ClearDelegateCache() { var type = appdomain.LoadedTypes["HotFix_Project.TestDelegate"]; ILMethod m = type.GetMethod("Method", 1) as ILMethod; m.DelegateAdapter = null; m = type.GetMethod("Function", 1) as ILMethod; m.DelegateAdapter = null; m = type.GetMethod("Action", 1) as ILMethod; m.DelegateAdapter = null; } void OnHotFixLoaded() { Debug.Log("完全在热更DLL内部使用的委托,直接可用,不需要做任何处理"); appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize", null, null); appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest", null, null); Debug.Log("如果需要跨域调用委托(将热更DLL里面的委托实例传到Unity主工程用), 就需要注册适配器,不然就会像下面这样"); try { appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null); } catch (System.Exception ex) { Debug.LogError(ex.ToString()); } // 为了演示,清除适配器缓存,实际使用中不要这么做 ClearDelegateCache(); Debug.Log("这是因为iOS的IL2CPP模式下,不能动态生成类型,为了避免出现不可预知的问题,我们没有通过反射的方式创建委托实例,因此需要手动进行一些注册"); Debug.Log("首先需要注册委托适配器,刚刚的报错的错误提示中,有提示需要的注册代码"); // 下面这些注册代码,正式使用的时候,应该写在InitializeILRuntime中 // TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可 appdomain.DelegateManager.RegisterMethodDelegate<int>(); // 带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个 appdomain.DelegateManager.RegisterFunctionDelegate<int, string>(); // Action的参数为一个string appdomain.DelegateManager.RegisterMethodDelegate<string>(); Debug.Log("注册完毕后再次运行会发现这次会报另外的错误"); try { appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null); } catch (System.Exception ex) { Debug.LogError(ex.ToString()); } Debug.Log("ILRuntime内部是用Action和Func这两个系统内置的委托类型来创建实例的,所以其他的委托类型都需要写转换器"); Debug.Log("将Action或者Func转换成目标委托类型"); appdomain.DelegateManager.RegisterDelegateConvertor((action) => { // 转换器的目的是把Action或者Func转换成正确的类型,这里则是把Action 转换成TestDelegateMethod return new TestDelegateMethod((a) => { // 调用委托实例 ((System.Action<int>)action)(a); }); }); // 对于TestDelegateFunction同理,只是是将Func转换成TestDelegateFunction appdomain.DelegateManager.RegisterDelegateConvertor((action) = > return new TestDelegateFunction((a) => { return ((System.Func<int, string>)action)(a); }); }); Debug.Log("现在我们再来运行一次"); appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null); appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest2", null, null); Debug.Log("运行成功,我们可以看见,用Action或者Func当作委托类型的话,可以避免写转换器,所以项目中在不必要的情况下尽量只用Action和Func"); Debug.Log("另外应该尽量减少不必要的跨域委托调用,如果委托只在热更DLL中用,是不需要进行任何注册的"); Debug.Log("---------"); Debug.Log("我们再来在Unity主工程中调用一下刚刚的委托试试"); TestMethodDelegate(789); var str = TestFunctionDelegate(098); Debug.Log("!! OnHotFixLoaded str = " + str); TestActionDelegate("Hello From Unity Main Project"); } } 热更新工程 namespace HotFix_Project { public class TestDelegate { // TestDelegateMethod, TestDelegateFunction 位于主工程 static TestDelegateMethod delegateMethod; static TestDelegateFunction delegateFunc; static Action<string> delegateAction; public static void Initialize() { delegateMethod = Method; delegateFunc = Function; delegateAction = Action; } public static void RunTest() { delegateMethod(123); var res = delegateFunc(456); UnityEngine.Debug.Log("!! TestDelegate.RunTest res = " + res); delegateAction("rrr"); } public static void Initialize2() { // DelegateDemo 位于主工程 DelegateDemo.TestMethodDelegate = Method; DelegateDemo.TestFunctionDelegate = Function; DelegateDemo.TestActionDelegate = Action; } public static void RunTest2() { DelegateDemo.TestMethodDelegate(123); var res = DelegateDemo.TestFunctionDelegate(456); UnityEngine.Debug.Log("!! TestDelegate.RunTest2 res = " + res); DelegateDemo.TestActionDelegate("rrr"); } static void Method(int a) { UnityEngine.Debug.Log("!! TestDelegate.Method, a = " + a); } static string Function(int a) { return a.ToString(); } static void Action(string a) { UnityEngine.Debug.Log("!! TestDelegate.Action, a = " + a); } } }
主工程 using System; using ILRuntime.CLR.Method; using ILRuntime.Runtime.Enviorment; using ILRuntime.Runtime.Intepreter; public class InheritanceAdapter: CrossBindingAdaptor { public override Type BaseCLR Type { get { return typeof(TestClassBase); // 这是你想继承的那个类 } } public override Type AdaptorType { get { return typeof(Adaptor); // 这是实际的适配器类 } } public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { return new Adaptor(appdomain, instance); // 创建一个新得实例 } // 实际的适配器类需要继承你想继承的那个类 class Adaptor: TestClassBase, CrossBindingAdaptorType { ILTypeInstance instance; ILRuntime.Runtime.Enviorment.AppDomain appdomain; IMethod mTestAbstract; bool mTestAbstractGot; IMethod mTestVirtual; bool mTestVirtualGot; bool isTestVirtualInvoking = false; IMethod mGetValue; bool mGetValueGot; bool isGetValueInvoking = false; // 缓存这个数组来避免调用时的GC Alloc object[] param1 = new object[1]; public Adaptor() { } public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { this.appdomain = appdomain; this.instance = instance; } public ILTypeInstance ILInstance { get { return instance; } } // 你需要重写所有你希望在热更脚本里面重写的方法,并且将控制权转到脚本里去 public override void TestAbstract(int ab) { if (!mTestAbstractGot) { mTestAbstract = instance.Type.GetMethod("TestAbstract", 1); mTestAbstractGot = true; } if (mTestAbstract != null) { param1[0] = ab; // 没有参数建议显式传递null为参数列表,否则会自动new object[0]导致GC Alloc appdomain.Invoke(mTestAbstract, instance, param1); } } public override void TestVirtual(string str) { if (!mTestVirtualGot) { mTestVirtual = instance.Type.GetMethod("TestVirtual", 1); mTestVirtualGot = true; } // 对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.TestVirtual()就会造成无限循环,最终导致爆栈 if (mTestVirtual != null && !isTestVirtualInvoking) { isTestVirtualInvoking = true; param1[0] = str; appdomain.Invoke(mTestVirtual, instance, param1); isTestVirtualInvoking = false; } else { base.TestVirtual(str); } } public override int Value { get { if (!mGetValueGot) { // 属性的Getter编译以后会以get_XXX存在,如果不确定的话可以打开Reflector等反编译软件看一下函数名称 mGetValue = instance.Type.GetMethod"get_Value", 1); mGetValueGot = true; } // 对于虚函数而言,必须设定一个标识位来确定是否当前已经在调用中,否则如果脚本类中调用base.Value就会造成无限循环,最终导致爆栈 if (mGetValue != null && !isGetValueInvoking) { isGetValueInvoking = true; var res = (int)appdomain.Invoke(mGetValue, instance, null); isGetValueInvoking = false; return res; } else { return base.Value; } } } public override string ToString() { IMethod m = appdomain.ObjectType.GetMethod("ToString", 0); m = instance.Type.GetVirtualMethod(m); if (m == null || m is ILMethod) { return instancde.ToString(); } else { return instance.Type.FullName; } } } } public abstract class TestClassBase { public virtual int Value { get { return 0; } } public virtual void TestVirtual(string str) { Debug.Log("!! TestClassBase.TestVirtual, str = " + str); } public abstract void TestAbstract(int gg); } public class Inheritance: MonoBehaviour { void OnHotFixLoaded() { Debug.Log("首先我们来创建热更里的类实例"); TestClassBase obj; try { obj = appdomain.Instantiate("HotFix_Project.TestInheritance"); } catch (System.Exception ex) { Debug.LogError(ex.ToString()); } Debug.Log("Oops, 报错了, 因为跨域继承必须要注册适配器.如果是热更DLL里面继承热更里面的类型,不需要任何注册."); Debug.Log("所以现在我们来注册适配器"); appdomain.RegisterCrossBindingAdaptor(new InheritanceAdapter()); Debug.Log("现在再来尝试创建一个实例"); obj = appdomain.Instantiate ("HotFix_Project.TestInheritance"); Debug.Log("现在来调用成员方法"); obj.TestAbstract(123); obj.TestVirtual("Hello"); Debug.Log("现在换个方式创建实例"); obj = appdomain.Invoke("HotFix_Project.TestInheritance", "NewObject", null, null) as TestClassBase; obj.TestAbstract(456); obj.TestVirtual("Foorbar"); } } 热更新工程 namespace HotFix_Project { // 一定要特别注意: 后面只允许有1个Unity主工程的类或者接口,但是可以有随便多少个热更DLL中的接口 public class TestInheritance: TestClassBase { UnityEngine.Debug.Log("!! TestInheritance.TestAbstract gg = " + gg); } public override void TestVirtual(string str) { base.TestVirtual(str); UnityEngine.Debug.Log("!! TestInheritance.TestVirtual str =" + str); } public static TestInheritance NewObject() { return new HotFix_Project.TestInheritance(); } }
主工程 unsafe void OnHotFixLoaded() { Debug.Log("什么时候需要CLR重定向呢,当我们需要挟持原方法实现,添加一些热更DLL中的特殊处理的时候,就需要CLR重定向了"); Debug.Log("详细文档请参见Github主页的相关文档"); Debug.Log("CLR重定向对ILRuntime底层实现密切相关,因此要完全理解这个Demo, 需要大家先看关于ILRuntime实现原理的Demo"); Debug.Log("下面介绍一个CLR重定向的典型用法,比如我们在DLL里调用Debug.Log,默认情况下是无法显示DLL内堆栈的,像下面这样"); Debug.Log("但是经过CLR重定向之后可以做到输出DLL内堆栈,接下来进行CLR重定向注册"); var mi = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) }); // appdomain.RegisterCLRMethodRedirection(mi, Log_11); // 这个只是为了演示加的,平时不要这么用,直接在InitializeILRuntime方法里面写CLR重定向注册就行了 Debug.Log("我们再来调用一次刚刚的方法,注意看下一行日志的变化"); appdomain.Invoke("HotFix_Project.TestCLRRedirection", "RunTest", null, null); } //编写重定向方法对于刚接触ILRuntime的朋友可能比较困难,比较简单的方式是通过CLR绑定生成绑定代码,然后在这个基础上改,比如下面这个代码是从UnityEngine_Debug_Binding里面复制来改的 //如何使用CLR绑定请看相关教程和文档 unsafe static StackObject* Log_11(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj) { //ILRuntime的调用约定为被调用者清理堆栈,因此执行这个函数后需要将参数从堆栈清理干净,并把返回值放在栈顶,具体请看ILRuntime实现原理文档 ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; StackObject* ptr_of_this_method; //这个是最后方法返回后esp栈指针的值,应该返回清理完参数并指向返回值,这里是只需要返回清理完参数的值即可 StackObject* __ret = ILIntepreter.Minus(__esp, 1); //取Log方法的参数,如果有两个参数的话,第一个参数是esp - 2,第二个参数是esp -1, 因为Mono的bug,直接-2值会错误,所以要调用ILIntepreter.Minus ptr_of_this_method = ILIntepreter.Minus(__esp, 1); //这里是将栈指针上的值转换成object,如果是基础类型可直接通过ptr->Value和ptr->ValueLow访问到值,具体请看ILRuntime实现原理文档 object message = typeof(object).CheckCLRTypes(StackObject.ToObject(ptr_of_this_method, __domain, __mStack)); //所有非基础类型都得调用Free来释放托管堆栈 __intp.Free(ptr_of_this_method); //在真实调用Debug.Log前,我们先获取DLL内的堆栈 var stacktrace = __domain.DebugService.GetStackTrace(__intp); //我们在输出信息后面加上DLL堆栈 UnityEngine.Debug.Log(message + "\n" + stacktrace); return __ret; } 看看这行的详细Log信息 System.Reflection.MethodBase:Invoke(Object, Object[]) ILRuntime.CLR.Method.CLRMethod:Invoke(ILIntepreter, StackObject*, IList`1, Boolean) (at Assets/ILRuntime/CLR/Method/CLRMethod.cs:316) ILRuntime.Runtime.Intepreter.ILIntepreter:Execute(ILMethod, StackObject*, Boolean&) (at Assets/ILRuntime/Runtime/Intepreter/ILIntepreter.cs:1797) ILRuntime.Runtime.Intepreter.ILIntepreter:Run(ILMethod, Object, Object[]) (at Assets/ILRuntime/Runtime/Intepreter/ILIntepreter.cs:96) ILRuntime.Runtime.Enviorment.AppDomain:Invoke(IMethod, Object, Object[]) (at Assets/ILRuntime/Runtime/Enviorment/AppDomain.cs:1086) ILRuntime.Runtime.Enviorment.AppDomain:Invoke(String, String, Object, Object[]) (at Assets/ILRuntime/Runtime/Enviorment/AppDomain.cs:989) CLRRedirectionDemo:OnHotFixLoaded() (at Assets/Scripts/Examples/05_CLRRedirection/CLRRedirectionDemo.cs:85)d__4:MoveNext() (at Assets/Scripts/Examples/05_CLRRedirection/CLRRedirectionDemo.cs:63) UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr) 热更新工程 namespace HotFix_Project { public class TestCLRRedirection { public static void RunTest() { UnityEngine.Debug.Log("看看这行的详细Log信息"); } } }
主工程 public class CLRBindingTestClass { public static float DoSomeTest(int a, float b) { return a + b; } } public class CLRBindingDemo: MonoBehaviour { unsafe void OnHotFixLoaded() { ilruntimeReady = true; } bool ilruntimeReady = false; bool executed = false; void Update() { if (ilruntimeReady && !executed && Time.realtimeSinceStartup > 3) { executed = true; //这里为了方便看Profiler,代码挪到Update中了 System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch(); Debug.LogWarning("运行这个Demo前请先点击菜单ILRuntime->Generate来生成所需的绑定代码,并按照提示解除下面相关代码的注释"); Debug.Log("默认情况下,从热更DLL里调用Unity主工程的方法,是通过反射的方式调用的,这个过程中会产生GC Alloc,并且执行效率会偏低"); Debug.Log("接下来进行CLR绑定注册,在进行注册前,需要先在ILRuntimeCodeGenerator的绑定列表里面,添加上CLRBindingTestClass这个测试类型"); Debug.Log("CLR绑定会生成较多C#代码,最终会增大包体和Native Code的内存耗用,所以只添加常用类型和频繁调用的接口即可"); Debug.Log("接下来需要点击Unity菜单里面的ILRuntime->Generate CLR Binding Code来生成绑定代码"); Debug.Log("ILRuntime->Generate CLR Binding Code by Analysis是ILRT1.2版新加入的功能,可以根据热更DLL自动生成绑定代码"); //由于CLR重定向只能重定向一次,并且CLR绑定就是利用的CLR重定向,所以请在初始化最后阶段再执行下面的代码,以保证CLR重定向生效 //请在生成了绑定代码后注释下面这行 //throw new System.Exception("请在生成了绑定代码后再运行这个示例");// //请在生成了绑定代码后解除下面这行的注释 //请在生成了绑定代码后解除下面这行的注释 //请在生成了绑定代码后解除下面这行的注释 //ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain); //这个只是为了演示加的,平时不需要这么用,直接在InitializeILRuntime方法里面写CLR绑定注册就行了 var type = appdomain.LoadedTypes["HotFix_Project.TestCLRBinding"]; var m = type.GetMethod("RunTest", 0); Debug.Log("现在我们再来试试绑定后的效果"); sw.Reset(); sw.Start(); Profiler.BeginSample("RunTest2"); appdomain.Invoke(m, null, null); Profiler.EndSample(); sw.Stop(); Debug.LogFormat("刚刚的方法执行了:{0} ms", sw.ElapsedMilliseconds); Debug.Log("可以看到运行时间和GC Alloc有大量的差别,RunTest2之所以有20字节的GC Alloc是因为Editor模式ILRuntime会有调试支持,正式发布(关闭Development Build)时这20字节也会随之消失"); } } void RunTest() { appdomain.Invoke("HotFix_Project.TestCLRBinding", "RunTest", null, null); } void RunTest2(IMethod m) { appdomain.Invoke(m, null, null); } } 热更新工程 namespace HotFix_Project { public class TestCLRBinding { public static void RunTest() { for (int i = 0; i < 100000; i++) { CLRBindingTestClass.DoSomeTest(i, i); } } } }
主工程 using System.Collections.Generic; using System; using System.Collections; using ILRuntime.Runtime.Enviorment; using ILRuntime.Runtime.Intepreter; using ILRuntime.CLR.Method; public class CoroutineAdapter: CrossBindingAdaptor { public override Type BaseCLRType { get { return null; } } public override Type[] BaseCLRTypes { get { //跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,对于coroutine来说是IEnumerator//ILRuntime虽然支持,但是一定要小心这种用法,使用不当很容易造成不可预期的问题 //日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类 return new Type[] { typeof(IEnumerator<object>), typeof(IEnumerator), typeof(IDisposable) }; } } public override Type AdaptorType { get { return typeof(Adaptor); } } public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain,ILTypeInstance instance) { return new Adaptor(appdomain, instance); } // Coroutine生成的类实现了IEnumerator, IEnumerator, IDisposable,所以都要实现,这个可以通过reflector之类的IL反编译软件得知 internal class Adaptor: IEnumerator, IEnumerator, IDisposable, CrossBindingAdaptorType { ILTypeInstance instance; ILRuntime.Runtime.Enviorment.AppDomain appdomain; public Adaptor() { } public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { this.appdomain = appdomain; this.instance = instance; } public ILTypeInstance ILInstance { get { return instance; } } IMethod mCurrentMethod; bool mCurrentMethodGot; public object Current { get { if (!mCurrentMethodGot) { mCurrentMethod = instance.Type.GetMethod("get_Current", 0); if (mCurrentMethod == null) { //这里写System.Collections.IEnumerator.get_Current而不是直接get_Current是因为coroutine生成的类是显式实现这个接口的,通过Reflector等反编译软件可得知 //为了兼容其他只实现了单一Current属性的,所以上面先直接取了get_Current mCurrentMethod = instance.Type.GetMethod("System.Collections.IEnumerator.get_Current", 0); } mCurrentMethodGot = true; } if (mCurrentMethod != null) { var res = appdomain.Invoke(mCurrentMethod, instance, null); return res; } else { return null; } } } IMethod mDisposeMethod; bool mDisposeMethodGot; public void Dispose() { if (!mDisposeMethodGot) { mDisposeMethod = instance.Type.GetMethod("Dispose", 0); if (mDisposeMethod == null) { mDisposeMethod = instance.Type.GetMethod("System.IDisposable.Dispose", 0); } mDisposeMethodGot = true; } if (mDisposeMethod != null) { appdomain.Invoke(mDisposeMethod, instance, null); } } IMethod mMoveNextMethod; bool mMoveNextMethodGot; public bool MoveNext() { if (!mMoveNextMethodGot) { mMoveNextMethod = instance.Type.GetMethod("MoveNext", 0); mMoveNextMethodGot = true; } if (mMoveNextMethod != null) { return (bool)appdomain.Invoke(mMoveNextMethod, instance, null); } else { return false; } } IMethod mResetMethod; bool mResetMethodGot; public void Reset() { if (!mResetMethodGot) { mResetMethod = instance.Type.GetMethod("Reset", 0); mResetMethodGot = true; } if (mResetMethod != null) { appdomain.Invoke(mResetMethod, instance, null); } } public override string ToString() { IMethod m = appdomain.ObjectType.GetMethod("ToString", 0); m = instance.Type.GetVirtualMethod(m); if (m == null || m is ILMethod) { return instance.ToString(); } else { return instance.Type.FullName; } } } } public class CoroutineDemo: MonoBehaviour { ... static CoroutineDemo instance; public static CoroutineDemo Instance { get { return instance; } } void InitializeILRuntime() { //这里做一些ILRuntime的注册 //使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator appdomain.RegisterCrossBindingAdaptor(new CoroutineAdapter()); appdomain.DebugService.StartDebugService(56000); } unsafe void OnHotFixLoaded() { appdomain.Invoke("HotFix_Project.TestCoroutine", "RunTest", null, null); } public void DoCoroutine(IEnumerator coroutine) { StartCoroutine(coroutine); } } 热更工程 namespace HotFix_Project { public class TestCoroutine { public static void RunTest() { CoroutineDemo.Instance.DoCoroutine(Coroutine()); } static System.Collections.IEnumerator Coroutine() { Debug.Log("开始协程, t = " + Time.time); yield return new WaitForSeconds(3); Debug.Log("等待了3秒, t = " + Time.time); } } }
主工程 using UnityEngine; using System.Collections.Generic; using ILRuntime.Other; using System; using System.Collections; using ILRuntime.Runtime.Enviorment; using ILRuntime.Runtime.Intepreter; using ILRuntime.CLR.Method; public class MonoBehaviourAdapter : CrossBindingAdaptor { public override Type BaseCLRType { get { return typeof(MonoBehaviour); } } public override Type AdaptorType { get { return typeof(Adaptor); } } public override object CreateCLRInstance(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { return new Adaptor(appdomain, instance); } //为了完整实现MonoBehaviour的所有特性,这个Adapter还得扩展,这里只抛砖引玉,只实现了最常用的Awake, Start和Update public class Adaptor : MonoBehaviour, CrossBindingAdaptorType { ILTypeInstance instance; ILRuntime.Runtime.Enviorment.AppDomain appdomain; public Adaptor() { } public Adaptor(ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) { this.appdomain = appdomain; this.instance = instance; } public ILTypeInstance ILInstance { get { return instance; } set { instance = value; } } public ILRuntime.Runtime.Enviorment.AppDomain AppDomain { get { return appdomain; } set { appdomain = value; } } IMethod mAwakeMethod; bool mAwakeMethodGot; public void Awake() { //Unity会在ILRuntime准备好这个实例前调用Awake,所以这里暂时先不掉用 if (instance != null) { if (!mAwakeMethodGot) { mAwakeMethod = instance.Type.GetMethod("Awake", 0); mAwakeMethodGot = true; } if (mAwakeMethod != null) { appdomain.Invoke(mAwakeMethod, instance, null); } } } IMethod mStartMethod; bool mStartMethodGot; void Start() { if (!mStartMethodGot) { mStartMethod = instance.Type.GetMethod("Start", 0); mStartMethodGot = true; } if (mStartMethod != null) { appdomain.Invoke(mStartMethod, instance, null); } } IMethod mUpdateMethod; bool mUpdateMethodGot; void Update() { if (!mUpdateMethodGot) { mUpdateMethod = instance.Type.GetMethod("Update", 0); mUpdateMethodGot = true; } if (mStartMethod != null) { appdomain.Invoke(mUpdateMethod, instance, null); } } public override string ToString() { IMethod m = appdomain.ObjectType.GetMethod("ToString", 0); m = instance.Type.GetVirtualMethod(m); if (m == null || m is ILMethod) { return instance.ToString(); } else return instance.Type.FullName; } } } using UnityEngine; using System.Collections; using System.Collections.Generic; using System.IO; using ILRuntime.CLR.TypeSystem; using ILRuntime.CLR.Method; using ILRuntime.CLR.Utils; using ILRuntime.Runtime.Intepreter; using ILRuntime.Runtime.Stack; using ILRuntime.Runtime.Enviorment; public class MonoBehaviourDemo : MonoBehaviour { static MonoBehaviourDemo instance; System.IO.MemoryStream fs; System.IO.MemoryStream p; public static MonoBehaviourDemo Instance { get { return instance; } } //AppDomain是ILRuntime的入口,最好是在一个单例类中保存,整个游戏全局就一个,这里为了示例方便,每个例子里面都单独做了一个 //大家在正式项目中请全局只创建一个AppDomain AppDomain appdomain; void Start() { instance = this; StartCoroutine(LoadHotFixAssembly()); } IEnumerator LoadHotFixAssembly() { //首先实例化ILRuntime的AppDomain,AppDomain是一个应用程序域,每个AppDomain都是一个独立的沙盒 appdomain = new ILRuntime.Runtime.Enviorment.AppDomain(); //正常项目中应该是自行从其他地方下载dll,或者打包在AssetBundle中读取,平时开发以及为了演示方便直接从StreammingAssets中读取, //正式发布的时候需要大家自行从其他地方读取dll //!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! //这个DLL文件是直接编译HotFix_Project.sln生成的,已经在项目中设置好输出目录为StreamingAssets,在VS里直接编译即可生成到对应目录,无需手动拷贝 #if UNITY_ANDROID WWW www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.dll"); #else WWW www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.dll"); #endif while (!www.isDone) yield return null; if (!string.IsNullOrEmpty(www.error)) UnityEngine.Debug.LogError(www.error); byte[] dll = www.bytes; www.Dispose(); //PDB文件是调试数据库,如需要在日志中显示报错的行号,则必须提供PDB文件,不过由于会额外耗用内存,正式发布时请将PDB去掉,下面LoadAssembly的时候pdb传null即可 #if UNITY_ANDROID www = new WWW(Application.streamingAssetsPath + "/HotFix_Project.pdb"); #else www = new WWW("file:///" + Application.streamingAssetsPath + "/HotFix_Project.pdb"); #endif while (!www.isDone) yield return null; if (!string.IsNullOrEmpty(www.error)) UnityEngine.Debug.LogError(www.error); byte[] pdb = www.bytes; fs = new MemoryStream(dll); p = new MemoryStream(pdb); appdomain.LoadAssembly(fs, p, new ILRuntime.Mono.Cecil.Pdb.PdbReaderProvider()); InitializeILRuntime(); OnHotFixLoaded(); } private void OnDestroy() { fs.Close(); p.Close(); } unsafe void InitializeILRuntime() { //这里做一些ILRuntime的注册 appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId; appdomain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter()); appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder()); //ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain); } unsafe void OnHotFixLoaded() { Debug.Log("在热更DLL里面使用MonoBehaviour是可以做到的,但是并不推荐这么做"); Debug.Log("因为即便能做到使用,要完全支持MonoBehaviour的所有特性,会需要很多额外的工作量"); Debug.Log("而且通过MonoBehaviour做游戏逻辑当项目规模大到一定程度之后会是个噩梦,因此应该尽量避免"); Debug.Log("直接调用GameObject.AddComponent会报错,这是因为这个方法是Unity实现的,他并不可能取到热更DLL内部的类型 "); Debug.Log("因此我们需要挟持AddComponent方法,然后自己实现"); Debug.Log("我们先销毁掉之前创建的不合法的MonoBehaviour"); SetupCLRRedirection(); appdomain.Invoke("HotFix_Project.TestMonoBehaviour", "RunTest", null, gameObject); Debug.Log("可以看到已经成功了"); Debug.Log("下面做另外一个实验"); Debug.Log("GetComponent跟AddComponent类似,需要我们自己处理"); SetupCLRRedirection2(); appdomain.Invoke("HotFix_Project.TestMonoBehaviour", "RunTest2", null, gameObject); Debug.Log("成功了"); Debug.Log("那我们怎么从Unity主工程获取热更DLL的MonoBehaviour呢?"); Debug.Log("这需要我们自己实现一个GetComponent方法"); var type = appdomain.LoadedTypes["HotFix_Project.SomeMonoBehaviour2"] as ILType; var smb = GetComponent(type); var m = type.GetMethod("Test2"); Debug.Log("现在来试试调用"); appdomain.Invoke(m, smb, null); Debug.Log("调用成功!"); Debug.Log("我们点一下左边列表里的GameObject,查看一下我们刚刚挂的脚本"); Debug.Log("默认情况下是无法显示DLL里面定义的public变量的值的"); Debug.Log("这个Demo我们写了一个自定义Inspector来查看变量,同样只是抛砖引玉"); Debug.Log("要完整实现MonoBehaviour所有功能得大家自己花功夫了,最好还是避免脚本里使用MonoBehaviour"); Debug.Log("具体实现请看MonoBehaviourAdapterEditor"); Debug.Log("特别注意,现在仅仅是运行时可以看到和编辑,由于没有处理序列化的问题,所以并不可能保存到Prefab当中,要想实现就得靠大家自己了"); } unsafe void SetupCLRRedirection() { //这里面的通常应该写在InitializeILRuntime,这里为了演示写这里 var arr = typeof(GameObject).GetMethods(); foreach (var i in arr) { if (i.Name == "AddComponent" && i.GetGenericArguments().Length == 1) { appdomain.RegisterCLRMethodRedirection(i, AddComponent); } } } unsafe void SetupCLRRedirection2() { //这里面的通常应该写在InitializeILRuntime,这里为了演示写这里 var arr = typeof(GameObject).GetMethods(); foreach (var i in arr) { if (i.Name == "GetComponent" && i.GetGenericArguments().Length == 1) { appdomain.RegisterCLRMethodRedirection(i, GetComponent); } } } MonoBehaviourAdapter.Adaptor GetComponent(ILType type) { var arr = GetComponents(); for(int i = 0; i < arr.Length; i++) { var instance = arr[i]; if(instance.ILInstance != null && instance.ILInstance.Type == type) { return instance; } } return null; } unsafe static StackObject* AddComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj) { //CLR重定向的说明请看相关文档和教程,这里不多做解释 ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; var ptr = __esp - 1; //成员方法的第一个参数为this GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject; if (instance == null) throw new System.NullReferenceException(); __intp.Free(ptr); var genericArgument = __method.GenericArguments; //AddComponent应该有且只有1个泛型参数 if (genericArgument != null && genericArgument.Length == 1) { var type = genericArgument[0]; object res; if(type is CLRType) { //Unity主工程的类不需要任何特殊处理,直接调用Unity接口 res = instance.AddComponent(type.TypeForCLR); } else { //热更DLL内的类型比较麻烦。首先我们得自己手动创建实例 var ilInstance = new ILTypeInstance(type as ILType, false);//手动创建实例是因为默认方式会new MonoBehaviour,这在Unity里不允许 //接下来创建Adapter实例 var clrInstance = instance.AddComponent (); //unity创建的实例并没有热更DLL里面的实例,所以需要手动赋值 clrInstance.ILInstance = ilInstance; clrInstance.AppDomain = __domain; //这个实例默认创建的CLRInstance不是通过AddComponent出来的有效实例,所以得手动替换 ilInstance.CLRInstance = clrInstance; res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance clrInstance.Awake();//因为Unity调用这个方法时还没准备好所以这里补调一次 } return ILIntepreter.PushObject(ptr, __mStack, res); } return __esp; } unsafe static StackObject* GetComponent(ILIntepreter __intp, StackObject* __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj) { //CLR重定向的说明请看相关文档和教程,这里不多做解释 ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain; var ptr = __esp - 1; //成员方法的第一个参数为this GameObject instance = StackObject.ToObject(ptr, __domain, __mStack) as GameObject; if (instance == null) throw new System.NullReferenceException(); __intp.Free(ptr); var genericArgument = __method.GenericArguments; //AddComponent应该有且只有1个泛型参数 if (genericArgument != null && genericArgument.Length == 1) { var type = genericArgument[0]; object res = null; if (type is CLRType) { //Unity主工程的类不需要任何特殊处理,直接调用Unity接口 res = instance.GetComponent(type.TypeForCLR); } else { //因为所有DLL里面的MonoBehaviour实际都是这个Component,所以我们只能全取出来遍历查找 var clrInstances = instance.GetComponents (); for(int i = 0; i < clrInstances.Length; i++) { var clrInstance = clrInstances[i]; if (clrInstance.ILInstance != null)//ILInstance为null, 表示是无效的MonoBehaviour,要略过 { if (clrInstance.ILInstance.Type == type) { res = clrInstance.ILInstance;//交给ILRuntime的实例应该为ILInstance break; } } } } return ILIntepreter.PushObject(ptr, __mStack, res); } return __esp; } } using UnityEngine; using UnityEditor; using System.Collections; using System.Collections.Generic; using System.IO; using ILRuntime.CLR.TypeSystem; using ILRuntime.CLR.Method; using ILRuntime.CLR.Utils; using ILRuntime.Runtime.Intepreter; using ILRuntime.Runtime.Stack; using ILRuntime.Runtime.Enviorment; [CustomEditor(typeof(MonoBehaviourAdapter.Adaptor), true)] public class MonoBehaviourAdapterEditor : UnityEditor.UI.GraphicEditor { public override void OnInspectorGUI() { serializedObject.Update(); MonoBehaviourAdapter.Adaptor clr = target as MonoBehaviourAdapter.Adaptor; var instance = clr.ILInstance; if (instance != null) { EditorGUILayout.LabelField("Script", clr.ILInstance.Type.FullName); foreach (var i in instance.Type.FieldMapping) { //这里是取的所有字段,没有处理不是public的 var name = i.Key; var type = instance.Type.FieldTypes[i.Value]; var cType = type.TypeForCLR; if (cType.IsPrimitive)//如果是基础类型 { if (cType == typeof(float)) { instance[i.Value] = EditorGUILayout.FloatField(name, (float)instance[i.Value]); } else throw new System.NotImplementedException();//剩下的大家自己补吧 } else { object obj = instance[i.Value]; if (typeof(UnityEngine.Object).IsAssignableFrom(cType)) { //处理Unity类型 var res = EditorGUILayout.ObjectField(name, obj as UnityEngine.Object, cType, true); instance[i.Value] = res; } else { //其他类型现在没法处理 if (obj != null) EditorGUILayout.LabelField(name, obj.ToString()); else EditorGUILayout.LabelField(name, "(null)"); } } } } } } 热更工程 namespace HotFix_Project { class SomeMonoBehaviour: MonoBehaviour { float time; void Awake() { Debug.Log("!! SomeMonoBehaviour.Awake"); } void Start() { Debug.Log("!! SomeMonoBehaviour.Start"); } void Update() { if (Time.time - time > 1) { Debug.Log("!! SomeMonoBehaviour.Update, t = " + Time.time); time = Time.time; } } public void Test() { Debug.Log("SomeMonoBehaviour"); } } class SomeMonoBehaviour2: MonoBehaviour { public GameObject TargetGO; public Texture2D Texture; public void Test2() { Debug.Log("!!! SomeMonoBehaviour2.Test2"); } } public class TestMonoBehaviour { public static void RunTest(GameObject go) { go.AddComponent (); } public static void RunTest2(GameObject go) { go.AddComponent (); var mb = go.GetComponent (); Debug.Log("!!! TestMonoBehaviour.RunTest2 mb = " + mb); mb.Test2(); } } }
主工程 void OnHotFixLoaded() { Debug.Log("C#工程中反射是一个非常经常用到功能,ILRuntime也对反射进行了支持,在热更DLL中使用反射跟原生C#没有任何区别,故不做介绍"); Debug.Log("这个Demo主要是介绍如何在主工程中反射热更DLL中的类型"); Debug.Log("假设我们要通过反射创建HotFix_Project.InstanceClass的实例"); Debug.Log("显然我们通过Activator或者Type.GetType(\"HotFix_Project.InstanceClass\")是无法取到类型信息的"); Debug.Log("热更DLL中的类型我们均需要通过AppDomain取得"); var it = appdomain.LoadedType["HotFix_Project.InstanceClass"]; Debug.Log("LoadedType返回的是IType类型,但是我们需要获得对应的System.Type才能继续使用反射接口"); var type = it.ReflectionType; Debug.Log("取得Type之后就可以按照我们熟悉的方式来反射调用了"); var ctor = type.GetConstructor(new System.Type[0]); var obj = ctor.Invoke(null); Debug.Log("打印一下结果"); Debug.Log(obj); Debug.Log("我们试一下用反射给字段赋值"); var fi = type.GetField("id", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance); fi.SetValue(obj, 111111); Debug.Log("我们用反射调用属性检查刚刚的赋值"); var pi = type.GetProperty("ID"); Debug.Log("ID = " + pi.GetValue(obj, null)); } 热更工程 namespace HotFix_Project { public class InstanceClass { private int id; public InstanceClass() { UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass()"); this.id = 0; } public InstanceClass(int id) { UnityEngine.Debug.Log("!!! InstanceClass::InstanceClass() id = " + id); this.id = id; } public int ID { get { return id; } } // static method public static void StaticFunTest() { UnityEngine.Debug.Log("!!! InstanceClass.StaticFunTest()"); } public static void StaticFunTest2(int a) { UnityEngine.Debug.Log("!!! InstanceClass.StaticFunTest2(), a=" + a); } public static void GenericMethod(T a) { UnityEngine.Debug.Log("!!! InstanceClass.GenericMethod(), a=" + a); } } }
相关文章
ILRuntime入门笔记
Unity实现c#热更新方案探究(一)
程序的热升级
unity dll实现热更新
Unity实现c#热更新方案探究(二)
Mono为何能跨平台?聊聊CIL(MSIL)
谁偷了我的热更新?Mono,JIT,iOS
Unity实现c#热更新方案探究(三)