How does Lua call C#

Time:2021-9-25

xLuaIt is an open source project of Tencent, which adds Lua script programming capability to c# environments such as unity,. Net and mono. This paper mainly discusses the implementation principle of lua calling c# under xlua.

Lua and c# data communication mechanism

Whether Lua calls c#, or c# calls Lua, a communication mechanism is needed to complete the data transmission. Lua itself is written in C language, so it is born with a communication mechanism with C / C + +.

The data interaction between Lua and C / C + + is carried out through the stack. When operating the data, first copy the data to the “stack”, and then obtain the data. Each data in the stack is located through the index value. When the index value is positive, it indicates the offset index relative to the bottom of the stack, and when the index value is negative, it indicates the offset index relative to the top of the stack. The index value starts with 1 or – 1, so the index value at the top of the stack is always – 1, The index value at the bottom of the stack is always 1. “Stack” is equivalent to the transfer place of data between Lua and C / C + +. Each kind of data has a corresponding access interface.

C # can call Lua’s DLL through P / invoke, and execute Lua’s C API through this DLL. In other words, c# can communicate with Lua with C / C + +. In the luadll.cs file of xlua, you can find many dllimport modified data stacking and acquisition interfaces.

// LuaDLL.cs
[DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)]
public static extern void lua_pushnumber(IntPtr L, double number);

[DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)]
public static extern void lua_pushboolean(IntPtr L, bool value);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern void xlua_pushinteger(IntPtr L, int value);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern double lua_tonumber(IntPtr L, int index);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern int xlua_tointeger(IntPtr L, int index);

[DllImport(LUADLL, CallingConvention = CallingConvention.Cdecl)]
public static extern uint xlua_touint(IntPtr L, int index);

[DllImport(LUADLL,CallingConvention=CallingConvention.Cdecl)]
public static extern bool lua_toboolean(IntPtr L, int index);

Pass c# object to Lua

For bool, simple value types such as int can be passed directly through the C API. However, the c# object is different. Lua has no corresponding type, so what is passed to Lua is only an index of the c# object. See the following code for the specific implementation

// ObjectTranslator.cs
public void Push(RealStatePtr L, object o)
{
    // ...
    int index = -1;
    Type type = o.GetType();
#if !UNITY_WSA || UNITY_EDITOR
    bool is_enum = type.IsEnum;
    bool is_valuetype = type.IsValueType;
#else
    bool is_enum = type.GetTypeInfo().IsEnum;
    bool is_valuetype = type.GetTypeInfo().IsValueType;
#endif
    bool needcache = ! is_ valuetype || is_ enum;  //  If it is a reference or enumeration, it will be cached
    If (needcache & & (is_enum? Enummap. TryGetValue (O, out index): reversemap. TryGetValue (O, out index)) // if there is a cache
    {
        if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)  
        {
            return;
        }
        //It's too classic here. The weaktable is deleted first, and then the GC will delay the call. When the index will be recycled, not commenting on this line will lead to repeated release
        //collectObject(index);
    }

    bool is_first;
    int type_id = getTypeId(L, type, out is_first);

    //If the definition of a type contains its own static readonly instance, gettypeid will push an instance, which should be used at this time
    if (is_first && needcache && (is_enum ? enumMap.TryGetValue(o, out index) : reverseMap.TryGetValue(o, out index))) 
    {
        if (LuaAPI.xlua_tryget_cachedud(L, index, cacheRef) == 1)   
        {
            return;
        }
    }
    //Cache on the c# side
    index = addObject(o, is_valuetype, is_enum);
    //Push the index representing the object to Lua
    LuaAPI.xlua_pushcsobj(L, index, type_id, needcache, cacheRef);
}

The two if statements in the code are mainly used to judge the cache. If the object to be delivered has been cached, the cached is directly used. If this object is passed for the first time, perform the following two steps

  1. Cache the object in the objects object pool through addObject and get an index (the object can be obtained through this index)

    // ObjectTranslator.cs
    int addObject(object obj, bool is_valuetype, bool is_enum)
    {
        int index = objects.Add(obj);
        if (is_enum)
        {
            enumMap[obj] = index;
        }
        else if (!is_valuetype)
        {
            reverseMap[obj] = index;
        }
        
        return index;
    }
  2. Through xlua_ Pushcsobj passes the index representing the object to Lua.

    The parameter key represents the index of the object, and the parameter meta_ Ref represents the index of the table representing the object type. Its value is obtained through the gettypeid function, which will be described in detail later. Parameter need_ Cache indicates whether to cache on the Lua side. Parameter cache_ Ref indicates the index of the Lua side cache table

    // xlua.c
    LUA_API void xlua_pushcsobj(lua_State *L, int key, int meta_ref, int need_cache, int cache_ref) {
        int* pointer = (int*)lua_newuserdata(L, sizeof(int));
        *pointer = key;
        
        if (need_cache) cacheud(L, key, cache_ref);  //  Lua side cache
    
        lua_rawgeti(L, LUA_REGISTRYINDEX, meta_ref);
    
        lua_ setmetatable(L, -2);  //  Set meta table for UserData
    }
    
    //Store key = UserData in the cache table
    static void cacheud(lua_State *L, int key, int cache_ref) {
        lua_rawgeti(L, LUA_REGISTRYINDEX, cache_ref);
        lua_pushvalue(L, -2);
        lua_rawseti(L, -2, key);
        lua_pop(L, 1);
    }

    xlua_ The main logic of pushcsobj is that after the index representing the object is pushed to Lua, Lua will create a UserData for it and point the UserData to the object index. If caching is needed, save the UserData to the cache table, and finally set the meta table for UserData. In other words, the c# object corresponds to a UserData on Lua’s side, and the object index is used to maintain the connection with the c# object.

