XLua源码学习:Lua中调用CS

        在使用lua代码开发的过程中,一个非常重要的部分是对C#代码的调用,接下来就结合源码分析理解它的实现:

        在lua中,使用诸如以下代码便可以调用C#的代码:

CS.UnityEngine.Debug.Log('hello world')

        CS是一个全局的Table,所以CS.UnityEngine可以当做是在一个名为CS的Table中查询名为UnityEngine的值。在调动虚拟机也就是LuaEnv的创建时,会调用以下代码对CS表进行初始化:

DoString(init_xlua, "Init");

        初始化代码部分截取如下:

local metatable = {}
local rawget = rawget
local setmetatable = setmetatable
local import_type = xlua.import_type
local import_generic_type = xlua.import_generic_type
local load_assembly = xlua.load_assembly

function metatable:__index(key)
     --获取key为".fqn"的值
    local fqn = rawget(self, ".fqn")
    --拼接".fqn"的值和本次调用的key
    fqn = ((fqn and fqn .. ".") or "") .. key
    --查询CS类型
    local obj = import_type(fqn)

    if obj == nil then
        -- It might be an assembly, so we load it too.
        obj = {[".fqn"] = fqn}
        setmetatable(obj, metatable)
    elseif obj == true then
        return rawget(self, key)
    end

    -- Cache this lookup
    rawset(self, key, obj)
    return obj
end

CS = CS or {}
setmetatable(CS, metatable)

        这部分描述了CS表中__index元方法的实现,在我们调用CS表中一个不存在的字段时便会调用这个函数。

        我们用CS.UnityEngine.Debug为例来解释这个函数是怎么执行的:

        1)首先CS为一个全局的空表,访问CS.UnityEngine由于UnityEngine字段不存在而直接调用到此函数。第一行获取key为".fqn"的值,fqn为空,所以第二行结束后fqn=UnityEngine

        2)调用import_type去查询UnityEngine这个类型对应的lua表。

        3)对返回值obj进行判断,UnityEngine很明显不是一个类型所以走==nil的分支,创建一个表并且设置".fqn"="UnityEngine",并且将此metatable也设置为这个表的元表

        4)将CS表中将obj这个表设置给“UnityEngine”。

        5)接下来访问CS.UnityEngine.Debug就相当于从CS.UnityEngine中访问字段Debug,由于没有Debug这个字段所以也会调用到__index这个函数中,这时候fqn就有值了,第二行结束后fqn的值为“UnityEngine.Debug”

        6)这时候再通过import_type函数去查询这个类型就有值了,它会走obj==true这个分支,而在这个import_type函数中实际上已经将CS.UnityEngine.Debug的值给设置好了,这时候通过rawget函数来拿到这个lua表返回

        以上应该很清晰的解释了CS.XXX的写法实现调用C#代码的过程,而import_type内部是怎么去查找C#类型的,接下来说明:

        (注:接下来代码中含有大量关于LuaAPI的部分,想要深入了解的可以查阅《lua程序设计(第4版)》27章之后的内容)

        import_type = xlua.import_type。而这个xlua是一个全局table,在C代码中声明,位置为build/xlua.c:

xlua.c:
LUA_API void luaopen_xlua(lua_State *L) {
	luaL_openlibs(L);
	
#if LUA_VERSION_NUM >= 503
	luaL_newlib(L, xlualib);
	lua_setglobal(L, "xlua");
#else
	luaL_register(L, "xlua", xlualib);
    lua_pop(L, 1);
#endif
}

        这段代码属于xlua.dll,在创建LuaEnv时会调用,设置一个全局表xlua。同时,luaEnv创建时会注册import_type这个函数,当调用xlua.import_type时会调用到对应的C#委托上:

