上一篇写了怎么使用ILRuntime,这篇文章就来说使用FairyGUI的一些问题
只是我个人单纯遇到的一些问题,可能有点参错或是有其他方面的理解错误,谅解一下
开始
我直接随便创建一个fgui工程,创建个包“Main”,在里面创建一个组件“VMain”
导出到Resources文件夹下,代码导出到Hotfix层里面。
现在我们根据上一篇代码,在InitTest方法上面稍微修改一下
public static void InitTest () {
UnityEngine.Debug.Log ("Hello ILRuntime InitTest");
//--------------------------------新增---------------------------------
GRoot.inst.SetContentScaleFactor (1280, 720);
UIPackage.AddPackage ("Main");
Unity.Hotfix.Main.MainBinder.BindAll ();
Main.UI_VMain ui=Main.UI_VMain.CreateInstance();
GRoot.inst.AddChild (ui);
}
这个时候启动untiy 会报错找不到适配器
因为我们生成的 Main.UI_VMain是继承于主工程里面的GComponent,这就碰到了ILRuntime官方说的跨域继承的问题了。
我们创建一个适配器 GComponentAdaptor
using System;
using FairyGUI;
using ILRuntime.CLR.Method;
using ILRuntime.Runtime.Enviorment;
using ILRuntime.Runtime.Intepreter;
public class GComponentAdaptor : CrossBindingAdaptor {
public override Type BaseCLRType {
get {
//如果你是想一个类实现多个Unity主工程的接口,这里需要return null;
return typeof (GComponent); //这是你想继承的那个类
}
}
public override Type[] BaseCLRTypes {
get {
//跨域继承只能有1个Adapter,因此应该尽量避免一个类同时实现多个外部接口,
//ILRuntime虽然支持同时实现多个接口,但是一定要小心这种用法,使用不当很容易造成不可预期的问题
//日常开发如果需要实现多个DLL外部接口,请在Unity这边先做一个基类实现那些个接口,然后继承那个基类
//如需一个Adapter实现多个接口,请用下面这行
//return new Type[] { typeof(IEnumerator<object>), typeof(IEnumerator), typeof(IDisposable) };
return null;
}
}
public override Type AdaptorType {
get {
return typeof (Adaptor); //这是实际的适配器类
}
}
public override object CreateCLRInstance (ILRuntime.Runtime.Enviorment.AppDomain appdomain, ILTypeInstance instance) {
return new Adaptor (appdomain, instance); //创建一个新的实例
}
//实际的适配器类需要继承你想继承的那个类,并且实现CrossBindingAdaptorType接口
class Adaptor : GComponent, CrossBindingAdaptorType {
ILTypeInstance instance;
ILRuntime.Runtime.Enviorment.AppDomain appdomain;
IMethod mTestAbstract;
bool mTestAbstractGot;
IMethod mTestVirtual;
bool mTestVirtualGot;
bool isTestVirtualInvoking = 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; } }
}
}
回到GameMain
在OnILRuntimeInitialized函数调用前,注册这个适配器
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 ());
OnBindingAdaptor (appdomain);
OnILRuntimeInitialized ();
}
}
}
void OnBindingAdaptor (ILRuntime.Runtime.Enviorment.AppDomain appdomain) {
appdomain.RegisterCrossBindingAdaptor(new GComponentAdaptor());
}
void OnILRuntimeInitialized () {
appdomain.Invoke ("Unity.Hotfix.Init", "InitTest", null, null);
}
ok 注册完启动unity 会出现下一个问题 找不到构造函数。。。
这里是因为在Unity主工程中,是无法通过Activator来创建热更DLL内类型的实例,必须通过AppDomain来创建实例
而当我们调用UIPackage.CreateObject的时候,如果用了下面fgui自动生成的代码,那么就出现了问题,因为使用了Activator.CreateInstance(type)
Unity.Hotfix.Main.MainBinder.BindAll ();
public class MainBinder
{
public static void BindAll()
{
UIObjectFactory.SetPackageItemExtension(UI_VMain.URL, typeof(UI_VMain));
}
}
public static void SetPackageItemExtension(string url, System.Type type)
{
SetPackageItemExtension(url, () => { return (GComponent)Activator.CreateInstance(type); });
}
解决方法:
直接不用FairyGUI生成的绑定代码,而使用自己来绑定
我们在主工程的GameMain 增加一个静态方法,直接替换掉,使用appdomain.Instantiate方式创建实例
public static void OnBindFui (string url, string name) {
UIObjectFactory.SetPackageItemExtension (url, () => {
return Camera.main.GetComponent<GameMain>().appdomain.Instantiate<GComponent>(name);
});
}
在Hotfix工程里面的Init 把
Unity.Hotfix.Main.MainBinder.BindAll ();
替换成OnBindFui
public static void InitTest () {
UnityEngine.Debug.Log ("Hello ILRuntime InitTest");
GRoot.inst.SetContentScaleFactor (1280, 720);
UIPackage.AddPackage ("Main");
// Unity.Hotfix.Main.MainBinder.BindAll ();
GameMain.OnBindFui(Main.UI_VMain.URL,typeof(Main.UI_VMain).FullName);
Main.UI_VMain ui=Main.UI_VMain.CreateInstance();
GRoot.inst.AddChild (ui);
}
这样就O了,不过这样的话就需要我们每次都去自己创建绑定代码,还是有点而蛋疼的。
下面使用第二种方法,还是使用fgui生成的代码,但是把SetPackageItemExtension方法进行重定向
我们在主工程的里面 加多两个方法来注册下方法重定向
unsafe void CLRMethod () {
BindingFlags flag = BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly;
MethodBase method;
Type[] args;
Type type = typeof (FairyGUI.UIObjectFactory);
args = new Type[] { typeof (System.String), typeof (System.Type) };
method = type.GetMethod ("SetPackageItemExtension", flag, null, args, null);
appdomain.RegisterCLRMethodRedirection (method, SetPackageItemExtension_0);
}
unsafe static StackObject * SetPackageItemExtension_0 (ILIntepreter __intp, StackObject * __esp, IList<object> __mStack, CLRMethod __method, bool isNewObj) {
ILRuntime.Runtime.Enviorment.AppDomain __domain = __intp.AppDomain;
StackObject * ptr_of_this_method;
StackObject * __ret = ILIntepreter.Minus (__esp, 2);
ptr_of_this_method = ILIntepreter.Minus (__esp, 1);
System.Type @type = (System.Type) typeof (System.Type).CheckCLRTypes (StackObject.ToObject (ptr_of_this_method, __domain, __mStack));
__intp.Free (ptr_of_this_method);
ptr_of_this_method = ILIntepreter.Minus (__esp, 2);
System.String @url = (System.String) typeof (System.String).CheckCLRTypes (StackObject.ToObject (ptr_of_this_method, __domain, __mStack));
__intp.Free (ptr_of_this_method);
FairyGUI.UIObjectFactory.SetPackageItemExtension (@url, () => {
return __domain.Instantiate<GComponent> (@type.FullName);
});
// FairyGUI.UIObjectFactory.SetPackageItemExtension (@url, @type);
return __ret;
}
在加载完dll后注册这个方法;
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 ());
CLRMethod ();
OnBindingAdaptor ();
OnILRuntimeInitialized ();
}
}