Register c# type information to Lua

The meta table set for UserData (specifically refers to the proxy UserData corresponding to the c# object on the Lua side, and the UserData that appears later also has the same meaning, so it will not be repeated). It actually represents the type information of the object. After the c# object is passed to Lua, Lua needs to be informed of the type information of the object, such as which member methods, attributes or static methods of the object type. Only after these are registered with Lua can Lua call them correctly. This meta table is generated through the gettypeid function

// ObjectTranslator.cs
internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
{
    int type_id;
    is_first = false;
    if (!typeIdMap.TryGetValue(type, out type_id)) // no reference
    {
        // ...
        is_first = true;
        Type alias_type = null;
        aliasCfg.TryGetValue(type, out alias_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);
            }
        }

        //Circular dependency. It depends on its own class. For example, it has a static readonly object of its own type.
        if (typeIdMap.TryGetValue(type, out type_id))
        {
            LuaAPI.lua_pop(L, 1);
        }
        else
        {
            // ...
            LuaAPI.lua_pushvalue(L, -1);
            type_ id = LuaAPI.luaL_ ref(L, LuaIndexes.LUA_REGISTRYINDEX);  //  Add meta table to registry
            LuaAPI.lua_pushnumber(L, type_id);
            LuaAPI.xlua_ rawseti(L, -2, 1);   //  Meta table [1] = type_ id
            LuaAPI.lua_pop(L, 1);

            if (type.IsValueType())
            {
                typeMap.Add(type_id, type);
            }

            typeIdMap.Add(type, type_id);
        }
    }
    return type_id;
}

The main logic of the function is to take the name of the class as the key through lual_ Getmetatable obtains the meta table corresponding to the class. If it cannot be obtained, it is generated through the trydelaywraploader function. Then call luaL_. Ref adds the obtained meta table to the Lua registry and returns type_ id。 type_ ID refers to the index of the meta table in the Lua registry. Through this index, the meta table can be retrieved from the Lua registry. Xlua mentioned earlier_ The pushcsobj function uses type_ ID is meta_ Ref, get the meta table, and then set the meta table for UserData.

Let’s look at how the meta table is generated

// ObjectTranslator.cs
public bool TryDelayWrapLoader(RealStatePtr L, Type type)
{
    // ...
    LuaAPI.luaL_ newmetatable(L, type.FullName); // Create a metatable first, because it may be needed in the loading process
    LuaAPI.lua_pop(L, 1);

    Action loader;
    int top = LuaAPI.lua_gettop(L);
    If (delaywrap. TryGetValue (type, out loader)) // if there is a pre registered type meta table generator, use it directly
    {
        delayWrap.Remove(type);
        loader(L);
    }
    else
    {
#if !GEN_CODE_MINIMIZE && !ENABLE_IL2CPP && (UNITY_EDITOR || XLUA_GENERAL) && !FORCE_REFLECTION && !NET_STANDARD_2_0
        if (!DelegateBridge.Gen_Flag && !type.IsEnum() && !typeof(Delegate).IsAssignableFrom(type) && Utils.IsPublic(type))
        {
            Type wrap = ce.EmitTypeWrap(type);
            MethodInfo method = wrap.GetMethod("__Register", BindingFlags.Static | BindingFlags.Public);
            method.Invoke(null, new object[] { L });
        }
        else
        {
            Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
        }
#else
        Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
#endif
        // ...
    }
    if (top != LuaAPI.lua_gettop(L))
    {
        throw new Exception("top change, before:" + top + ", after:" + LuaAPI.lua_gettop(L));
    }

    foreach (var nested_type in type.GetNestedTypes(BindingFlags.Public))
    {
        If (nested_type. Isgenerictypedefinition()) // filter generic type definitions
        {
            continue;
        }
        GetTypeId(L, nested_type);
    }
    
    return true;
}

Trydelaywraploader is mainly used to handle two situations

  1. Judge whether code is generated for this class through delaywrap. If so, directly use the generation function to fill the meta table (loader method). There is a xluagenautoregister.cs file in the generated code of xlua. In this file, the initializer will be registered for the corresponding class, and this initializer is responsible for adding the meta table generation function corresponding to the class to delaywrap.
    // XLuaGenAutoRegister.cs
    public class XLua_Gen_Initer_Register__
    {
        static void wrapInit0(LuaEnv luaenv, ObjectTranslator translator)
        {
            // ...
            translator.DelayWrapLoader(typeof(TestXLua), TestXLuaWrap.__Register);  //  Fill the function with the meta table corresponding to the type__ Add register to delaywrap
            // ...
        }
        
        static void Init(LuaEnv luaenv, ObjectTranslator translator)
        {
            wrapInit0(luaenv, translator);
            translator.AddInterfaceBridgeCreator(typeof(System.Collections.IEnumerator), SystemCollectionsIEnumeratorBridge.__Create);
        }
        
        static XLua_Gen_Initer_Register__()
        {
    	    XLua.LuaEnv.AddIniter(Init);  //  Register initializer
    	}
    }
  2. If no code is generated, the meta table is populated by reflection (reflectionwrap method)

