一 概述 NativeChannel提供Lua层通过反射调用C#层代码的机制,支持对静态类和非静态类中属性、方法、委托的调用,并支持在Lua层使用宏定义对代码进行隔离。通过NativeChannel可以不生成额外的wrap文件来达到Lua层访问C#层的效果,调用更加灵活,适合需要集成到Lua层但又要灵活增删的模块(如SDK)。
二 实现 实现的大致思路为封装两个类AnyObject
和AnyStaticObject
暴露到Lua层,这两个类都实现IAny
接口。这两个类包含对具体类中的生命周期、属性、方法、委托的所有操作。再定义通道类NativeChannel
,实现INativeChannel和IServiceStart
接口,包装成Service管理所有反射调用的类。
Object
AnyObject
和AnyStaticObject
都实现了IAny
接口,它们之间的区别是前者储存了目标类实例的引用target
,在使用时调用属性、方法需要传入该引用;而后者储存的是静态类的类型。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public interface IAny { void OnStart ( ) ; void OnUpdate (float deltaTime ) ; void OnEnd ( ) ; object Get (string prop ) ; void Set (string prop, object value ) ; object Call (string method, params object [] args ) ; void AddDelegate (string del, string func, LuaTable self = null ) ; void AddDelegate (string del, LuaFunction func ) ; void RemoveDelegate (string del, string func, LuaTable self = null ) ; void RemoveDelegate (string del, LuaFunction func ) ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 public void OnStart ( ) { var method = this .target.GetType().GetMethod("OnStart" ); method?.Invoke(this .target, null ); } public void OnUpdate (float deltaTime ) { var method = this .target.GetType().GetMethod("OnRefresh" ); Object[] p = new Object[] {deltaTime}; method?.Invoke(this .target, p); } public void OnEnd ( ) { var method = this .target.GetType().GetMethod("OnEnd" ); method?.Invoke(this .target, null ); }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public object Get (string prop ) { return this .target.GetType().GetProperty(prop)?.GetValue(this .target); } public void Set (string prop, object value ) { var propInfo = this .target.GetType().GetProperty(prop); if (propInfo != null ) { value = ConvertType(propInfo.PropertyType, value ); propInfo?.SetValue(this .target, value ); } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public object Call (string method, params object [] args ) { var methodInfo = this .target.GetType().GetMethod(method); var parameters = methodInfo?.GetParameters(); if (args.Length != parameters?.Length) { Debug.LogError("Wrong parameters!" ); return null ; } for (int i = 0 ; i < parameters.Length; i++) { args[i] = ConvertType(parameters[i].ParameterType, args[i]); } return methodInfo?.Invoke(this .target, args); }
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 36 37 38 39 40 41 public void AddDelegate (string del, string func, LuaTable self = null ) { var eventInfo = this .target.GetType().GetEvent(del); var eventHandlerType = eventInfo.EventHandlerType; var eventArgs = eventHandlerType.GetMethod("Invoke" )?.GetParameters(); Type type; if (eventArgs == null ) { type = null ; eventHandlerType = null ; return ; } var args = new Type[eventArgs.Length]; for (int i = 0 ; i < eventArgs.Length; i++) { args[i] = eventArgs[i].ParameterType; } switch (eventArgs.Length) { case 0 : type = typeof (Handler); break ; case 1 : type = typeof (Handler<>).MakeGenericType(args); break ; default : type = null ; break ; } if (type == null || eventHandlerType == null ) { return ; } var helper = Activator.CreateInstance(type, func, self); var handler = Delegate.CreateDelegate(eventHandlerType, helper, "Handle" ); eventInfo.AddEventHandler(this .target, handler); }
NativeChannel 1 2 3 4 5 6 7 8 9 10 11 [LuaUsage ] public interface INativeChannel { [LuaIgnore ] void Register (string name, Func<object > factory ) ; IAny Get (string name ) ; [LuaIgnore ] void Define (string macro ) ; }
在NativeChannel
中维护一个字典factories
存储所有反射调用的类,维护一个列表macros
存储所有在Lua层需要添加的宏定义。
提供在C#层注册Object的方法
1 2 3 4 public void Register (string name, Func<object > factory ) { this .factories.Add(name, () => new AnyObject(factory())); }
暴露到Lua层,供Lua层通过类名获取对应的引用,若字典中存在该类型则直接返回,若不存在则创建对应的Object。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 public IAny Get (string name ) { if (this .factories.TryGetValue(name, out var factory)) { return factory(); } var type = Type.GetType(name); if (type == null ) return null ; if (type.IsAbstract && type.IsSealed) { return new AnyStaticObject(type); } else { var obj = Activator.CreateInstance(type); if (obj == null ) return null ; return new AnyObject(obj); } }
在C#层中添加宏定义。
三 问题与解决方法 类型转换 对于属性的赋值和方法的参数,Lua层传递过来的数据在C#中为Object类型,需要根据目标类型进行显式的转换。
1 2 3 4 5 6 7 8 private object ConvertType (Type type, object value ) { if (type == typeof (short )) { return Convert.ToInt16(value ); } }
委托的调用
为了能正确调用委托,需要确定委托的参数和类型,因此需要特别封装各种参数数量的泛型类Handler
,每种Handler
类包含构造方法Handler()
和触发回调方法的函数Handle()
。
在委托添加方法时,先获取方法所有的参数,根据参数个数和每个参数的类型确定Handler
的类型:type = typeof(Handler<>).MakeGenericType(args);
。
接着使用var helper = Activator.CreateInstance(type, func, self);
构造实例。
最后绑定方法
var handler = Delegate.CreateDelegate(eventHandlerType, helper, "Handle");
eventInfo.AddEventHandler(this.target, handler);
至此,当委托触发时,就能调用到对应Handler
类中的Handle()
方法,通过该方法再触发Lua层对应的方法,实现动态的绑定调用。
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 public class Handler <T1 , T2 >{ private readonly string funcName; private readonly LuaTable self; private readonly LuaFunction func; public Handler (string funcName, LuaTable self = null ) { this .funcName = funcName; this .self = self; } public Handler (LuaFunction func ) { this .func = func; } public void Handle (T1 a, T2 b ) { if (this .func == null ) { if (self == null ) return ; if (!(self[funcName] is LuaFunction f)) return ; f.Call(self, a, b); } else { func.Call(a, b); } } }
IL2CPP裁剪代码问题
1 2 3 4 5 6 7 8 9 10 [AttributeUsage(AttributeTargets.Delegate) ] public class NativeHandle : Attribute { public NativeHandle (Type type ) { this .Type = type; } public Type Type { get ; private set ; } = null ; }
1 2 [NativeHandle(typeof(Handler<int>)) ] public delegate void InitCompleteHandler (int retCode ) ;