ILRuntime 学习

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()");
        }
    }
}
01_HelloWorld
主工程

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));
    // 参数类型列表
    List paramList = 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);
        }
    }
}
02_Invocation
主工程

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);
        }
    }
}
03_DelegateDemo
主工程

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();
    }
}
04_Inheritance
主工程



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信息");
        }
    }
}
05_CLRRedirection
主工程

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);
            }
        }
    }
}
06_CLRBinding
主工程

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,IEnumerator和IDisposable,
            //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,IDisposable接口的类,因为这是跨域继承,所以需要写CrossBindAdapter(详细请看04_Inheritance教程),Demo已经直接写好,直接注册即可
        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);
        }
    }
}

07_Coroutine

主工程

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();
        }
    }
}
08_MonoBehaviour
主工程

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);
        }
    }
}
09_Reflection

 

 

 

 

 

 

 

 

 

 

相关文章

ILRuntime入门笔记

Unity实现c#热更新方案探究(一)

  程序的热升级

  unity dll实现热更新 

Unity实现c#热更新方案探究(二)

  Mono为何能跨平台?聊聊CIL(MSIL)

  谁偷了我的热更新?Mono,JIT,iOS 

Unity实现c#热更新方案探究(三)

 

你可能感兴趣的:(ILRuntime 学习)