Populate the meta table with the build function

Take the testxlua class modified by luacallcsharp as an example to see how the generation function is generated

// TestXLua.cs
[LuaCallCSharp]
public class TestXLua
{
    public string Name;
    public void Test1(int a){
    }
    public static void Test2(int a, bool b, string c)
    {
    }
}

Testxluawrap.cs generated after generate code is as follows

public class TestXLuaWrap 
{
    public static void __Register(RealStatePtr L)
    {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        System.Type type = typeof(TestXLua);
        Utils.BeginObjectRegister(type, L, translator, 0, 1, 1, 1);
        Utils.RegisterFunc(L, Utils.METHOD_IDX, "Test1", _m_Test1);
        Utils.RegisterFunc(L, Utils.GETTER_IDX, "Name", _g_get_Name);
        Utils.RegisterFunc(L, Utils.SETTER_IDX, "Name", _s_set_Name);
        Utils.EndObjectRegister(type, L, translator, null, null,
            null, null, null);
        Utils.BeginClassRegister(type, L, __CreateInstance, 2, 0, 0);
        Utils.RegisterFunc(L, Utils.CLS_IDX, "Test2", _m_Test2_xlua_st_);
        Utils.EndClassRegister(type, L, translator);
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int __CreateInstance(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
            if(LuaAPI.lua_gettop(L) == 1)
            {
                TestXLua gen_ret = new TestXLua();
                translator.Push(L, gen_ret);
                return 1;
            }
        }
        catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
        return LuaAPI.luaL_error(L, "invalid arguments to TestXLua constructor!");
        
    }

    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _m_Test1(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
            TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
            {
                int _a = LuaAPI.xlua_tointeger(L, 2);
                gen_to_be_invoked.Test1( _a );
                return 0;
            }
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _m_Test2_xlua_st_(RealStatePtr L)
    {
        try {
            {
                int _a = LuaAPI.xlua_tointeger(L, 1);
                bool _b = LuaAPI.lua_toboolean(L, 2);
                string _c = LuaAPI.lua_tostring(L, 3);
                TestXLua.Test2( _a, _b, _c );
                return 0;
            }
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _g_get_Name(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        
            TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
            LuaAPI.lua_pushstring(L, gen_to_be_invoked.Name);
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
        return 1;
    }
    
    [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
    static int _s_set_Name(RealStatePtr L)
    {
        try {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        
            TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
            gen_to_be_invoked.Name = LuaAPI.lua_tostring(L, 2);
        
        } catch(System.Exception gen_e) {
            return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
        }
        return 0;
    }
}

Generating function__ Register is mainly such a framework

  1. Utils.beginobjectregister, do some preparatory work before registering the non static values of the class (such as member variables, member methods, etc.). It is mainly used to add metadata for meta tables__ GC and__ ToString meta method, method table, getter table and setter table are prepared. When calling registerfunc later, you can choose to insert them into the corresponding table

    // Utils.cs
    public static void BeginObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, int meta_count, int method_count, int getter_count,
        int setter_count, int type_id = -1)
    {
        if (type == null)
        {
            if (type_id == -1) throw new Exception("Fatal: must provide a type of type_id");
            LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id);
        }
        else
        {
            LuaAPI.luaL_getmetatable(L, type.FullName);
            //If the meta table corresponding to type. Fullname is empty, create a new meta table and set it to the registry
            if (LuaAPI.lua_isnil(L, -1))
            {
                LuaAPI.lua_pop(L, 1);
                LuaAPI.luaL_newmetatable(L, type.FullName);
            }
        }
        LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
        LuaAPI.lua_pushnumber(L, 1);
        LuaAPI.lua_ rawset(L, -3);  //  Set flag for meta table
    
        if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
        {
            LuaAPI.xlua_pushasciistring(L, "__gc");
            LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
            LuaAPI.lua_ rawset(L, -3);  //  Set for meta table__ GC method
        }
    
        LuaAPI.xlua_pushasciistring(L, "__tostring");
        LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
        LuaAPI.lua_ rawset(L, -3);  //  Set for meta table__ ToString method
    
        if (method_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_ createtable(L, 0, method_count);  //  Create method table
        }
    
        if (getter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_ createtable(L, 0, getter_count);  //  Create getter table
        }
    
        if (setter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_ createtable(L, 0, setter_count);  //  Create setter table
        }
    }
  2. Multiple utils.registerffunc to register the package method corresponding to each non static value of the class in different Lua tables. The wrap method is dynamically generated when generating code. For class properties, two wrap methods will be generated, namely get and set wrap methods.

    For example, the package method corresponding to member method test1 is_ m_ Test1 and registered in the method table. Name variable_ g_ get_ The name wrapper method is registered in the getter table, and_ s_ set_ The name wrapper method is registered in the setter table. This wrapped method is only a layer of wrapping of the original method. Calling this wrapped method is essentially calling the original method. As for why the package generation method is needed, we will talk about it later

    //Utils.cs registerfunc has different versions according to different macro definitions, but it is similar
    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);  //  Add the key value pair name = func to the table pointed to by IDX
    }
  3. Utils.endobjectregister to end the registration of non static values of the class. The main logic is to generate meta tables__ Index meta method and__ Newindex meta method, which is also the core of lua calling c#

    // Utils.cs
    public static void EndObjectRegister(Type type, RealStatePtr L, ObjectTranslator translator, LuaCSFunction csIndexer,
        LuaCSFunction csNewIndexer, Type base_type, LuaCSFunction arrayIndexer, LuaCSFunction arrayNewIndexer)
    {
        int top = LuaAPI.lua_gettop(L);
        int meta_idx = abs_idx(top, OBJ_META_IDX);
        int method_idx = abs_idx(top, METHOD_IDX);
        int getter_idx = abs_idx(top, GETTER_IDX);
        int setter_idx = abs_idx(top, SETTER_IDX);
    
        //begin index gen
        LuaAPI.xlua_pushasciistring(L, "__index");
        LuaAPI.lua_ pushvalue(L, method_idx);  //  1. Press in the methods table
        LuaAPI.lua_ pushvalue(L, getter_idx);  //  2. Press in getters table
    
        if (csIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_ pushstdcallcfunction(L, csIndexer);  //  3. Press in csindexer
            // ...
        }
    
        translator.Push(L, type == null ? base_type : type.BaseType());  //  4. Press in
    
        LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
        LuaAPI.lua_ rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  //  5. Press in indexfuncs
        if (arrayIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_ pushstdcallcfunction(L, arrayIndexer);  //  6. Press in arrayindexer
            // ...
        }
    
        LuaAPI.gen_ obj_ indexer(L);  //  Generate__ Index meta method
    
        if (type != null)
        {
            LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
            translator.Push(L, type);
            LuaAPI.lua_pushvalue(L, -3);
            LuaAPI.lua_ rawset(L, -3);  //  Registry [luaindexes] [type] =__ Index function
            LuaAPI.lua_pop(L, 1);
        }
    
        LuaAPI.lua_rawset(L, meta_idx);
        //end index gen
    
        //begin newindex gen
        LuaAPI.xlua_pushasciistring(L, "__newindex");
        LuaAPI.lua_pushvalue(L, setter_idx);
    
        if (csNewIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_pushstdcallcfunction(L, csNewIndexer);
            // ...
        }
    
        translator.Push(L, type == null ? base_type : type.BaseType());
    
        LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    
        if (arrayNewIndexer == null)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            // ...
            LuaAPI.lua_pushstdcallcfunction(L, arrayNewIndexer);
            // ...
        }
    
        LuaAPI.gen_ obj_ newindexer(L);  //  Generate__ Newindex meta method
    
        if (type != null)
        {
            LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
            LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables
            translator.Push(L, type);
            LuaAPI.lua_pushvalue(L, -3);
            LuaAPI.lua_ rawset(L, -3);  //  Registry [luanewindexes] [type] =__ Newindex function
            LuaAPI.lua_pop(L, 1);
        }
    
        LuaAPI.lua_rawset(L, meta_idx);
        //end new index gen
        LuaAPI.lua_pop(L, 4);
    }

    __ The index metamethod calls Gen_ obj_ Obtained by indexer, 6 parameters (marked in code comments) will be pressed in sequence before calling this method, Gen_ obj_ Another nil value will be pressed into the indexer to occupy the base index in advance. A total of 7 parameters will be associated to closure obj as upvalue_ indexer。 obj_ The indexer function is__ The logic of the index meta method is that when accessing UserData [key], first query whether there is a package method corresponding to the key in the methods, getters and other tables previously filled in through registerfunc. If so, it will be used directly. If not, it will be searched recursively in the parent class__ The newindex metamethod calls Gen_ obj_ Obtained by newindexer, and__ The principle of obtaining index is similar, so it will not be listed here.

    // xlua.c
    LUA_API int gen_obj_indexer(lua_State *L) {
        lua_pushnil(L);
        lua_pushcclosure(L, obj_indexer, 7);
        return 0;
    }
    
    //upvalue --- [1]: methods, [2]:getters, [3]:csindexer, [4]:base, [5]:indexfuncs, [6]:arrayindexer, [7]:baseindex
    //param   --- [1]: obj, [2]: key
    LUA_API int obj_indexer(lua_State *L) {	
        If (! Lua_isnil (L, lua_upvalueindex (1))) {// if there is a key in methods, use methods [key]
            lua_pushvalue(L, 2);
            lua_gettable(L, lua_upvalueindex(1));
            if (!lua_isnil(L, -1)) {//has method
                return 1;
            }
            lua_pop(L, 1);
        }
        
        If (! Lua_isnil (L, lua_upvalueindex (2))) {// if there is a key in getters, getters [key] is called
            lua_pushvalue(L, 2);
            lua_gettable(L, lua_upvalueindex(2));
            if (!lua_isnil(L, -1)) {//has getter
                lua_pushvalue(L, 1);
                lua_call(L, 1, 1);
                return 1;
            }
            lua_pop(L, 1);
        }
        
        
        If (! Lua_isnil (L, lua_upvalueindex (6)) & & lua_type (L, 2) = = lua_tnumber) {// if there is a key in arrayindexer and the key is a number, call arrayindexer [key]
            lua_pushvalue(L, lua_upvalueindex(6));
            lua_pushvalue(L, 1);
            lua_pushvalue(L, 2);
            lua_call(L, 2, 1);
            return 1;
        }
        
        If (! Lua_isnil (L, lua_upvalueindex (3))) {// if there is a key in csindexer, call csindexer [key]
            lua_pushvalue(L, lua_upvalueindex(3));
            lua_pushvalue(L, 1);
            lua_pushvalue(L, 2);
            lua_call(L, 2, 2);
            if (lua_toboolean(L, -2)) {
                return 1;
            }
            lua_pop(L, 2);
        }
        
        If (! Lua_isnil (L, lua_upvalueindex (4))) {// recursively look up in base
            lua_pushvalue(L, lua_upvalueindex(4));
            while(!lua_isnil(L, -1)) {
                lua_pushvalue(L, -1);
                lua_gettable(L, lua_upvalueindex(5));
                if (!lua_isnil(L, -1)) // found
                {
                    lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]
                    lua_pop(L, 1);
                    break;
                }
                lua_pop(L, 1);
                lua_getfield(L, -1, "BaseType");
                lua_remove(L, -2);
            }
            lua_pushnil(L);
            lua_replace(L, lua_upvalueindex(4));//base = nil
        }
        
        if (!lua_isnil(L, lua_upvalueindex(7))) {  
            lua_settop(L, 2);
            lua_pushvalue(L, lua_upvalueindex(7));  
            lua_insert(L, 1);
            lua_ call(L, 2, 1);  //  Calling the parent class__ index,indexfuncs[base](obj, key)
            return 1;
        } else {
            return 0;
        }
    }
  4. Utils.beginclassregister, do some preparatory work before registering the static values of the class (such as static variables, static methods, etc.). It mainly generates the corresponding CLS for the class_ Table and create static in advance_ Getter table and static_ The setter table is used to store the get and set package methods corresponding to static fields. Note that CLS is also used here_ Table set meta table meta_ table

    // Utils.cs
    public static void BeginClassRegister(Type type, RealStatePtr L, LuaCSFunction creator, int class_field_count,
        int static_getter_count, int static_setter_count)
    {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        LuaAPI.lua_createtable(L, 0, class_field_count);
    
        LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
        translator.PushAny(L, type);
        LuaAPI.lua_rawset(L, -3);
    
        int cls_table = LuaAPI.lua_gettop(L);
    
        SetCSTable(L, type, cls_table);
    
        LuaAPI.lua_createtable(L, 0, 3);
        int meta_table = LuaAPI.lua_gettop(L);
        if (creator != null)
        {
            LuaAPI.xlua_pushasciistring(L, "__call");
    #if GEN_CODE_MINIMIZE
            translator.PushCSharpWrapper(L, creator);
    #else
            LuaAPI.lua_pushstdcallcfunction(L, creator);
    #endif
            LuaAPI.lua_rawset(L, -3);
        }
    
        if (static_getter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_ createtable(L, 0, static_getter_count);   //  Create static_ Getter table
        }
    
        if (static_setter_count == 0)
        {
            LuaAPI.lua_pushnil(L);
        }
        else
        {
            LuaAPI.lua_ createtable(L, 0, static_setter_count);  //  Create static_ Setter table
        }
        LuaAPI.lua_pushvalue(L, meta_table);
        LuaAPI.lua_ setmetatable(L, cls_table);  //  Set meta table
    }

    cls_ The table is added to the registry layer by layer according to the namespace name of the class, mainly through setcstable.

    // Utils.cs
    public static void SetCSTable(RealStatePtr L, Type type, int cls_table)
    {
        int oldTop = LuaAPI.lua_gettop(L);
        cls_table = abs_idx(oldTop, cls_table);
        LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    
        List path = getPathOfType(type);
    
        //For a.b.c
    
        //For loop processing A.B
        //1. Registry [xlua_csharp_namespace] [a] = {} and out of stack Registry [xlua_csharp_namespace]
        //2. Registry [xlua_csharp_namespace] [a] [b] = {} and out of stack Registry [xlua_csharp_namespace] [a]
    
        for (int i = 0; i < path.Count - 1; ++i)
        {
            LuaAPI.xlua_pushasciistring(L, path[i]);
            if (0 != LuaAPI.xlua_pgettable(L, -2))
            {
                var err = LuaAPI.lua_tostring(L, -1);
                LuaAPI.lua_settop(L, oldTop);
                throw new Exception("SetCSTable for [" + type + "] error: " + err);
            }
            If (luaapi. Lua_isnil (L, - 1)) // if there is no key path [i] in the Registry [xlua_csharp_namespace], add a path [i] = {} key value pair
            {
                LuaAPI.lua_pop(L, 1);
                LuaAPI.lua_createtable(L, 0, 0);
                LuaAPI.xlua_pushasciistring(L, path[i]);
                LuaAPI.lua_pushvalue(L, -2);
                LuaAPI.lua_rawset(L, -4);
            }
            else if (!LuaAPI.lua_istable(L, -1))
            {
                LuaAPI.lua_settop(L, oldTop);
                throw new Exception("SetCSTable for [" + type + "] error: ancestors is not a table!");
            }
            LuaAPI.lua_remove(L, -2);
        }
    
        //Process C
        //Registry [xlua_csharp_namespace] [a] [b] [C] = CLS_ Table and out of the stack [xlua_csharp_namespace] [a] [b] [C]
        LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]);
        LuaAPI.lua_pushvalue(L, cls_table);
        LuaAPI.lua_rawset(L, -3);  
        LuaAPI.lua_pop(L, 1);
    
        //Add the key value pair [Lua proxy UserData corresponding to type] = CLS in the Registry [xlua_csharp_namespace]_ table
        LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
        ObjectTranslatorPool.Instance.Find(L).PushAny(L, type);
        LuaAPI.lua_pushvalue(L, cls_table);
        LuaAPI.lua_rawset(L, -3);
        LuaAPI.lua_pop(L, 1);
    }

    Taking class A.B.C as an example, the following table structure will be added to the Lua registry, and the Lua Registry [xlua_csharp_namespace] actually corresponds to the CS global table, so the form of CS. A.B.C can be directly used when accessing c# classes in xlua

    Lua registry ={
        xlua_ csharp_ Namespace = {-- CS global table
            A = {
                B = {
                    C = cls_table
                }
            },
        },
    }
  5. Multiple utils.registerfunctions have the same function as registerfunctions from beginobjectregister to endobjectregister, and register the package method corresponding to each static value of the class in the corresponding Lua table. The get and set wrapper methods corresponding to static variables will be registered to static respectively_ Getter table and static_ Setter table (except read-only static variables)

  6. Utils.endclassregister to end the registration of the static value of the class. Similar to endobjectregister, but it is CLS_ Meta table of table_ Tab settings__ Index meta method and__ Newindex meta method

    // Utils.cs
    public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator)
    {
        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");
        LuaAPI.lua_pushvalue(L, cls_getter_idx);
        LuaAPI.lua_pushvalue(L, cls_idx);
        translator.Push(L, type.BaseType());
        LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  
        LuaAPI.gen_cls_indexer(L);
    
        LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables  
        translator.Push(L, type);
        LuaAPI.lua_pushvalue(L, -3);
        LuaAPI.lua_ rawset(L, -3);  //  Registry [luaclassindexes] [type] =__ Index function
        LuaAPI.lua_pop(L, 1);
    
        LuaAPI.lua_rawset(L, cls_meta_idx);
        //end cls index
    
        //begin cls newindex
        LuaAPI.xlua_pushasciistring(L, "__newindex");
        LuaAPI.lua_pushvalue(L, cls_setter_idx);
        translator.Push(L, type.BaseType());
        LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
        LuaAPI.gen_cls_newindexer(L);
    
        LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua newindexs function tables
        translator.Push(L, type);
        LuaAPI.lua_pushvalue(L, -3);
        LuaAPI.lua_ rawset(L, -3);  //  Registry [luaclassnewindexes] [type] =__ Newindex function
        LuaAPI.lua_pop(L, 1);
    
        LuaAPI.lua_rawset(L, cls_meta_idx);
        //end cls newindex
    
        LuaAPI.lua_pop(L, 4);
    }

