在取得DelegateBridge对象后,还需要通过getDelegate方法,获取delegateType类型的委托,即C#这边指定要接收Lua function时声明的委托类型。在本例中是typeof(TestXLua.Func)
Delegate getDelegate(DelegateBridgeBase bridge, Type delegateType) { // ... Func<DelegateBridgeBase, Delegate> delegateCreator; if (!delegateCreatorCache.TryGetValue(delegateType, out delegateCreator)) { // get by parameters MethodInfo delegateMethod = delegateType.GetMethod("Invoke"); // 生成代码为配置了 CSharpCallLua的委托 生成以__Gen_Delegate_Imp开头的方法 并添加到 DelegateBridge 类中 var methods = bridge.GetType().GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.DeclaredOnly).Where(m => !m.IsGenericMethodDefinition && (m.Name.StartsWith("__Gen_Delegate_Imp") || m.Name == "Action")).ToArray(); // 查找bridge中与delegateMethod匹配的方法,这个方法必须是以__Gen_Delegate_Imp或Action开头 for (int i = 0; i < methods.Length; i++) { if (!methods[i].IsConstructor && Utils.IsParamsMatch(delegateMethod, methods[i])) { var foundMethod = methods[i]; delegateCreator = (o) => #if !UNITY_WSA || UNITY_EDITOR Delegate.CreateDelegate(delegateType, o, foundMethod); // 创建表示foundMethod的delegateType类型的委托 #else foundMethod.CreateDelegate(delegateType, o); #endif break; } } if (delegateCreator == null) { delegateCreator = getCreatorUsingGeneric(bridge, delegateType, delegateMethod); } delegateCreatorCache.Add(delegateType, delegateCreator); } ret = delegateCreator(bridge); // 创建委托 if (ret != null) { return ret; } throw new InvalidCastException("This type must add to CSharpCallLua: " + delegateType.GetFriendlyName()); }如何利用bridge获取到指定类型delegateType的委托呢?主要逻辑是,先获得delegateType委托的Invoke方法,然后通过反射遍历bridge类型的所有方法,找到与Invoke参数匹配的目标方法。再使用bridge实例与目标方法创建一个delegateType类型的委托。换言之,这个delegateType类型的委托绑定的是bridge的与之参数匹配的成员方法,而且这个方法名称要以"__Gen_Delegate_Imp"开头
用于接收Lua function的委托必须添加CSharpCallLua特性也正是因为要为其生成以"__Gen_Delegate_Imp"开头的方法,如果不添加则会抛出异常
c# exception:System.InvalidCastException: This type must add to CSharpCallLua: TestXLua+Func添加CSharpCallLua特性后,点击Generate Code,会为该委托生成如下代码。虽然代码生成在DelegatesGensBridge.cs文件中,但它通过partial声明为DelegateBridge类的一部分。生成的函数名均以"__Gen_Delegate_Imp"开头,且参数类型和个数与该委托一致
// DelegatesGensBridge.cs public partial class DelegateBridge : DelegateBridgeBase { // ... public int __Gen_Delegate_Imp1(string p0, bool p1, float p2) { #if THREAD_SAFE || HOTFIX_ENABLE lock (luaEnv.luaEnvLock) { #endif RealStatePtr L = luaEnv.rawL; int errFunc = LuaAPI.pcall_prepare(L, errorFuncRef, luaReference); LuaAPI.lua_pushstring(L, p0); // 压栈参数 LuaAPI.lua_pushboolean(L, p1); // 压栈参数 LuaAPI.lua_pushnumber(L, p2); // 压栈参数 PCall(L, 3, 1, errFunc); // Lua function调用 int __gen_ret = LuaAPI.xlua_tointeger(L, errFunc + 1); LuaAPI.lua_settop(L, errFunc - 1); return __gen_ret; #if THREAD_SAFE || HOTFIX_ENABLE } #endif } }TestXLua.Func类型委托绑定的就是这个生成函数__Gen_Delegate_Imp1。之所以要使用生成函数,是因为需要生成函数来完成参数的压栈与Lua function调用
为了正确的和Lua通讯,C函数已经定义好了协议。这个协议定义了参数以及返回值传递方法:C函数通过Lua中的栈来接受参数,参数以正序入栈(第一个参数首先入栈)。因此,当函数开始的时候,lua_gettop(L)可以返回函数收到的参数个数。第一个参数(如果有的话)在索引1的地方,而最后一个参数在索引lua_gettop(L)处。当需要向Lua返回值的时候,C函数只需要把它们以正序压到堆栈上(第一个返回值最先压入),然后返回这些返回值的个数。在这些返回值之下的,堆栈上的东西都会被Lua丢掉。和Lua函数一样,从Lua中调用C函数可以有很多返回值。
文章开头也已提到,C#可以借助C/C++来与Lua进行数据通信,所以C#在函数调用前,需要通过C API来压栈函数调用所需的参数,而这个逻辑就被封装在了以"__Gen_Delegate_Imp"开头的生成方法中。生成方法将参数压栈后,再通过PCall调用Lua function,PCall内部调用的就是Lua原生API lua_pcall
总结一下整个流程
-- Lua测试代码 CS.TestXLua.func = function(s, b, i) end