做网站的五要素,济南网站建设方案报价,来年做哪些网站致富,两学一做山东网站xlua源码分析#xff08;三#xff09;C#访问lua的映射 上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过#xff0c;C#使用LuaTable类持有lua层的table#xff0c;以及使用Action委托持有lua层的function。而在xlua的官方文档中#xff0c;推荐使… xlua源码分析三C#访问lua的映射 上一节我们主要分析了lua call C#的无wrap实现。同时我们在第一节里提到过C#使用LuaTable类持有lua层的table以及使用Action委托持有lua层的function。而在xlua的官方文档中推荐使用interface和delegate访问lua层数据结构 映射到一个interface 这种方式依赖于生成代码如果没生成代码会抛InvalidCastException异常代码生成器会生成这个interface的实例如果get一个属性生成代码会get对应的table字段如果set属性也会设置对应的字段。甚至可以通过interface的方法访问lua的函数。 映射到delegate 这种是建议的方式性能好很多而且类型安全。缺点是要生成代码如果没生成代码会抛InvalidCastException异常。 delegate要怎样声明呢 对于function的每个参数就声明一个输入类型的参数。 多返回值要怎么处理从左往右映射到c#的输出参数输出参数包括返回值out参数ref参数。 参数、返回值类型支持哪些呢都支持各种复杂类型outref修饰的甚至可以返回另外一个delegate。 delegate的使用就更简单了直接像个函数那样用就可以了。 那么这一节我们就对照着Examples 04_LuaObjectOrented来看一下如何把包含任意数据的lua table和包含任意参数的lua function映射到C#让C#可以直接访问。
首先看一下例子中用到的lua代码
local calc_mt {__index {Add function(self, a, b)return (a b) * self.Multend,get_Item function(self, index)return self.list[index 1]end,set_Item function(self, index, value)self.list[index 1] valueself:notify({name index, value value})end,add_PropertyChanged function(self, delegate)if self.notifylist nil thenself.notifylist {}endtable.insert(self.notifylist, delegate)print(add,delegate)end,remove_PropertyChanged function(self, delegate)for i1, #self.notifylist doif CS.System.Object.Equals(self.notifylist[i], delegate) thentable.remove(self.notifylist, i)breakendendprint(remove, delegate)end,notify function(self, evt)if self.notifylist ~ nil thenfor i1, #self.notifylist doself.notifylist[i](self, evt)endend end,}
}Calc {New function (mult, ...)print(...)return setmetatable({Mult mult, list {aaaa,bbbb,cccc}}, calc_mt)end
}这个例子很简单就是定义了一个Calc.New的函数这个函数会使用传入的参数构建一个新的table并设置calc_mt作为它的metatable。calc_mt的__index表中定义了若干供C#访问的函数如Addget_Itemset_Itemadd_PropertyChanged和remove_PropertyChanged。
回到C#C#层如果想要访问lua层的Calc.New就需要定义一个和该函数匹配的委托。这个委托定义如下
[CSharpCallLua]
public delegate ICalc CalcNew(int mult, params string[] args);委托有一个int类型的参数mult和不定数量的string类型参数argsint和string类型都可以很容易地从C#类型转换到对应的lua类型。再看返回值这里的返回类型是一个ICalc的interface它其实映射就是lua层的table也就是Calc.New所返回的那个table。为了让xlua识别CalcNew这个委托类型是用来映射lua函数的也就是要使用这个委托调用lua层函数需要给CalcNew类型打上CSharpCallLua的标签这样xlua就会生成代码来完成这一工作。
映射lua table的ICalc定义如下
[CSharpCallLua]
public interface ICalc
{event EventHandlerPropertyChangedEventArgs PropertyChanged;int Add(int a, int b);int Mult { get; set; }object this[int index] { get; set; }
}接口类中包含了一个PropertyChanged的event一个Add方法一个Multi属性还实现了下标操作符。那么想必大家都能猜出来这里就是分别对应了lua层calc_mt的__index表中定义的若干函数。同样地我们也需要为这个interface打上[CSharpCallLua]标签这样xlua就会生成一个具体实现该接口的类。
在理解映射思路之后我们再看下测试代码
void Test(LuaEnv luaenv)
{luaenv.DoString(script);CalcNew calc_new luaenv.Global.GetInPathCalcNew(Calc.New);ICalc calc calc_new(10, hi, john); //constructorDebug.Log(sum(*10) calc.Add(1, 2));calc.Mult 100;Debug.Log(sum(*100) calc.Add(1, 2));Debug.Log(list[0] calc[0]);Debug.Log(list[1] calc[1]);calc.PropertyChanged Notify;calc[1] dddd;Debug.Log(list[1] calc[1]);calc.PropertyChanged - Notify;calc[1] eeee;Debug.Log(list[1] calc[1]);
}void Notify(object sender, PropertyChangedEventArgs e)
{Debug.Log(string.Format({0} has property changed {1}{2}, sender, e.name, e.value));
}运行之后输出结果如下 可以看到我们通过映射的方式访问到了lua的函数和table而且很重要的一点是测试代码中C#和lua实现了解耦这种做法也是xlua的官方文档中所推荐的 使用建议 访问lua全局数据特别是table以及function代价比较大建议尽量少做比如在初始化时把要调用的lua function获取一次映射到delegate后保存下来后续直接调用该delegate即可。table也类似。如果lua侧的实现的部分都以delegate和interface的方式提供使用方可以完全和xLua解耦由一个专门的模块负责xlua的初始化以及delegate、interface的映射然后把这些delegate和interface设置到要用到它们的地方。 那么现在我们开始跟着测试代码一步步地研究背后的实现吧。
第一步就是调用了GetInPath通过变量的名称获取到lua函数再将其转换为CalcNew委托类型
public T GetInPathT(string path)
{
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifvar L luaEnv.L;var translator luaEnv.translator;int oldTop LuaAPI.lua_gettop(L);LuaAPI.lua_getref(L, luaReference);if (0 ! LuaAPI.xlua_pgettable_bypath(L, -1, path)){luaEnv.ThrowExceptionFromError(oldTop);}LuaTypes lua_type LuaAPI.lua_type(L, -1);if (lua_type LuaTypes.LUA_TNIL typeof(T).IsValueType()){throw new InvalidCastException(can not assign nil to typeof(T).GetFriendlyName());}T value;try{translator.Get(L, -1, out value);}catch (Exception e){throw e;}finally{LuaAPI.lua_settop(L, oldTop);}return value;
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif
}重点需要关注的其实就是这句translator.Get(L, -1, out value);它负责对lua栈上的函数进行类型转换。这个委托类型并不是实现注册好的类型那么就会走到通用的GetObject函数
public void GetT(RealStatePtr L, int index, out T v)
{FuncRealStatePtr, int, T get_func;if (tryGetGetFuncByType(typeof(T), out get_func)){v get_func(L, index);}else{v (T)GetObject(L, index, typeof(T));}
}这个GetObject函数我们在前面的章节中也分析过对于不是userdata的lua对象它会寻找一个caster函数进行转换如果找不到则会通过一系列规则生成一个caster
public ObjectCast GetCaster(Type type)
{if (type.IsByRef) type type.GetElementType();Type underlyingType Nullable.GetUnderlyingType(type);if (underlyingType ! null){return genNullableCaster(GetCaster(underlyingType)); }ObjectCast oc;if (!castersMap.TryGetValue(type, out oc)){oc genCaster(type);castersMap.Add(type, oc);}return oc;
}这里的委托类型是我们自定义的默认的castersMap中显然不包含那么xlua就会为我们生成一个
ObjectCast fixTypeGetter (RealStatePtr L, int idx, object target)
{if (LuaAPI.lua_type(L, idx) LuaTypes.LUA_TUSERDATA){object obj translator.SafeGetCSObj(L, idx);return (obj ! null type.IsAssignableFrom(obj.GetType())) ? obj : null;}return null;
}; if (typeof(Delegate).IsAssignableFrom(type))
{return (RealStatePtr L, int idx, object target) {object obj fixTypeGetter(L, idx, target);if (obj ! null) return obj;if (!LuaAPI.lua_isfunction(L, idx)){return null;}return translator.CreateDelegateBridge(L, type, idx);};
}这里的关键也是在translator.CreateDelegateBridge这句这个函数之前我们也分析过它负责生成一个DelegateBridge对象。这个对象就是指代lua函数用的它自身可以与多个C#的委托绑定。
bridge new DelegateBridge(reference, luaEnv);
try {var ret getDelegate(bridge, delegateType);bridge.AddDelegate(delegateType, ret);delegate_bridges[reference] new WeakReference(bridge);return ret;
}
catch(Exception e)
{bridge.Dispose();throw e;
}getDelegate这个函数会根据传入的delegateType调用DelegateBridgeBase.GetDelegateByType生成对应类型的Delegate对象它是个virtual方法我们在生成代码之后就会产生继承自它的DelegateBridge.GetDelegateByTypeoverride方法这段生成代码位于DelegatesGenBridge.cs这个文件里
public partial class DelegateBridge : DelegateBridgeBase
{public override Delegate GetDelegateByType(Type type){if (type typeof(System.Action)){return new System.Action(__Gen_Delegate_Imp0);}if (type typeof(UnityEngine.Events.UnityAction)){return new UnityEngine.Events.UnityAction(__Gen_Delegate_Imp0);}if (type typeof(System.Funcdouble, double, double)){return new System.Funcdouble, double, double(__Gen_Delegate_Imp1);}if (type typeof(System.Actionstring)){return new System.Actionstring(__Gen_Delegate_Imp2);}if (type typeof(System.Actiondouble)){return new System.Actiondouble(__Gen_Delegate_Imp3);}if (type typeof(XLuaTest.IntParam)){return new XLuaTest.IntParam(__Gen_Delegate_Imp4);}if (type typeof(XLuaTest.Vector3Param)){return new XLuaTest.Vector3Param(__Gen_Delegate_Imp5);}if (type typeof(XLuaTest.CustomValueTypeParam)){return new XLuaTest.CustomValueTypeParam(__Gen_Delegate_Imp6);}if (type typeof(XLuaTest.EnumParam)){return new XLuaTest.EnumParam(__Gen_Delegate_Imp7);}if (type typeof(XLuaTest.DecimalParam)){return new XLuaTest.DecimalParam(__Gen_Delegate_Imp8);}if (type typeof(XLuaTest.ArrayAccess)){return new XLuaTest.ArrayAccess(__Gen_Delegate_Imp9);}if (type typeof(System.Actionbool)){return new System.Actionbool(__Gen_Delegate_Imp10);}if (type typeof(Tutorial.CSCallLua.FDelegate)){return new Tutorial.CSCallLua.FDelegate(__Gen_Delegate_Imp11);}if (type typeof(Tutorial.CSCallLua.GetE)){return new Tutorial.CSCallLua.GetE(__Gen_Delegate_Imp12);}if (type typeof(XLuaTest.InvokeLua.CalcNew)){return new XLuaTest.InvokeLua.CalcNew(__Gen_Delegate_Imp13);}return null;}
}得到Delegate之后这里会将其进行缓存这样下次遇到相同类型直接取出该委托即可。DelegateBridgeBase类缓存Delegate的数据结构比较有意思它有一对firstKey和firstValue然后一个DictionaryType, Delegate的字典所组成缓存时会优先将数据保存到firstKey和firstValue上这样取出的时候就无需对字典进行查找查找效率更高。
public bool TryGetDelegate(Type key, out Delegate value)
{if(key firstKey){value firstValue;return true;}if (bindTo ! null){return bindTo.TryGetValue(key, out value);}value null;return false;
}public void AddDelegate(Type key, Delegate value)
{if (key firstKey){throw new ArgumentException(An element with the same key already exists in the dictionary.);}if (firstKey null bindTo null) // nothing {firstKey key;firstValue value;}else if (firstKey ! null bindTo null) // one key existed{bindTo new DictionaryType, Delegate();bindTo.Add(firstKey, firstValue);firstKey null;firstValue null;bindTo.Add(key, value);}else{bindTo.Add(key, value);}
}就这样这个新生成的委托经过辗转终于返回到了测试代码也就是calc_new对象那么我们就可以直接通过委托的方式调用它此时就会触发生成的__Gen_Delegate_Imp13函数了我们来看看生成的代码长什么样
public XLuaTest.InvokeLua.ICalc __Gen_Delegate_Imp13(int p0, string[] p1)
{
#if THREAD_SAFE || HOTFIX_ENABLElock (luaEnv.luaEnvLock){
#endifRealStatePtr L luaEnv.rawL;int errFunc LuaAPI.pcall_prepare(L, errorFuncRef, luaReference);ObjectTranslator translator luaEnv.translator;LuaAPI.xlua_pushinteger(L, p0);if (p1 ! null) { for (int __gen_i 0; __gen_i p1.Length; __gen_i) LuaAPI.lua_pushstring(L, p1[__gen_i]); };PCall(L, 1 (p1 null ? 0 : p1.Length), 1, errFunc);XLuaTest.InvokeLua.ICalc __gen_ret (XLuaTest.InvokeLua.ICalc)translator.GetObject(L, errFunc 1, typeof(XLuaTest.InvokeLua.ICalc));LuaAPI.lua_settop(L, errFunc - 1);return __gen_ret;
#if THREAD_SAFE || HOTFIX_ENABLE}
#endif
}代码逻辑很简单就是准备调用环境然后把C#的参数push到lua层然后pcall调用然后从lua栈中取出返回的结果由于lua是弱类型的无法事先知道返回值的类型所以这里只能使用通用的GetObject函数对lua的返回值进行类型转换。
同样ICalc类型是我们自定义的默认的castersMap是不包含的也需要生成一个caster
return (RealStatePtr L, int idx, object target)
{object obj fixTypeGetter(L, idx, target);if (obj ! null) return obj;if (!LuaAPI.lua_istable(L, idx)){return null;}return translator.CreateInterfaceBridge(L, type, idx);
};那么这里的关键就是在translator.CreateInterfaceBridge上了与委托非常类似这里会根据interface的类型寻找负责生成interface对象的函数
public object CreateInterfaceBridge(RealStatePtr L, Type interfaceType, int idx)
{Funcint, LuaEnv, LuaBase creator;if (!interfaceBridgeCreators.TryGetValue(interfaceType, out creator)){
#if (UNITY_EDITOR || XLUA_GENERAL) !NET_STANDARD_2_0var bridgeType ce.EmitInterfaceImpl(interfaceType);creator (int reference, LuaEnv luaenv) {return Activator.CreateInstance(bridgeType, new object[] { reference, luaEnv }) as LuaBase;};interfaceBridgeCreators.Add(interfaceType, creator);
#elsethrow new InvalidCastException(This type must add to CSharpCallLua: interfaceType);
#endif}LuaAPI.lua_pushvalue(L, idx);return creator(LuaAPI.luaL_ref(L), luaEnv);
}往interfaceBridgeCreators注册creator的逻辑就是在生成代码中完成的位于XLuaGenAutoRegister.cs中
static void Init(LuaEnv luaenv, ObjectTranslator translator)
{wrapInit0(luaenv, translator);translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.IExchanger), XLuaTestIExchangerBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(Tutorial.CSCallLua.ItfD), TutorialCSCallLuaItfDBridge.__Create);translator.AddInterfaceBridgeCreator(typeof(XLuaTest.InvokeLua.ICalc), XLuaTestInvokeLuaICalcBridge.__Create);}XLuaTestInvokeLuaICalcBridge是继承自ICalc接口的类它负责实现ICalc的功能也就是我们一开始提到的一个PropertyChanged的event 和-操作一个Add方法一个Multi属性以及下标操作符。__Create方法就是简单了返回了一个XLuaTestInvokeLuaICalcBridge对象
public class XLuaTestInvokeLuaICalcBridge : LuaBase, XLuaTest.InvokeLua.ICalc
{public static LuaBase __Create(int reference, LuaEnv luaenv){return new XLuaTestInvokeLuaICalcBridge(reference, luaenv);}
}有了ICalc对象后我们再次回到例子中例子中接下来调用了Add方法与Multi的set属性XLuaTestInvokeLuaICalcBridge类对它们的实现都比较简单这里就不再赘述了。接下来是下标访问对于get来说会去尝试访问lua层的get_item函数而对于set来说则会去访问lua层的set_item函数。例子里还往PropertyChanged事件中注册了一个Notify方法这时则会触发lua层的add_PropertyChanged函数把C#的Notify方法push到lua层。
上一节我们提到把C#对象push到lua层时会调用到xlua的getTypeId方法用来获取表示对象类的唯一ID对于Notify方法来说它就是一个委托而委托实质上使用的是同一个type id
if (typeof(MulticastDelegate).IsAssignableFrom(type))
{if (common_delegate_meta -1) throw new Exception(Fatal Exception! Delegate Metatable not inited!);TryDelayWrapLoader(L, type);return common_delegate_meta;
}TryDelayWrapLoader我们上一节分析过这里就不展开了由于没有wrap还是通过反射生成类的各种table。最终lua层缓存了一个表示C# Notify方法的userdata。
此时再对table进行set_item就会触发Notify方法调用了对于delegate来说xlua在初始化时就往metatable里设置了__call元方法
public void CreateDelegateMetatable(RealStatePtr L)
{Utils.BeginObjectRegister(null, L, this, 3, 0, 0, 0, common_delegate_meta);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, __call, StaticLuaCallbacks.DelegateCall);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, __add, StaticLuaCallbacks.DelegateCombine);Utils.RegisterFunc(L, Utils.OBJ_META_IDX, __sub, StaticLuaCallbacks.DelegateRemove);Utils.EndObjectRegister(null, L, this, null, null,typeof(System.MulticastDelegate), null, null);
}[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int DelegateCall(RealStatePtr L)
{try{ObjectTranslator translator ObjectTranslatorPool.Instance.Find(L);object objDelegate translator.FastGetCSObj(L, 1);if (objDelegate null || !(objDelegate is Delegate)){return LuaAPI.luaL_error(L, trying to invoke a value that is not delegate nor callable);}return translator.methodWrapsCache.GetDelegateWrap(objDelegate.GetType())(L);}catch (Exception e){return LuaAPI.luaL_error(L, c# exception in DelegateCall: e);}
}GetDelegateWrap方法就是根据委托的类型反射取出它的Inovke方法然后包装到MethodWrap的Call方法中进行最终的反射调用。