The above six parts have a large amount of code and complex logic. It is necessary to make a summary here.

The generated code will generate corresponding wrapping methods for non static values of the class, and register the wrapping methods in different tables in the form of key = func. Of UserData meta table__ Index and__ Newindex is responsible for finding the package method of the corresponding key from these different tables, and finally controlling the c# object by calling the package method

--Lua test code
local obj = CS.TestXLua()
Obj.name = "test" -- the assignment operation will trigger the obj meta table__ newindex,__ Newindex finds the set method corresponding to name in the setter table_ s_ set_ Name, and then call_ s_ set_ The name method sets the name property of the testxlua object to "test"

The generated code also generates CLS for each class in a namespace hierarchy_ Table. Like the non static values of the class, the generated code will also generate corresponding package methods for the static values of the class and register them in different tables (note that there are some differences here. The static methods of the class will be directly registered in the cls_table table). And CLS_ Table meta table__ Index and__ Newindex is responsible for finding the wrapping method of the corresponding key from these different tables, and finally controlling the c# class by calling the wrapping method

--Lua test code
CS. Testxlua. Test2() -- cs.testxlua obtains the CLS corresponding to the testxlua class_ Table, because test2 is a static method, in CLS_ The corresponding package method can be obtained directly from the table_ m_ Test2_ xlua_ st_, Then by calling_ m_ Test2_ xlua_ st_ The test2 method of the testxlua class is called indirectly