ObjectTranslator.cs:
public void OpenLib(RealStatePtr L) {
    if (0 != LuaAPI.xlua_getglobal(L, "xlua")){  throw new Exception("call xlua_getglobal fail!" + LuaAPI.lua_tostring(L, -1));} 
    LuaAPI.xlua_pushasciistring(L, "import_type");
    LuaAPI.lua_pushstdcallcfunction(L,importTypeFunction);
    LuaAPI.lua_rawset(L, -3); 
    ...
}

        接下来找到这个importTypeFunction委托,会发现最终调用到这个函数:

StaticLuaCallbacks.cs:
public static int ImportType(RealStatePtr L)
{
    try
    {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        //需要查询的类名
        string className = LuaAPI.lua_tostring(L, 1);
        //查找C#对应的Type(此处还没去查找对应Lua的表)
        Type type = translator.FindType(className);
        if (type != null)
        {
            //这句查找Type对应的lua表
            if (translator.GetTypeId(L, type) >= 0)
            {
                LuaAPI.lua_pushboolean(L, true);
            }
            else
            {
                return LuaAPI.luaL_error(L, "can not load type " + type);
            }
        }
        else
        {
            LuaAPI.lua_pushnil(L);
        }
             
        return 1;
    }
}

        通过以上代码会发现,如果没有找到对应的type则直接向lua虚拟栈(lua与其他语言通信的主要组件)中推一个nil,否则则去查找对应的lua表,如果有则推一个true,否则推一个错误码。最终return 1代表的是返回的参数数量为1。总而言之,上述lua代码中xlua.import_type函数返回的obj事实上只可能为true/nil/错误码,而对错误码是没有特殊处理的。

        而translator.GetTypeId内部的细节是关键,它包含了xlua实现lua调用C#代码的两种方式:一种是通过生成适配代码,另一种是通过反射来调用

        

ObjectTranslator.cs
internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
{
    int type_id;
    is_first = false;
    //查询是否缓存中有Type对应的Lua表,有就直接返回
    if (!typeIdMap.TryGetValue(type, out type_id)) // no reference
    {
        ...
        is_first = true;
        Type alias_type = null;
        aliasCfg.TryGetValue(type, out alias_type);
        //从注册表中检查Type对应的元表
        LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
        //元表为空,走相关注册逻辑
        if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta
        {
            LuaAPI.lua_pop(L, 1);
            //此处会去检查是使用反射还是生成适配代码
            if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type))
            {
                LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
            }
            else
            {
                throw new Exception("Fatal: can not load metatable of type:" + type);
            }
        }
        //循环依赖,自身依赖自己的class,比如有个自身类型的静态readonly对象。
        if (typeIdMap.TryGetValue(type, out type_id))
        {
            LuaAPI.lua_pop(L, 1);
        }
        else
        {
            ...
            LuaAPI.lua_pop(L, 1);
            //缓存type与其对应到lua中的表
            typeIdMap.Add(type, type_id);
        }
    }
    return type_id;
}

public bool TryDelayWrapLoader(RealStatePtr L, Type type)
{
    if (loaded_types.ContainsKey(type)) return true;
    loaded_types.Add(type, true);
    LuaAPI.luaL_newmetatable(L, type.FullName); 
    LuaAPI.lua_pop(L, 1);
    Action<RealStatePtr> loader;
    int top = LuaAPI.lua_gettop(L);.
    //这个delayWrap字典在Xlua生成的代码中(在XLua_Gen_Initer_Register__类
    //实例化时)将每一个类型的wrap注册在里面
    if (delayWrap.TryGetValue(type, out loader))
    {
        delayWrap.Remove(type);
        //将类方法,字段,成员等注册
        loader(L);
    }
    //这里就是反射的逻辑了
    else
    {
        ...
        //用反射将类方法,字段,成员等注册
        Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
        ...
    }
    ...
    ...
    return true;
}

        首先来看如果有生成适配代码是怎么执行的,也就是上面代码中的loader(L)的执行。以Vector3为例,实际上执行的是以下代码:

public static void __Register(RealStatePtr L)
{
    ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
	System.Type type = typeof(UnityEngine.Vector3);
	Utils.BeginObjectRegister(type, L, translator, 6, 6, 6, 3);
    //这边注册一些+/-/==等操作符相关的方法
	Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__add", __AddMeta);
    ...
    Utils.RegisterFunc(L, Utils.OBJ_META_IDX, "__eq", __EqMeta);
   
    //这边注册的是成员函数                  
    Utils.RegisterFunc(L, Utils.METHOD_IDX, "Set", _m_Set);
    Utils.RegisterFunc(L, Utils.METHOD_IDX, "GetHashCode", _m_GetHashCode);
    ...
    Utils.RegisterFunc(L, Utils.METHOD_IDX, "Equals", _m_Equals);
    Utils.RegisterFunc(L, Utils.METHOD_IDX, "ToString", _m_ToString);
	
    //这边注册的是成员变量的get   					
    Utils.RegisterFunc(L, Utils.GETTER_IDX, "normalized", _g_get_normalized);
    ...
    Utils.RegisterFunc(L, Utils.GETTER_IDX, "y", _g_get_y);
    Utils.RegisterFunc(L, Utils.GETTER_IDX, "z", _g_get_z);
    
    //这边注册的是成员变量的set          
    Utils.RegisterFunc(L, Utils.SETTER_IDX, "x", _s_set_x);
    Utils.RegisterFunc(L, Utils.SETTER_IDX, "y", _s_set_y);
    Utils.RegisterFunc(L, Utils.SETTER_IDX, "z", _s_set_z);
            		
    Utils.EndObjectRegister(type, L, translator, __CSIndexer, __NewIndexer,
		null, null, null);

    Utils.BeginClassRegister(type, L, __CreateInstance, 26, 10, 0);
    //这边注册的是静态函数 
    Utils.RegisterFunc(L, Utils.CLS_IDX, "Slerp", _m_Slerp_xlua_st_);
    ...  
    Utils.RegisterObject(L, translator, Utils.CLS_IDX, "kEpsilonNormalSqrt", UnityEngine.Vector3.kEpsionNormalSqrt);
    //这边注册的是静态变量的get和set(Vector3没有静态变量的set所以这里没有)         
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "zero", _g_get_zero);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "one", _g_get_one);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "forward", _g_get_forward);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "back", _g_get_back);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "up", _g_get_up);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "down", _g_get_down);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "left", _g_get_left);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "right", _g_get_right);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "positiveInfinity", _g_get_positiveInfinity);
    Utils.RegisterFunc(L, Utils.CLS_GETTER_IDX, "negativeInfinity", _g_get_negativeInfinity);
            			
    Utils.EndClassRegister(type, L, translator);
}

        从上代码可看出主要分两部分:静态和对象。

        首先从静态部分说起:

        Utils.BeginClassRegister函数中在创建了4个lua表,分别是cls_table、cls_metatable、cls_getter_table、cls_setter_table放在栈中-4、-3、-2、-1的位置(-1指的是栈顶,-2是栈顶向下一个的位置,以此类推,栈底是1)。其中cls_table就是此类型对应的lua表,cls_metatable是这个lua表的元表,getter表和Setter表是后面辅助实现__index和__newindex元方法的。

        而Utils.RegisterFunc则在这些表中注册上对应的方法:

Utils.cs
public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func)
{
    idx = abs_idx(LuaAPI.lua_gettop(L), idx);
    LuaAPI.xlua_pushasciistring(L, name);
    LuaAPI.lua_pushstdcallcfunction(L, func);
    LuaAPI.lua_rawset(L, idx);
}

        以代码“Utils.RegisterFunc(L, Utils.CLS_IDX, "Slerp", _m_Slerp_xlua_st_);”为例,相当于在cls_table中注册了Slerp字段,当调用Vector3.Slerp时就会调用到 _m_Slerp_xlua_st_函数上来。

        当类型中所有东西都注册完了以后,在函数Utils.EndClassRegister中将__index元方法和__newindex元方法实现好并设置在元表中:

public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator)
{
    //获取栈顶索引以及4个lua表的索引
    int top = LuaAPI.lua_gettop(L);
    int cls_idx = abs_idx(top, CLS_IDX);
    int cls_getter_idx = abs_idx(top, CLS_GETTER_IDX);
    int cls_setter_idx = abs_idx(top, CLS_SETTER_IDX);
    int cls_meta_idx = abs_idx(top, CLS_META_IDX);

    //begin cls index
    LuaAPI.xlua_pushasciistring(L, "__index");//向栈中压入“__index”
    LuaAPI.lua_pushvalue(L, cls_getter_idx);//压入cls_get_table
    LuaAPI.lua_pushvalue(L, cls_idx);//压入cls_table
    translator.Push(L, type.BaseType());//压入BaseType的地址
    //压入存放__index元方法的表的Key,这个表存放了所有类型的__index元方法
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    //从注册表(可以看作是存放全局变量的地方)中取出这个__index集合表
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    //这里会创建一个闭包函数cls_indexer并压入栈,关联并弹出上面压入的除了"__index"的值作为上值
    //这个闭包函数中实现了关于lua中__index元方法的功能
    //这一步执行过后栈中为:"__index"|cls_indexer
    LuaAPI.gen_cls_indexer(L);

    //压入LuaClassIndexsFieldName
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    //弹出key,压入__index集合表
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
    //压入当前类型
    translator.Push(L, type);
    //此时-3的位置是cls_indexer,这里意思是复制一份压入栈顶
    LuaAPI.lua_pushvalue(L, -3);
    //将这个cls_indexer存入__index的集合表,
    LuaAPI.lua_rawset(L, -3);
    //__index将集合表弹出
    LuaAPI.lua_pop(L, 1);
    //设置cls_metatable[__index] = cls_indexer,并弹出键和值
    LuaAPI.lua_rawset(L, cls_meta_idx);
    //end cls index

    //begin cls newindex
    LuaAPI.xlua_pushasciistring(L, "__newindex");
    //__nexindex与__index步骤一样,不再叙述
    ...
    //设置cls_metatable[__newindex] = cls_newindexer
    LuaAPI.lua_rawset(L, cls_meta_idx);
    //end cls newindex

    LuaAPI.lua_pop(L, 4);
}

        对象和静态实现的代码基本上一致,在Utils.BeginObjectRegister创建4个表:obj_meta,obj_method,obj_get,obj_set。之后在Utils.EndObjectRegister中将元表中设置并实现__index和__newindex,当每次使用时根据类型去全局变量中查找这个元表,完成操作映射。

        当某个类型没有被生成适配代码时,实际上也是可以被lua访问到的,这时候xlua走的是一个反射的机制,反射的分支可以从ObjectTranslator.TryDelayWrapLoader看到:

public bool TryDelayWrapLoader(RealStatePtr L, Type type)
{
    ...
    if (delayWrap.TryGetValue(type, out loader))
    {
        ...
    }
    //当Type没有适配代码时会走这个else分支
    else
    {
        ...
        //用反射将类方法,字段,成员等注册
        Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
        ...
    }
    ...
    ...
    return true;
}

         Utils.ReflectionWrap这个函数中实际上是将上面适配代码相关注册逻辑都包含在里面了,对于类型中的字段处理:

Utils.cs
static void makeReflectionWrap(RealStatePtr L, Type type, int cls_field, int cls_getter, int cls_setter,
			int obj_field, int obj_getter, int obj_setter, int obj_meta, out LuaCSFunction item_getter, out LuaCSFunction item_setter, BindingFlags access)
{
     ...   
     for (int i = 0; i < fields.Length; ++i)
     {
         ...
        if (field.IsStatic && (field.IsInitOnly || field.IsLiteral))
        {
        //对于静态字段,直接将字段名=value保存在cls_field表(等同于之前的cls_table)中
        LuaAPI.xlua_pushasciistring(L, fieldName);
        translator.PushAny(L, field.GetValue(null));
        LuaAPI.lua_rawset(L, cls_field);
        }
        else
        {
            //成员字段则做一个闭包函数保存在表中,
            //映射于C#端的StaticLuaCallbacks.FixCSFunctionWraper这个委托
            LuaAPI.xlua_pushasciistring(L, fieldName);
           translator.PushFixCSFunction(L, genFieldGetter(type, field));
           LuaAPI.lua_rawset(L, field.IsStatic ? cls_getter : obj_getter);

            LuaAPI.xlua_pushasciistring(L, fieldName);
            translator.PushFixCSFunction(L, genFieldSetter(type, field));
            LuaAPI.lua_rawset(L, field.IsStatic ? cls_setter : obj_setter);
        }
    }
    ...
    
    //事件映射到FixCSFunctionWraper委托
    EventInfo[] events = type.GetEvents(flag);
	for (int i = 0; i < events.Length; ++i)
	{
    	EventInfo eventInfo = events[i];
		LuaAPI.xlua_pushasciistring(L, eventInfo.Name);
		translator.PushFixCSFunction(L, translator.methodWrapsCache.GetEventWrap(type, eventInfo.Name));
		bool is_static = (eventInfo.GetAddMethod(true) != null) ? eventInfo.GetAddMethod(true).IsStatic : eventInfo.GetRemoveMethod(true).IsStatic;
		LuaAPI.lua_rawset(L, is_static ? cls_field : obj_field);
	}

    ...
    //方法映射到FixCSFunctionWraper委托
    foreach (var kv in pending_methods)
	{
		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);
		    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);
		}
	}
}

        另外还有属性的getter、setter同样也映射到FixCSFunctionWraper委托,代码都在Utils.ReflectionWrap这个函数中,但分散在不同的地方,就不贴了。关于这个PushFixCSFunction中是如何映射的:

internal void PushFixCSFunction(RealStatePtr L, LuaCSFunction func)
{
    if (func == null)
    {
        LuaAPI.lua_pushnil(L);
    }
    else
    {
        //这里压入一个索引
        LuaAPI.xlua_pushinteger(L, fix_cs_functions.Count);
        //将函数缓存在fix_cs_functions中
        fix_cs_functions.Add(func);
        //创建一个闭包函数并压入栈,将上面的索引作为upvalue
        LuaAPI.lua_pushstdcallcfunction(L, metaFunctions.FixCSFunctionWraper, 1);
   }
}

         每当我们通过反射调用时,最终都会调用到FixCSFunctionWraper这个委托中:

StaticLuaCallbacks.cs
static int FixCSFunction(RealStatePtr L)
{
    try
    {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        int idx = LuaAPI.xlua_tointeger(L, LuaAPI.xlua_upvalueindex(1));
        LuaCSFunction func = (LuaCSFunction)translator.GetFixCSFunction(idx);
        return func(L);
   }
   catch (Exception e)
   {
        return LuaAPI.luaL_error(L, "c# exception in FixCSFunction:" + e);
    }
}

         从上面代码可以看到,每次调用时会取出upvalue中的索引,然后从fix_cs_functions列表中取出函数进行调用。

        至此,整个调用就已经讲完了。从上可以看出,每个类型的第一次调用会进行执行一系列注册逻辑,这部分逻辑是有大量的GC Alloc的,在我们实际开发中,应该尽量将这些逻辑提前在性能不吃紧的位置执行。

        另外在lua中使用到的CS类型也应该保证去生成适配代码,否则将会走反射的逻辑,而反射的效率不高并且在反射API的调用中有少量GC产生。

  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值