在lua中调用C#函数调用,如调用B.Load(),我们都知道lua是无法支持函数重载的,但load函数又只是一个函数地址,所以我推测在c#中传入lua的函数地址,并不是真正的该函数的地址,而是在中间封装了一层,通过判断参数来调用不同的重载函数。事实证明,这个推测确实是正确的。
推测过程如下:
Utils.makeReflectionWrap
foreach (var kv in pending_methods) //遍历C#某个类所有的函数
{
if (kv.Key.Name.StartsWith("op_")) // 操作符
{
LuaAPI.xlua_pushasciistring(L, InternalGlobals.supportOp[kv.Key.Name]);
translator.PushFixCSFunction(L,
new LuaCSFunction(translator.methodWrapsCache._GenMethodWrap(type, kv.Key.Name, kv.Value.ToArray()).Call));
LuaAPI.lua_rawset(L, obj_meta);
}
else
{
LuaAPI.xlua_pushasciistring(L, kv.Key.Name);
//将c#函数数组封装一个类MethodWrap,并将该类的Call方法作为lua中调用的函数地址。kv.Value是一个函数list,若是重载函数则count > 1
translator.PushFixCSFunction(L,new LuaCSFunction(translator.methodWrapsCache._GenMethodWrap(type, kv.Key.Name, kv.Value.ToArray()).Call));
LuaAPI.lua_rawset(L, kv.Key.IsStatic ? cls_field : obj_field);
}
}
translator.methodWrapsCache._GenMethodWrap将c#函数生成warp封装一层
public MethodWrap _GenMethodWrap(Type type, string methodName, IEnumerable<MemberInfo> methodBases, bool forceCheck = false)
{
List<OverloadMethodWrap> overloads = new List<OverloadMethodWrap>();
foreach(var methodBase in methodBases)
{
var mb = methodBase as MethodBase;
if (mb == null)
continue;
if (mb.IsGenericMethodDefinition
#if !ENABLE_IL2CPP
&& !tryMakeGenericMethod(ref mb)
#endif
)
continue;
//初始化OverloadMethodWrap。初始化函数的参数个数类型等,在调用时判断是否匹配
var overload = new OverloadMethodWrap(translator, type, mb);
overload.Init(objCheckers, objCasters);
overloads.Add(overload);
}
//生成warp
return new MethodWrap(methodName, overloads, forceCheck);
}
我们着重看下MethodWrap类的Call方法,主要是通过判断lua调用时传入的参数来选择调用不同的c#重载函数
public MethodWrap(string methodName, List<OverloadMethodWrap> overloads, bool forceCheck)
{
this.methodName = methodName;
this.overloads = overloads;
this.forceCheck = forceCheck;
}
public int Call(RealStatePtr L)
{
if (overloads.Count == 1 && !overloads[0].HasDefalutValue && !forceCheck)
return overloads[0].Call(L);
for (int i = 0; i < overloads.Count; ++i)//遍历c#的所有重载函数
{
var overload = overloads[i];
if (overload.Check(L))//调用OverloadMethodWrap.check 判断lua中传入参数与当前c#函数是否一直
{
return overload.Call(L);
}
}
return LuaAPI.luaL_error(L, "invalid arguments to " + methodName);
}
}
我们再来看看check函数
public bool Check(RealStatePtr L)
{
int luaTop = LuaAPI.lua_gettop(L);//获取栈中有多少个元素
int luaStackPos = luaStackPosStart;//默认从2开始,因为lua的index从1开始。其次栈的第一个位置为函数地址,也就是上面MethodWrap.Call的地址,所有第一个参数的pos为2
//checkArray 在创建OverloadMethodWrap成员,init时候已经初始化。存储了c#函数的函数参数个数和类型
for(int i = 0; i < checkArray.Length; i++)
{
if ((i == (checkArray.Length - 1)) && (paramsType != null))
{
break;
}
if(luaStackPos > luaTop && !isOptionalArray[i])
{
return false;
}
//checkArray[i](L, luaStackPos)为重点,检查第i+1个参数和lua栈中luaStackPos位置的参数类型是否匹配(i = 0,luaStackPos = 2)上述已解释原因
else if(luaStackPos <= luaTop && !checkArray[i](L, luaStackPos))
{
return false;
}
if (luaStackPos <= luaTop || !isOptionalArray[i])
{
luaStackPos++; //判断下一个元素
}
}
return paramsType != null ? (luaStackPos < luaTop + 1 ?
checkArray[checkArray.Length - 1](L, luaStackPos) : true) : luaStackPos == luaTop + 1;
}
我们再来看看判断lua与C#类型是否相同的checkArray函数,checkArray函数通过ObjectCheck.genChecker(Type type)生成。
简单研究了下几个判断。
//通用判断 //是否可以强转,可以的话就判断为相同类型
ObjectCheck fixTypeCheck = (RealStatePtr L, int idx) =>
{
if (LuaAPI.lua_type(L, idx) == LuaTypes.LUA_TUSERDATA)
{
object obj = translator.SafeGetCSObj(L, idx);
if (obj != null)
{
return type.IsAssignableFrom(obj.GetType());
}
else
{
Type type_of_obj = translator.GetTypeOf(L, idx);
if (type_of_obj != null)
return type.IsAssignableFrom(type_of_obj);
}
}
return false;
};
//类的判断
if ((type.IsClass() && type.GetConstructor(System.Type.EmptyTypes) != null)) //class has default construtor
{
return (RealStatePtr L, int idx) =>
{
return LuaAPI.lua_isnil(L, idx) || LuaAPI.lua_istable(L, idx) || fixTypeCheck(L, idx);
};
}
//再来看看枚举类型
if (type.IsEnum())
{
return fixTypeCheck;
}
//看看委托类型
if (!type.IsAbstract() && typeof(Delegate).IsAssignableFrom(type))
{
return (RealStatePtr L, int idx) =>
{
var a = LuaAPI.lua_isnil(L, idx);
var b = LuaAPI.lua_isfunction(L, idx);
var c = fixTypeCheck(L, idx);
return LuaAPI.lua_isnil(L, idx) || LuaAPI.lua_isfunction(L, idx) || fixTypeCheck(L, idx);
};
}
通过分析发现在判断时,还是无法做到非常严谨,比如在c#中重载函数中,参数个数一样,一个函数参数为Vector2,另一个函数参数为Vector3,此时因为在判断类时候使用了LuaAPI.lua_istable,若此时lua侧直接传入为{x=1,y=2},此时可能开发者想调用参数为vector2的函数,但却调用到了参数为Vector3的函数。以及如果重载函数的参数类型为一个委托或者函数,此时也可能无法调用到正确函数。