Fill meta table with reflection

When no code is generated, reflection is used for registration, which is basically the same as the logic of generating code for registration. Get the static and non-static values of the class through reflection, and then register them in different tables, and fill them in__ Index and__ Newindex meta method

// Utils.cs
public static void ReflectionWrap(RealStatePtr L, Type type, bool privateAccessible)
{
    LuaAPI.lua_checkstack(L, 20);

    int top_enter = LuaAPI.lua_gettop(L);
    ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
    //create obj meta table
    LuaAPI.luaL_getmetatable(L, type.FullName);
    if (LuaAPI.lua_isnil(L, -1))
    {
        LuaAPI.lua_pop(L, 1);
        LuaAPI.luaL_newmetatable(L, type.FullName);
    }
    //Add xlua to meta table_ Tag flag
    LuaAPI.lua_pushlightuserdata(L, LuaAPI.xlua_tag());
    LuaAPI.lua_pushnumber(L, 1);
    LuaAPI.lua_ rawset(L, -3);  //  Meta table [xlua_tag] = 1
    int obj_meta = LuaAPI.lua_gettop(L);  

    LuaAPI.lua_newtable(L);
    int cls_meta = LuaAPI.lua_gettop(L);

    LuaAPI.lua_newtable(L);
    int obj_field = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int obj_getter = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int obj_setter = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int cls_field = LuaAPI.lua_gettop(L);
    //set cls_field to namespace
    SetCSTable(L, type, cls_field);
    //finish set cls_field to namespace
    LuaAPI.lua_newtable(L);
    int cls_getter = LuaAPI.lua_gettop(L);
    LuaAPI.lua_newtable(L);
    int cls_setter = LuaAPI.lua_gettop(L);

    LuaCSFunction item_getter;
    LuaCSFunction item_setter;
    makeReflectionWrap(L, type, cls_field, cls_getter, cls_setter, obj_field, obj_getter, obj_setter, obj_meta,
        out item_getter, out item_setter, privateAccessible ? (BindingFlags.Public | BindingFlags.NonPublic) : BindingFlags.Public);

    // init obj metatable
    LuaAPI.xlua_pushasciistring(L, "__gc");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
    LuaAPI.lua_rawset(L, obj_meta);

    LuaAPI.xlua_pushasciistring(L, "__tostring");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
    LuaAPI.lua_rawset(L, obj_meta);

    LuaAPI.xlua_pushasciistring(L, "__index");
    LuaAPI.lua_pushvalue(L, obj_field);  // 1.upvalue methods = obj_field
    LuaAPI.lua_pushvalue(L, obj_getter);  // 2.upvalue getters = obj_getter
    translator.PushFixCSFunction(L, item_getter);  // 3.upvalue csindexer = item_getter
    translator.PushAny(L, type.BaseType());  //  Press basetype, 4.upvalue base
    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_ rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  //  5. Upvalue indexfuncs = Registry [luaindexes]
    LuaAPI.lua_pushnil(L);  // 6.upvalue arrayindexer = nil
    LuaAPI.gen_ obj_ indexer(L);  //  Generate__ Index function
    //store in lua indexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);  
    translator.Push(L, type);  //  Press in type
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_ rawset(L, -3);  //  Registry [luaindexes] [type] =__ Index function
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_ rawset(L, obj_meta); //  set __ Index is obj_ Meta ["_index"] = generated__ Index function

    LuaAPI.xlua_pushasciistring(L, "__newindex");
    LuaAPI.lua_pushvalue(L, obj_setter);
    translator.PushFixCSFunction(L, item_setter);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.lua_pushnil(L);
    LuaAPI.gen_obj_newindexer(L);
    //store in lua newindexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_ rawset(L, -3);  //  Registry [luanewindexes] [type] =__ Newindex function
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, obj_meta); // set __newindex
                                    //finish init obj metatable

    LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
    translator.PushAny(L, type);
    LuaAPI.lua_ rawset(L, cls_field);  //  cls_ Field ["underlyingsystemtype"] = type, the basic type of the record class

    if (type != null && type.IsEnum())
    {
        LuaAPI.xlua_pushasciistring(L, "__CastFrom");
        translator.PushFixCSFunction(L, genEnumCastFrom(type));
        LuaAPI.lua_rawset(L, cls_field);
    }

    //init class meta
    LuaAPI.xlua_pushasciistring(L, "__index");
    LuaAPI.lua_pushvalue(L, cls_getter);
    LuaAPI.lua_pushvalue(L, cls_field);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.gen_cls_indexer(L);
    //store in lua indexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_ rawset(L, -3);  //  Registry [luaclassindexes] [type] =__ Index function
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, cls_meta); // set __index 

    LuaAPI.xlua_pushasciistring(L, "__newindex");
    LuaAPI.lua_pushvalue(L, cls_setter);
    translator.Push(L, type.BaseType());
    LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    LuaAPI.gen_cls_newindexer(L);
    //store in lua newindexs function tables
    LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    translator.Push(L, type);
    LuaAPI.lua_pushvalue(L, -3);
    LuaAPI.lua_ rawset(L, -3);  ////  Registry [luaclassnewindexes] [type] =__ Newindex function
    LuaAPI.lua_pop(L, 1);
    LuaAPI.lua_rawset(L, cls_meta); // set __newindex
    // ...
}

