Xlua调用C#重载函数原理

在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的函数。以及如果重载函数的参数类型为一个委托或者函数,此时也可能无法调用到正确函数。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

张_0

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值