ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新
项目位置:\ILRuntimeU3D-master\ILRuntimeDemo
\ILRuntimeU3D-master\HotFix_Project\HotFix_Project.sln
引用=》添加引用=》浏览 (如果存在可以先删除)
F:/Unity/Editor/Data/PlaybackEngines/windowsstandalonesupport/Variations/win64_nondevelopment_mono/Data/Managed/UnityEngine.dll
F:/Unity/Editor/Data/UnityExtensions/Unity/GUISystem/UnityEngine.UI.dll
F:/Unity/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll
<AllowUnsafeBlocks>false</AllowUnsafeBlocks>
AppDomain appdomain;
appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();
#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;
using (System.IO.MemoryStream fs = new MemoryStream(dll))
{
using (System.IO.MemoryStream p = new MemoryStream(pdb))
{
appdomain.LoadAssembly(fs, p, new Mono.Cecil.Pdb.PdbReaderProvider());
}
}
void InitializeILRuntime()
{
//这里做一些ILRuntime的注册,HelloWorld示例暂时没有需要注册的
}
void OnHotFixLoaded()
{
//第一次方法调用
}
无参数
//调用无参数静态方法,appdomain.Invoke("类名", "方法名", 对象引用, 参数列表);
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);
有参数
//调用带参数的静态方法
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);
无参数
//预先获得IMethod,可以减低每次调用查找方法耗用的时间
IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
//根据方法名称和参数个数获取方法
IMethod method = type.GetMethod("StaticFunTest", 0);
appdomain.Invoke(method, null, null);
有参数
IType intType = appdomain.GetType(typeof(int));
//参数类型列表
List<IType> paramList = new List<ILRuntime.CLR.TypeSystem.IType>();
paramList.Add(intType);
//根据方法名称和参数类型列表获取方法
method = type.GetMethod("StaticFunTest2", paramList, null);
appdomain.Invoke(method, null, 456);
方法1 实例化对象
object obj = appdomain.Instantiate("HotFix_Project.InstanceClass", new object[] { 233 })
调用方法
int id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj, null);
方法2 实例化对象
object obj2 = ((ILType)type).Instantiate();
调用方法
id = (int)appdomain.Invoke("HotFix_Project.InstanceClass", "get_ID", obj2, null);
IType stringType = appdomain.GetType(typeof(string));
IType[] genericArguments = new IType[] { stringType };
调用方法
appdomain.InvokeGenericMethod("HotFix_Project.InstanceClass", "GenericMethod", genericArguments, null, "TestString");
方法2(先获取IMethod) 构建参数
paramList.Clear();
paramList.Add(intType);
genericArguments = new IType[] { intType };
调用方法
method = type.GetMethod("GenericMethod", paramList, genericArguments);
appdomain.Invoke(method, null, 33333);
//TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
appdomain.DelegateManager.RegisterMethodDelegate<int>();
//带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
appdomain.DelegateManager.RegisterFunctionDelegate<int, string>();
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateMethod>((action) =>
{
//转换器的目的是把Action或者Func转换成正确的类型,这里则是把Action<int>转换成TestDelegateMethod
return new TestDelegateMethod((a) =>
{
//调用委托实例
((System.Action<int>)action)(a);
});
});
//对于TestDelegateFunction同理,只是是将Func<int, string>转换成TestDelegateFunction
appdomain.DelegateManager.RegisterDelegateConvertor<TestDelegateFunction>((action) =>
{
return new TestDelegateFunction((a) =>
{
return ((System.Func<int, string>)action)(a);
});
});
//下面再举一个这个Demo中没有用到,但是UGUI经常遇到的一个委托,例如UnityAction<float>
appdomain.DelegateManager.RegisterDelegateConvertor<UnityEngine.Events.UnityAction<float>>((action) =>
{
return new UnityEngine.Events.UnityAction<float>((a) =>
{
((System.Action<float>)action)(a);
});
});
appdomain.Invoke("HotFix_Project.TestDelegate", "Initialize2", null, null);
appdomain.Invoke("HotFix_Project.TestDelegate", "RunTest2", null, null);
TestMethodDelegate(789);
TestFunctionDelegate(098);
TestActionDelegate("Hello From Unity Main Project");
appdomain.RegisterCrossBindingAdaptor(new InheritanceAdapter());
obj = appdomain.Instantiate(“HotFix_Project.TestInheritance”); “` 3. 使用
obj.TestAbstract(123);
obj.TestVirtual("Hello");
使用到的地方(当我们需要挟持原方法实现,添加一些热更DLL中的特殊处理的时候,就需要CLR重定向了)
重定向Log方法
var mi = typeof(Debug).GetMethod("Log", new System.Type[] { typeof(object) });
appdomain.RegisterCLRMethodRedirection(mi, Log_11);
Log11的方法
//编写重定向方法对于刚接触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.GetStackTrance(__intp);
//我们在输出信息后面加上DLL堆栈
UnityEngine.Debug.Log(message + "\n" + stacktrace);
return __ret;
}
默认情况下,从热更DLL里调用Unity主工程的方法,是通过反射的方式调用的,这个过程中会产生GC Alloc,并且执行效率会偏低
unity=》ILRuntime=》Generate…
public class CLRBindingTestClass
{
public static float DoSomeTest(int a, float b)
{
return a + b;
}
}
ILRuntimeCLRBinding.cs=》class ILRuntimeCLRBinding=》GenerateCLRBinding添加添加types.Add(typeof(CLRBindingTestClass));
var type = appdomain.LoadedTypes["HotFix_Project.TestCLRBinding"];
var m = type.GetMethod("RunTest", 0);
appdomain.Invoke(m, null, null);
//使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator<object>,IDisposable接口的类,因为这是跨域继承,所以需要写CrossBindAdapter(详细请看04_Inheritance教程),Demo已经直接写好,直接注册即可
appdomain.RegisterCrossBindingAdaptor(new CoroutineAdapter());
appdomain.Invoke("HotFix_Project.TestCoroutine", "RunTest", null, null);
注意事项
重定向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);
}
}
}
AddComponent方法
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<MonoBehaviourAdapter.Adaptor>();
//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;
}
重定向GetComponent
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);
}
}
}
GetComponent方法
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<MonoBehaviourAdapter.Adaptor>();
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;
}
获取热更dll中的MonoBehaviour
var type = appdomain.LoadedTypes["HotFix_Project.SomeMonoBehaviour2"] as ILType;
var smb = GetComponent(type);
var m = type.GetMethod("Test2");
appdomain.Invoke(m, smb, null);
```
>主工程工程定义GetComponent方法
```c#
MonoBehaviourAdapter.Adaptor GetComponent(ILType type)
{
var arr = GetComponents<MonoBehaviourAdapter.Adaptor>();
for(int i = 0; i < arr.Length; i++)
{
var instance = arr[i];
if(instance.ILInstance != null && instance.ILInstance.Type == type)
{
return instance;
}
}
return null;
}
var it = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
var type = it.ReflectionType;
var ctor = type.GetConstructor(new System.Type[0]);
var obj = ctor.Invoke(null);
试一下用反射给字段赋值
var fi = type.GetField("id", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Instance);
fi.SetValue(obj, 111111);
用反射调用属性检查刚刚的赋值
var pi = type.GetProperty("ID");
在使用LitJson前,需要对LitJson进行注册,注册方法很简单,只需要在ILRuntime初始化阶段,在注册CLR绑定之前,执行下面这行代码即可:
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
LitJson的使用很简单,JsonMapper类里面提供了对象到Json以及Json到对象的转换方法,具体使用方法请看热更项目中的代码 TestJson.cs
使用的原因
appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
appdomain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
appdomain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());
#新知识
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Reset();
sw.Start();
sw.Stop();
Debug.LogFormat("刚刚的方法执行了:{0} ms", sw.ElapsedMilliseconds);
Profiler.BeginSample("xxx");
Profiler.EndSample();
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
发布者:全栈程序员栈长,转载请注明出处:https://javaforall.cn/209952.html原文链接:https://javaforall.cn