Parameter passing when calling c# method

First, let’s solve the previous problem. Why do you need to generate corresponding wrapping methods for static or non-static values of classes? In fact, the wrapping method is used to deal with the problem of parameter transfer.

In order to correctly communicate with Lua, the C function has defined the protocol. This protocol defines the parameter and return value transfer method: the C function accepts the parameters through the stack in Lua, and the parameters are put on the stack in positive order (the first parameter is put on the stack first). So when the function starts, Lua_ Gettop (L) can return the number of parameters received by the function. The first parameter (if any) is at index 1, and the last parameter is at index Lua_ At gettop (L). When you need to return values to Lua, the C function only needs to push them on the stack in positive order (the first return value is pushed in first), and then return the number of these return values. Under these return values, everything on the stack will be discarded by Lua. Like Lua functions, calling C functions from Lua can have many return values.

In other words, when Lua calls the C function, the parameters will be automatically pressed on the stack. This mechanism has been implemented inside Lua. At the beginning of the article, it is also mentioned that c# can communicate with Lua through C / C + +, so c# needs to obtain the parameters passed by Lua through C API, and this logic is encapsulated in the package method. Take test1 method of testxlua as an example, which requires an int parameter. Therefore, its wrapped method needs to obtain an int parameter through the C API, and then use this parameter to call the real method

