找回密码
 立即注册
查看: 203|回复: 0

xLua源码解析——C#调用Lua

[复制链接]
发表于 2022-11-9 15:06 | 显示全部楼层 |阅读模式
概述

开始分析前,我们可以结合xLua的Tutorial->CSCallLua.cs看,C#调用Lua,主要分为以下4种情况:

  • 获取Lua的number、string、boolean
  • 获取原来在C#实例化的对象。
  • 获取table,映射为LuaTable、类、接口、数组、泛型List或者Dictionary。
  • 获取function,映射为LuaFunction、Delegate(包括Action、Func等)。
调用流程

C#访问Lua,只能通过LuaEnv的Global入手,其中Global是LuaTable类型的全局表,例如:
luaenv.Global.Get<int>("a");
luaenv.Global.Get<string>("b")
luaenv.Global.Get<bool>("c")
所以要了解C#调用Lua,就先从LuaTable的TValue Get<TValue>(string key)开始。
下面的Get方法比较简单,就是把即将要访问的table和key先压入栈,并取出value放在栈上,并初步判断合法性。
public void Get<TKey, TValue>(TKey key, out TValue value)
{
#if THREAD_SAFE || HOTFIX_ENABLE
    lock (luaEnv.luaEnvLock)
    {
#endif
        var L = luaEnv.L;
        var translator = luaEnv.translator;
        int oldTop = LuaAPI.lua_gettop(L);
        LuaAPI.lua_getref(L, luaReference);// 压入引用的table,luaReference是lua全局引用表的索引值
        translator.PushByType(L, key);// 栈压入key

        if (0 != LuaAPI.xlua_pgettable(L, -2))// 调用:table[key]
        {
            string err = LuaAPI.lua_tostring(L, -1);
            LuaAPI.lua_settop(L, oldTop);
            throw new Exception("get field [" + key + "] error:" + err);
        }

        LuaTypes lua_type = LuaAPI.lua_type(L, -1);
        Type type_of_value = typeof(TValue);
        if (lua_type == LuaTypes.LUA_TNIL && type_of_value.IsValueType())
        {
            // 取出了nil,但是要获取的是值类型
            throw new InvalidCastException("can not assign nil to " + type_of_value.GetFriendlyName());
        }

        try
        {
            translator.Get(L, -1, out value);// 真正去取值到value
        }
        catch (Exception e)
        {
            throw e;
        }
        finally
        {
            LuaAPI.lua_settop(L, oldTop);
        }
#if THREAD_SAFE || HOTFIX_ENABLE
    }
#endif
}
c函数xlua_pgettable,其实就是对lua_gettable的调用封装,有兴趣可以看看下面的源码。
static int c_lua_gettable(lua_State* L) {   
    lua_gettable(L, 1);   
    return 1;
}
LUA_API int xlua_pgettable(lua_State* L, int idx) {
    int top = lua_gettop(L);
    idx = lua_absindex(L, idx);
    lua_pushcfunction(L, c_lua_gettable);
    lua_pushvalue(L, idx);
    lua_pushvalue(L, top);
    lua_remove(L, top);
    return lua_pcall(L, 2, 1, 0);
}在上面取值后,就要进行转换了,转换逻辑在translator.Get(L, -1, out value);,先看源码:
// ObjectTranslator.Get
public void Get<T>(RealStatePtr L, int index, out T v)
{
    Func<RealStatePtr, int, T> get_func;
    // 获取配置好的转换函数(只有c#基础类型,例如int、float、string、byte等等)
    if (tryGetGetFuncByType(typeof(T), out get_func))
    {
        v = get_func(L, index);
    }
    else
    {
        v = (T)GetObject(L, index, typeof(T));
    }
}
tryGetGetFuncByType实际上就是最前面提到的情况1,获取Lua的基础类型,对应C#的基础类型,需要注意的是,转换函数都是通过c实现的,具体就不贴代码了。
重点看看其他类型的转换,在GetObject方法,主要在处理情况2,判断value是否是C#的实例。
public object GetObject(RealStatePtr L, int index, Type type)
{
    // 是否是c#引用类型的实例?
    // 参考《Lua调用C#》c#的实例创建都会在lua层记录实例在缓存列表的索引。
    int udata = LuaAPI.xlua_tocsobj_safe(L, index);
    if (udata != -1)
    {
        object obj = objects.Get(udata);// 从缓存取出实例
        RawObject rawObject = obj as RawObject;
        return rawObject == null ? obj : rawObject.Target;
    }
    else
    {
        if (LuaAPI.lua_type(L, index) == LuaTypes.LUA_TUSERDATA)
        {
            GetCSObject get;
            // 获取UserData的具体类型id
            int type_id = LuaAPI.xlua_gettypeid(L, index);
            // Decimal类型
            if (type_id != -1 && type_id == decimal_type_id){
                decimal d;
                Get(L, index, out d);
                return d;
            }
            // struct类型
            Type type_of_struct;
            if (type_id != -1
                && typeMap.TryGetValue(type_id, out type_of_struct)
                && type.IsAssignableFrom(type_of_struct)
                && custom_get_funcs.TryGetValue(type, out get))
            {
                return get(L, index);
            }
        }
        // 其他类型找转换函数并执行
        return (objectCasters.GetCaster(type)(L, index, null));
    }
}
如果看过《Lua调用C#》这一节的应该知道,C#的对象传入Lua,实际上传递的是C#侧缓存列表(objects)的索引,所以能取到索引,就能立刻得到C#的对象。这里先贴一下xlua_tocsobj_safe源码:
// 根据索引获取udata(udata是实例在c#侧缓存列表的索引值)
LUA_API int xlua_tocsobj_safe(lua_State *L,int index) {
    int *udata = (int *)lua_touserdata (L,index);// 根据索引取值,转换为userdata
    if (udata != NULL) {
        if (lua_getmetatable(L,index)) {// 获取userdata的metatable
            lua_pushlightuserdata(L, &tag);// 传入tag = 0
            lua_rawget(L,-2);// metatable[0]
            if (!lua_isnil (L,-1)) { // metatable[0] != nil 表示是c#的实例
                lua_pop (L, 2);// 弹出metatable和tag
                return *udata;
            }
            lua_pop (L, 2);
        }
    }
    return -1;
}如果没有索引,但是又是userdata类型,则可以看看是否是Decimal或者struct类型,因为struct是值类型,所以不会像类一样保存在缓存列表,这里注意custom_get_funcs列表,里面记录的是生成代码时候产生的类和struct的Get方法。
接下来看GetCaster方法,这个方法前半部分用于处理一些特殊的类型,获取最终要转换的类型,结合注释看:
public ObjectCast GetCaster(Type type)
{
    // 是否通过引用传递的类型,例如string[]的ElementType是string。
    if (type.IsByRef) type = type.GetElementType();
    // 例如:Nullable<Int32>,underlyingType = Int32
    Type underlyingType = Nullable.GetUnderlyingType(type);
    if (underlyingType != null)
    {
        return genNullableCaster(GetCaster(underlyingType));
    }
    ObjectCast oc;
    // 尝试获取基础的转换函数,列表包含c#的基础类型和LuaTable和LuaFunction。
    if (!castersMap.TryGetValue(type, out oc))
    {
        oc = genCaster(type);// 生成类型转换函数
        castersMap.Add(type, oc);
    }
    return oc;
}
上述的castersMap记录转换方法列表和前面tryGetGetFuncByType相比,只多了LuaFunction和LuaTable的转换方法,至于为什么会有重复的转换方法,这里我也没想明白。
不过可以看出上面方法处理情况3、4中的一部分,剩余的部分就到了最后的genCaster方法,这个方法比较长,主要就是类型的分类处理了,贴出源码方便大家看。
private ObjectCast genCaster(Type type)
{
    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;
    };
    // 能否分配给Delegate
    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);
        };
    }
    else if (typeof(DelegateBridgeBase).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, null, idx);
        };
    }
    else if (type.IsInterface())
    {
        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);
        };
    }
    else if (type.IsEnum())
    {
        return (RealStatePtr L, int idx, object target) =>
        {
            object obj = fixTypeGetter(L, idx, target);
            if (obj != null) return obj;

            LuaTypes lua_type = LuaAPI.lua_type(L, idx);
            if (lua_type == LuaTypes.LUA_TSTRING)
            {
                return Enum.Parse(type, LuaAPI.lua_tostring(L, idx));
            }
            else if (lua_type == LuaTypes.LUA_TNUMBER)
            {
                return Enum.ToObject(type, LuaAPI.xlua_tointeger(L, idx));
            }
            throw new InvalidCastException("invalid value for enum " + type);
        };
    }
    else if (type.IsArray)
    {
        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;
            }

            uint len = LuaAPI.xlua_objlen(L, idx);
            int n = LuaAPI.lua_gettop(L);
            idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
            Type et = type.GetElementType();
            ObjectCast elementCaster = GetCaster(et);
            Array ary = target == null ? Array.CreateInstance(et, (int)len) : target as Array;
            if (!LuaAPI.lua_checkstack(L, 1))
            {
                throw new Exception("stack overflow while cast to Array");
            }
            for (int i = 0; i < len; ++i)
            {
                LuaAPI.lua_pushnumber(L, i + 1);
                LuaAPI.lua_rawget(L, idx);
                if (et.IsPrimitive())
                {
                    if (!StaticLuaCallbacks.TryPrimitiveArraySet(type, L, ary, i, n + 1))
                    {
                        ary.SetValue(elementCaster(L, n + 1, null), i);
                    }
                }
                else
                {
                    if (InternalGlobals.genTryArraySetPtr == null
                        || !InternalGlobals.genTryArraySetPtr(type, L, translator, ary, i, n + 1))
                    {
                        ary.SetValue(elementCaster(L, n + 1, null), i);
                    }
                }
                LuaAPI.lua_pop(L, 1);
            }
            return ary;
        };
    }
    else if (typeof(IList).IsAssignableFrom(type) && type.IsGenericType())
    {
        Type elementType = type.GetGenericArguments()[0];
        ObjectCast elementCaster = GetCaster(elementType);

        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;
            }

            obj = target == null ? Activator.CreateInstance(type) : target;
            int n = LuaAPI.lua_gettop(L);
            idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
            IList list = obj as IList;


            uint len = LuaAPI.xlua_objlen(L, idx);
            if (!LuaAPI.lua_checkstack(L, 1))
            {
                throw new Exception("stack overflow while cast to IList");
            }
            for (int i = 0; i < len; ++i)
            {
                LuaAPI.lua_pushnumber(L, i + 1);
                LuaAPI.lua_rawget(L, idx);
                if (i < list.Count && target != null)
                {
                    if (translator.Assignable(L, n + 1, elementType))
                    {
                        list = elementCaster(L, n + 1, list); ;
                    }
                }
                else
                {
                    if (translator.Assignable(L, n + 1, elementType))
                    {
                        list.Add(elementCaster(L, n + 1, null));
                    }
                }
                LuaAPI.lua_pop(L, 1);
            }
            return obj;
        };
    }
    else if (typeof(IDictionary).IsAssignableFrom(type) && type.IsGenericType())
    {
        Type keyType = type.GetGenericArguments()[0];
        ObjectCast keyCaster = GetCaster(keyType);
        Type valueType = type.GetGenericArguments()[1];
        ObjectCast valueCaster = GetCaster(valueType);

        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;
            }

            IDictionary dic = (target == null ? Activator.CreateInstance(type) : target) as IDictionary;
            int n = LuaAPI.lua_gettop(L);
            idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index

            LuaAPI.lua_pushnil(L);
            if (!LuaAPI.lua_checkstack(L, 1))
            {
                throw new Exception("stack overflow while cast to IDictionary");
            }
            while (LuaAPI.lua_next(L, idx) != 0)
            {
                if (translator.Assignable(L, n + 1, keyType) && translator.Assignable(L, n + 2, valueType))
                {
                    object k = keyCaster(L, n + 1, null);
                    dic[k] = valueCaster(L, n + 2, !dic.Contains(k) ? null : dic[k]);
                }
                LuaAPI.lua_pop(L, 1); // removes value, keeps key for next iteration
            }
            return dic;
        };
    }
    else if ((type.IsClass() && type.GetConstructor(System.Type.EmptyTypes) != null) || (type.IsValueType() && !type.IsEnum())) //class has default construtor
    {
        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;
            }

            obj = target == null ? Activator.CreateInstance(type) : target;

            int n = LuaAPI.lua_gettop(L);
            idx = idx > 0 ? idx : LuaAPI.lua_gettop(L) + idx + 1;// abs of index
            if (!LuaAPI.lua_checkstack(L, 1))
            {
                throw new Exception("stack overflow while cast to " + type);
            }
            foreach (FieldInfo field in type.GetFields())
            {
                LuaAPI.xlua_pushasciistring(L, field.Name);
                LuaAPI.lua_rawget(L, idx);
                if (!LuaAPI.lua_isnil(L, -1))
                {
                    try
                    {
                        field.SetValue(obj, GetCaster(field.FieldType)(L, n + 1,
                                target == null || field.FieldType.IsPrimitive() || field.FieldType == typeof(string) ? null : field.GetValue(obj)));
                    }
                    catch (Exception e)
                    {
                        throw new Exception("exception in tran " + field.Name + ", msg=" + e.Message);
                    }
                }
                LuaAPI.lua_pop(L, 1);
            }

            return obj;
        };
    }
    else
    {
        return fixTypeGetter;
    }
}
这里以table转C#类为例,实际上就是枚举C#类属性,根据属性名取出table的值,根据类型进行转换并赋值。其他就不再赘述。
至此整个C#访问Lua流程就已经结束了。
总结

C#访问Lua,只能通过LuaTable的Get方法访问,关键技术点:

  • 实例化LuaTable时,会将栈顶的tabel加入到引用表的下一个空闲位置,并返回索引,LuaTable通过持有索引来与table产生映射。
  • 通过LuaTable.Get方法传入key取出值放入lua栈顶,根据这个值类型:

    • 如果类型本来就是C#实例,则直接通过UserData记录的C#侧缓存表的索引值取出实例。
    • 其他则获取转换函数并调用获取最终C#需要的值。

懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

小黑屋|手机版|Unity开发者联盟 ( 粤ICP备20003399号 )

GMT+8, 2024-11-24 21:25 , Processed in 0.091390 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

快速回复 返回顶部 返回列表