在ILRuntime中使用反射

反射接口的使用

1.ILRuntime对主要反射接口进行了模拟。
2.ILRuntimeType。
3.ILRuntimeWrapperType。
4.Attribute。
5.Enum。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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.LoadedTypes["HotFix_Project.InstanceClass"];
Debug.Log("LoadedTypes返回的是IType类型,但是我们需要获得对应的System.Type才能继续使用反射接口");
var type = it.ReflectionType;
Debug.Log("取得Type之后就可以按照我们熟悉的方式来反射调用了");
var ctor = type.GetConstructor(new System.Type[0]);
//var obj = System.Activator.CreateInstance(type);//NG
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));

if (type is ILRuntime.Reflection.ILRuntimeType)
{
ILRuntime.Reflection.ILRuntimeType ilt = (ILRuntime.Reflection.ILRuntimeType)type;
var ilType = ilt.ILType;
var gargs = ilt.GenericTypeArguments;
}
else if (type is ILRuntime.Reflection.ILRuntimeWrapperType)
{
var clrttype = (ILRuntime.Reflection.ILRuntimeWrapperType)type;
var t = clrttype.GenericTypeArguments;
}
}

在ILRuntime中使用序列化库

序列化库的使用

1.ILRuntime集成了LitJson。
2.LitJson集成展示了如何修改序列化库以支持ILRuntime。
3.序列化库不认识热更类型。
4.热更类型不能通过Activator创建实例。

1
2
3
4
5
6
7
8
9
    void InitializeILRuntime()
{
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
//由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
//这里做一些ILRuntime的注册,这里我们对LitJson进行注册
LitJson.JsonMapper.RegisterILRuntimeCLRRedirection(appdomain);
}

序列化库的修改方法

1.正确创建热更类型的实例。
2.获取泛型容器类的真实热更类型。
3.序列化子对象。
4.重定向挟持泛型方法。

MonoBehaviour的使用

使用MonoBehaviour

1.不推荐。
2.跨域继承适配器。
3.在inspector中显示字段。
4.Unity序列化的尝试。

1
2
3
4
5
6
7
8
9
10
11
    unsafe void InitializeILRuntime()
{
#if DEBUG && (UNITY_EDITOR || UNITY_ANDROID || UNITY_IPHONE)
//由于Unity的Profiler接口只允许在主线程使用,为了避免出异常,需要告诉ILRuntime主线程的线程ID才能正确将函数运行耗时报告给Profiler
appdomain.UnityMainThreadID = System.Threading.Thread.CurrentThread.ManagedThreadId;
#endif
//这里做一些ILRuntime的注册
appdomain.RegisterCrossBindingAdaptor(new MonoBehaviourAdapter());
appdomain.RegisterValueTypeBinder(typeof(Vector3), new Vector3Binder());
//ILRuntime.Runtime.Generated.CLRBindings.Initialize(appdomain);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
unsafe void OnHotFixLoaded()
{
Debug.Log("在热更DLL里面使用MonoBehaviour是可以做到的,但是并不推荐这么做");
Debug.Log("因为即便能做到使用,要完全支持MonoBehaviour的所有特性,会需要很多额外的工作量");
Debug.Log("而且通过MonoBehaviour做游戏逻辑当项目规模大到一定程度之后会是个噩梦,因此应该尽量避免");

Debug.Log("直接调用GameObject.AddComponent<T>会报错,这是因为这个方法是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当中,要想实现就得靠大家自己了");
}

IL2CPP打包注意事项

IL2CPP注意事项

1.IL2CPP会强制开启代码剔除和剪裁。
2.被剪裁的方法被调用时会抛出AOT异常。
3.使用CLR自动分析绑定可最大化避免剪裁。
4.可适当通过link.xml预留接口和类型。
5.尤其需要注意泛型方法和泛型类型。

如何卸载AppDomain

卸载AppDomain

1.AppDomain是完整的沙箱。
2.可实例化多个AppDomain,互相不影响。
3.主工程持有的实例需要手动置空。
4.等待GC。

ILRuntime的限制

ILRuntime的限制

1.不支持unsafe,volatile,P/invoke等需要直接跟硬件和操作系统打交道的功能。
2.Nullable支持有限。
3.多维数组支持有限,且需要CLR绑定才可使用。
4.并非完全线程安全,不可多个线程执行同一段代码。
5.跨域继承的子类中,无法调用当前重载以外的虚函数。
6.跨域继承中,不能在基类的构造函数中调用该类的虚函数。