[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
        {
            int _ a = LuaAPI.xlua_ tointeger(L, 2);  //  Get int parameter
            gen_ to_ be_ invoked.Test1( _a );  //  Call the real test1 method
            return 0;
        }
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
}

This also explains why it is necessary to generate corresponding get and set methods for class attributes, because only when Lua’s access or assignment operation is converted into function call form, the parameters can be automatically stacked by function call mechanism and passed to C#

--Lua test code
Obj.name = "test" -- assignment operation
Setter ["name"] ("test") -- function call form

Let’s talk about function overloading again. Because c# supports overloading, there will be multiple functions with the same name but different parameters. In this case, you can only judge which function should be called by the parameters passed when the function with the same name is called

[LuaCallCSharp]
public class TestXLua
{
    //Function overload test1
    public void Test1(int a){
    }
    //Function overload test1
    public void Test1(bool b){
    }
}

//Package method generated for test1
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_Test1(RealStatePtr L)
{
    try {
        ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
        TestXLua gen_to_be_invoked = (TestXLua)translator.FastGetCSObj(L, 1);
        int gen_param_count = LuaAPI.lua_gettop(L);
        If (gen_param_count = = 2 & & luatypes.lua_tnumber = = luaapi.lua_type (L, 2)) // judge which method to call according to the number and type of parameters
        {
            int _a = LuaAPI.xlua_tointeger(L, 2);
            gen_to_be_invoked.Test1( _a );
            return 0;
        }
        If (gen_param_count = = 2 & & luatypes.lua_tboolean = = luaapi.lua_type (L, 2)) // judge which method to call according to the number and type of parameters
        { 
            bool _b = LuaAPI.lua_toboolean(L, 2);
            gen_to_be_invoked.Test1( _b );
            return 0;
        }
    } catch(System.Exception gen_e) {
        return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
    }
    return LuaAPI.luaL_error(L, "invalid arguments to TestXLua.Test1!");
}

