ILRuntime介绍
ILRuntime项目为基于C#的平台(例如Unity)提供了一个纯C#实现,快速、方便且可靠的IL运行时,使得能够在不支持JIT的硬件环境(如iOS)能够实现代码的热更新
ILRuntime优势
访问C#工程的现成代码,无需额外抽象脚本API
直接使用VS2015进行开发,ILRuntime的解译引擎支持.Net 4.6编译的DLL
执行效率是L#的10-20倍
选择性的CLR绑定使跨域调用更快速,绑定后跨域调用的性能能达到slua的2倍左右(从脚本调用GameObject之类的接口)
支持跨域继承
完整的泛型支持
拥有Visual Studio的调试插件,可以实现真机源码级调试。支持Visual Studio 2015 Update3 以及Visual Studio 2017
----
ILRuntime demo工程
1. 下载ILRuntimeU3D demo
下载地址
2. unity打开项目
项目位置:\ILRuntimeU3D-master\ILRuntimeDemo
3. vs打开HotFix_project工程
\ILRuntimeU3D-master\HotFix_Project\HotFix_Project.sln
4. 修改HotFix_Project工程的引用
引用=》添加引用=》浏览 (如果存在可以先删除)
UnityEngine
F:/Unity/Editor/Data/PlaybackEngines/windowsstandalonesupport/Variations/win64_nondevelopment_mono/Data/Managed/UnityEngine.dll
UnityEngine.UI
F:/Unity/Editor/Data/UnityExtensions/Unity/GUISystem/UnityEngine.UI.dll
UnityEngine.CoreModule
F:/Unity/Editor/Data/Managed/UnityEngine/UnityEngine.CoreModule.dll
5. 修改ILRuntimeU3D-master\ILRuntimeDemo*.csproj
false/blockquote>
h4>6. 编译HotFix_Project工程
/h4>
blockquote>
/blockquote>
p>右键=>生成
/p>
p>生成成功,在\ILRuntimeU3D-master\ILRuntimeDemo\Assets\StreamingAssets\目录生成HotFix_Project.dll
/p>
h4>7. \ILRuntimeU3D-master\HotFix_Project\HotFix_Project.sln 热更新脚本
/h4>
h4>8. \LRuntimeU3D-master\ILRuntimeDemo\ILRuntimeDemo.sln unity主工程
/h4>
p>----
/p>
h2>ILRunTime的使用
/h2>
h3>1. 下载ILRuntime相关文件
/h3>
p>下载ILRuntime-master工程
/p>
p>下载ILRuntime vs调试插件
/p>
h3>2. 检查热更新
/h3>
p>如果有热更,进行热更新
/p>
h3>3. 实例化AppDomain(全局保存一个)
/h3>
pre class='language-javascript'>
AppDomain appdomain;
appdomain = new ILRuntime.Runtime.Enviorment.AppDomain();/pre>
h3>4. 加载dll和pdb
/h3>
pre class='language-javascript'>
#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());
}
}/pre>
h3>5. 初始化
/h3>
p>InitializeILRuntime();
/p>
pre class='language-javascript'>
void InitializeILRuntime()
{
//这里做一些ILRuntime的注册,HelloWorld示例暂时没有需要注册的
}/pre>
p>OnHotFixLoaded();
/p>
pre class='language-javascript'>
void OnHotFixLoaded()
{
//第一次方法调用
}/pre>
h3>6. 各个地方的使用
/h3>
h5>主工程脚本调用热更脚本
/h5>
p>调用类的静态方法
/p>
p>方法1
/p>
blockquote>无参数
/blockquote>
pre class='language-javascript'>
//调用无参数静态方法,appdomain.Invoke("类名", "方法名", 对象引用, 参数列表);
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest", null, null);/pre>
blockquote>有参数
/blockquote>
pre class='language-javascript'>
//调用带参数的静态方法
appdomain.Invoke("HotFix_Project.InstanceClass", "StaticFunTest2", null, 123);/pre>
p>方法2
/p>
blockquote>无参数
/blockquote>
pre class='language-javascript'>
//预先获得IMethod,可以减低每次调用查找方法耗用的时间
IType type = appdomain.LoadedTypes["HotFix_Project.InstanceClass"];
//根据方法名称和参数个数获取方法
IMethod method = type.GetMethod("StaticFunTest", 0);
appdomain.Invoke(method, null, null);/pre>
blockquote>有参数
/blockquote>
pre class='language-javascript'>
IType intType = appdomain.GetType(typeof(int));
//参数类型列表
ListparamList = new List ();
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);
调用类的静态泛型方法
方法1(直接调用)
构建参数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);热更DLL里面的委托实例传到Unity主工程用
注册适配器
//TestDelegateMethod, 这个委托类型为有个参数为int的方法,注册仅需要注册不同的参数搭配即可
appdomain.DelegateManager.RegisterMethodDelegate();
//带返回值的委托的话需要用RegisterFunctionDelegate,返回类型为最后一个
appdomain.DelegateManager.RegisterFunctionDelegate(); appdomain.DelegateManager.RegisterDelegateConvertor
((action) =>
{
//转换器的目的是把Action或者Func转换成正确的类型,这里则是把Action转换成TestDelegateMethod
return new TestDelegateMethod((a) =>
{
//调用委托实例
((System.Action)action)(a);
});
});
//对于TestDelegateFunction同理,只是是将Func转换成TestDelegateFunction
appdomain.DelegateManager.RegisterDelegateConvertor((action) =>
{
return new TestDelegateFunction((a) =>
{
return ((System.Func)action)(a);
});
});
//下面再举一个这个Demo中没有用到,但是UGUI经常遇到的一个委托,例如UnityAction
appdomain.DelegateManager.RegisterDelegateConvertor>((action) =>
{
return new UnityEngine.Events.UnityAction((a) =>
{
((System.Action)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());
InheritanceAdapter.cs
实例化
obj = appdomain.Instantiate(“HotFix_Project.TestInheritance”);
“`
3. 使用obj.TestAbstract(123);
obj.TestVirtual("Hello");CLR重定向
使用到的地方(当我们需要挟持原方法实现,添加一些热更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, IListCLR绑定
默认情况下,从热更DLL里调用Unity主工程的方法,是通过反射的方式调用的,这个过程中会产生GC Alloc,并且执行效率会偏低
使用到的地方
热更脚本调用主工程脚本
但需要在主工程中提前做好相应工作
注意事项
一定要记得将CLR绑定的注册写在CLR重定向的注册后面,因为同一个方法只能被重定向一次,只有先注册的那个才能生效。
可以选择性的对经常使用的CLR接口进行直接调用,从而尽可能的消除反射调用开销以及额外的GC Alloc
CLR绑定会生成较多C#代码,最终会增大包体和Native Code的内存耗用,所以只添加常用类型和频繁调用的接口即可
生成所需的绑定代码
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));
生成绑定代码
unity=》ILRuntime=》Generate CLR Binding Code
ILRuntime->Generate CLR Binding Code by Analysis是ILRT1.2版新加入的功能,可以根据热更DLL自生成绑定代码
注册
ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);
使用
var type = appdomain.LoadedTypes["HotFix_Project.TestCLRBinding"];
var m = type.GetMethod("RunTest", 0);
appdomain.Invoke(m, null, null);协程Coroutine
注册
//使用Couroutine时,C#编译器会自动生成一个实现了IEnumerator,IEnumerator
适配方法
CoroutineAdapter.csappdomain.Invoke("HotFix_Project.TestCoroutine", "RunTest", null, null);
MonoBehaviour
注意事项
在热更DLL里面使用MonoBehaviour是可以做到的,但是并不推荐这么做
缺什么补什么
热更脚本使用AddComponent
重定向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
热更脚本使用GetComponent
重定向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
从主工程获取热更DLL的MonoBehaviour
获取热更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();
for(int i = 0; i < arr.Length; i++)
{
var instance = arr[i];
if(instance.ILInstance != null && instance.ILInstance.Type == type)
{
return instance;
}
}
return null;
}反射Reflection(主工程中反射热更DLL中的类型)
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)
在使用LitJson前,需要对LitJson进行注册,注册方法很简单,只需要在ILRuntime初始化阶段,在注册CLR绑定之前,执行下面这行代码即可:
注册
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
使用
LitJson的使用很简单,JsonMapper类里面提供了对象到Json以及Json到对象的转换方法,具体使用方法请看热更项目中的代码
TestJson.cs
ValueTypeBinding(提供热更脚本使用)
使用的原因
Vector3等Unity常用值类型如果不做任何处理,在ILRuntime中使用会产生较多额外的CPU开销和GC Alloc
我们通过值类型绑定可以解决这个问题,只有Unity主工程的值类型才需要此处理,热更DLL内定义的值类型不需要任何处理
注册方法
appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
appdomain.RegisterValueTypeBinder(typeof(Quaternion), new QuaternionBinder());
appdomain.RegisterValueTypeBinder(typeof(Vector2), new Vector2Binder());----
#新知识unity测试代码运行的时间
System.Diagnostics.Stopwatch sw = new System.Diagnostics.Stopwatch();
sw.Reset();
sw.Start();
sw.Stop();
Debug.LogFormat("刚刚的方法执行了:{0} ms", sw.ElapsedMilliseconds);unity脚本代码性能(unity=》window=》profiler 不知道为什么我没有这个选项)
Profiler.BeginSample("xxx");
Profiler.EndSample();
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。
如需转载请保留出处:https://bianchenghao.cn/hz/148852.html