GC

C# and Lua both have automatic garbage collection mechanisms and are imperceptible to each other. If the c# object passed to Lua is automatically recycled by c# and Lua continues to use it without knowing it, it will inevitably lead to unpredictable errors. Therefore, the basic principle is that c# objects passed to Lua cannot be recycled automatically. Lua can only notify c# for recycling after it is determined that they are no longer used

In order to ensure that c# will not recycle objects automatically, all objects passed to Lua will be referenced by objects. The real object index passed to Lua is the index of the object in objects

The UserData created by Lua for the object index will be saved in the cache table, and the reference mode of the cache table is set to weak reference

// ObjectTranslator.cs
LuaAPI.lua_ newtable(L);  //  Create cache table
LuaAPI.lua_ newtable(L);  //  Create meta table
LuaAPI.xlua_pushasciistring(L, "__mode");
LuaAPI.xlua_pushasciistring(L, "v");
LuaAPI.lua_ rawset(L, -3);  //  Meta table [_mode] = V, indicating that all values in this table are weak references
LuaAPI.lua_ setmetatable(L, -2);  //  Set meta table for cache table
cacheRef = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);

When the UserData is no longer referenced by Lua, the UserData will be removed from the cache table. During Lua GC, the UserData will be recycled, and the user data meta table will be called before recycling__ GC method to inform c# “this object is no longer used by Lua. It’s time for you to recycle.”. Inside the beginobjectregister method, the user data meta table is added__ GC method

//Utils.cs beginobjectregister method
if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
{
    LuaAPI.xlua_pushasciistring(L, "__gc");
    LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
    LuaAPI.lua_ rawset(L, -3);  //  Set for meta table__ GC method
}

Translator.metafunctions.gcmeta is actually the luagc method of staticluacallbacks

// StaticLuaCallbacks.cs
[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int LuaGC(RealStatePtr L)
{
    try
    {
        int udata = LuaAPI.xlua_tocsobj_safe(L, 1);
        if (udata != -1)
        {
            ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
            if ( translator != null )
            {
                translator.collectObject(udata);
            }
        }
        return 0;
    }
    catch (Exception e)
    {
        return LuaAPI.luaL_error(L, "c# exception in LuaGC:" + e);
    }
}

The luagc method in turn calls the collectobject method. Within the collectobject method, objects will be removed from objects, so that objects are no longer fixed references and can be recycled normally by c# GC

// ObjectTranslator.cs
internal void collectObject(int obj_index_to_collect)
{
    object o;
    
    if (objects.TryGetValue(obj_index_to_collect, out o))
    {
        objects.Remove(obj_index_to_collect);
        
        if (o != null)
        {
            int obj_index;
            //Lua GC is called after removing the weak table__ GC. During this period, the same object may be pushed to Lua again and associated with a new index
            bool is_enum = o.GetType().IsEnum();
            if ((is_enum ? enumMap.TryGetValue(o, out obj_index) : reverseMap.TryGetValue(o, out obj_index))
                && obj_index == obj_index_to_collect)
            {
                if (is_enum)
                {
                    enumMap.Remove(o);
                }
                else
                {
                    reverseMap.Remove(o);
                }
            }
        }
    }
}

reference resources

Recommended Today

Supervisor

Supervisor [note] Supervisor – H view supervisor command help Supervisorctl – H view supervisorctl command help Supervisorctl help view the action command of supervisorctl Supervisorctl help any action to view the use of this action 1. Introduction Supervisor is a process control system. Generally speaking, it can monitor your process. If the process exits abnormally, […]