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

tolua源码分析(三)C#访谒lua变量的机制实现

[复制链接]
发表于 2024-7-15 18:37 | 显示全部楼层 |阅读模式
上一节我们讨论了C#是如何获取并调用到lua定义的函数,这一节我们更进一步,来看看如何让C#可以访谒lua定义的变量的。依旧从一个例子看起,此次是tolua自带的example04,主要代码如下:
  1. new LuaResLoader();
  2. LuaState lua = new LuaState();
  3. lua.Start();
  4. lua[”Objs2Spawn”] = 5;
  5. string script =
  6. @”
  7.     print('Objs2Spawn is: '..Objs2Spawn)
  8.     var2read = 42
  9.     varTable = {1,2,3,4,5}
  10.     varTable.default = 1
  11.     varTable.map = {}
  12.     varTable.map.name = 'map'
  13.     meta = {name = 'meta'}
  14.     setmetatable(varTable, meta)
  15.     function TestFunc(strs)
  16.         print('get func by variable')
  17.     end
  18. ”;
  19. lua.DoString(script);
  20. Debugger.Log(”Read var from lua: {0}”, lua[”var2read”]);
  21. Debugger.Log(”Read table var from lua: {0}”, lua[”varTable.default”]);  
  22. LuaFunction func = lua[”TestFunc”] as LuaFunction;
  23. func.Call();
  24. func.Dispose();
  25. LuaTable table = lua.GetTable(”varTable”);
  26. Debugger.Log(”Read varTable from lua, default: {0} name: {1}”, table[”default”], table[”map.name”]);
  27. table[”map.name”] = ”new”;
  28. Debugger.Log(”Modify varTable name: {0}”, table[”map.name”]);
  29. table.AddTable(”newmap”);
  30. LuaTable table1 = (LuaTable)table[”newmap”];
  31. table1[”name”] = ”table1”;
  32. Debugger.Log(”varTable.newmap name: {0}”, table1[”name”]);
  33. table1.Dispose();
  34. table1 = table.GetMetaTable();
  35. if (table1 != null)
  36. {
  37.     Debugger.Log(”varTable metatable name: {0}”, table1[”name”]);
  38. }
  39. object[] list = table.ToArray();
  40. for (int i = 0; i < list.Length; i++)
  41. {
  42.     Debugger.Log(”varTable[{0}], is {1}”, i, list[i]);
  43. }
  44. table.Dispose();                        
  45. lua.CheckTop();
  46. lua.Dispose();
复制代码
第1-3行对lua虚拟机进行了初始化,这块内容我们在第一节里已经详细展开过,这里就不再赘述。通过第4行,可以发现C#的LuaState类重载了下标访谒的操作符,使其扮演了lua中_G的角色,可以直接往lua层写入一个名为Objs2Spawn的变量,它的值为5。这一点在接下来执行的lua代码可以得到验证:



tolua源码分析(三) C#写入lua变量

下面就来看看下标访谒操作符的具体实现,这里用到的是set,那么就先忽略掉get相关的实现:
  1. public object this[string fullPath]
  2. {
  3.     get
  4.     {
  5.         ...
  6.     }
  7.     set
  8.     {
  9.         int oldTop = LuaGetTop();
  10.         int pos = fullPath.LastIndexOf(&#39;.&#39;);
  11.         if (pos > 0)
  12.         {
  13.             string tableName = fullPath.Substring(0, pos);
  14.             IntPtr p = LuaFindTable(LuaIndexes.LUA_GLOBALSINDEX, tableName);
  15.             if (p == IntPtr.Zero)
  16.             {
  17.                 string name = fullPath.Substring(pos + 1);
  18.                 LuaPushString(name);
  19.                 PushVariant(value);
  20.                 LuaSetTable(-3);
  21.             }
  22.             else
  23.             {
  24.                 LuaSetTop(oldTop);
  25.                 int len = LuaDLL.tolua_strlen(p);
  26.                 string str = LuaDLL.lua_ptrtostring(p, len);
  27.                 throw new LuaException(string.Format(”{0} not a Lua table”, str));
  28.             }
  29.         }
  30.         else
  31.         {
  32.             PushVariant(value);
  33.             LuaSetGlobal(fullPath);                    
  34.         }
  35.         LuaSetTop(oldTop);
  36.     }
  37. }
复制代码
第10-11行就是判断是直接往lua的_G里赋值,还是往_G中的某个table里赋值。如果是往某个table中赋值,那么首先需要在lua层找到这个table,也就是这里的LuaFindTable,它的实现就是简单地调用lua的API函数luaL_findtable,值得一提的是如果查找的table不存在,则该函数会去创建对应name的table,这样大大简化了在C#层创建table的操作。如果函数执行成功,返回的是NULL。对应到第20-23行,此时lua栈顶是获取或创建的table,我们只需将key和value都压入栈,调用lua_settable设置即可。当然,如果是直接往_G里赋值,那直接调用lua_setglobal就行了。
这里我们稍微看一下PushVariant,它会调用到ToLua.Push上,这个方式就是按照C# object的类型,调用分歧的push将object压入lua栈中。由于我们的参数是5,直接当作lua的number类型措置:
  1. public static void Push(IntPtr L, object obj)
  2. {
  3.     if (obj == null || obj.Equals(null))
  4.     {
  5.         LuaDLL.lua_pushnil(L);
  6.         return;
  7.     }
  8.     Type t = obj.GetType();
  9.     if (t.IsValueType)
  10.     {
  11.         if (TypeChecker.IsNullable(t))
  12.         {
  13.             Type[] ts = t.GetGenericArguments();
  14.             t = ts[0];
  15.         }
  16.         if (t.IsPrimitive)
  17.         {
  18.             double d = LuaMisc.ToDouble(obj);
  19.             LuaDLL.lua_pushnumber(L, d);
  20.         }
  21.         ...
  22.     }
  23.     else
  24.     {
  25.         ...
  26.     }
  27. }
复制代码
看完了set,我们回到例子中,第25-30行就用到了下标访谒操作符的get了,相关输出如下:



tolua源码分析(三) C#访谒lua变量

get的实现与set非常相似:
  1. public object this[string fullPath]
  2. {
  3.     get
  4.     {
  5.         int oldTop = LuaGetTop();
  6.         int pos = fullPath.LastIndexOf(&#39;.&#39;);
  7.         object obj = null;
  8.         if (pos > 0)
  9.         {
  10.             string tableName = fullPath.Substring(0, pos);
  11.             if (PushLuaTable(tableName))
  12.             {
  13.                 string name = fullPath.Substring(pos + 1);
  14.                 LuaPushString(name);
  15.                 LuaRawGet(-2);
  16.                 obj = ToVariant(-1);
  17.             }   
  18.             else
  19.             {
  20.                 LuaSetTop(oldTop);
  21.                 return null;
  22.             }
  23.         }
  24.         else
  25.         {
  26.             LuaGetGlobal(fullPath);
  27.             obj = ToVariant(-1);
  28.         }
  29.         LuaSetTop(oldTop);
  30.         return obj;
  31.     }
  32.     set
  33.     {
  34.         ...
  35.     }
  36. }
复制代码
一个区别就是这里使用的是PushLuaTable,该函数的感化就是将C#指定的table压入到lua栈中,但是如果该table不存在,并不会新建,而是会返回false。
  1. bool PushLuaTable(string fullPath, bool checkMap = true)
  2. {
  3.     if (checkMap)
  4.     {
  5.         WeakReference weak = null;
  6.         if (funcMap.TryGetValue(fullPath, out weak))
  7.         {
  8.             if (weak.IsAlive)
  9.             {
  10.                 LuaTable table = weak.Target as LuaTable;
  11.                 CheckNull(table, ”{0} not a lua table”, fullPath);
  12.                 Push(table);
  13.                 return true;
  14.             }
  15.             else
  16.             {
  17.                 funcMap.Remove(fullPath);
  18.             }
  19.         }
  20.     }
  21.     if (!LuaDLL.tolua_pushluatable(L, fullPath))
  22.     {               
  23.         return false;
  24.     }
  25.     return true;
  26. }
复制代码
这个函数和上一节里说的PushLuaFunction类似,会先去查找C#缓存,如果缓存中存在,就直接通过缓存中记录的reference,调用lua_getref拿到对应的table,从而避免了每次要去解析字符串,递归查找table的过程。拿到table之后,再将key压入栈,就能得到value了。
同样地,这里有一个ToVariant,会按照lua栈上object的类型,调用分歧的转换函数得到C#的object。例子中value的类型为number和function,对于number来说,直接调用lua_tonumber即可,而对于function来说,则稍微复杂一些:
  1. public static object ToVarObject(IntPtr L, int stackPos)
  2. {
  3.     LuaTypes type = LuaDLL.lua_type(L, stackPos);
  4.     switch (type)
  5.     {
  6.         case LuaTypes.LUA_TNUMBER:                    
  7.             return LuaDLL.lua_tonumber(L, stackPos);
  8.         case LuaTypes.LUA_TFUNCTION:
  9.             return ToLuaFunction(L, stackPos);
  10.         ...
  11.         default:
  12.             return null;
  13.     }
  14. }
  15. public static LuaFunction ToLuaFunction(IntPtr L, int stackPos)
  16. {
  17.     LuaTypes type = LuaDLL.lua_type(L, stackPos);
  18.     if (type == LuaTypes.LUA_TNIL)
  19.     {
  20.         return null;
  21.     }
  22.     stackPos = LuaDLL.abs_index(L, stackPos);
  23.     LuaDLL.lua_pushvalue(L, stackPos);
  24.     int reference = LuaDLL.toluaL_ref(L);
  25.     return LuaStatic.GetFunction(L, reference);
  26. }
复制代码
通过这种方式,从lua栈上获取lua函数时,需要在lua层和C#层都进行缓存,因为这个函数相当于同时被lua层和C#层引用了。上一节中我们提到,lua层缓存的方式走的是toluaL_ref,得到reference后返回给C#层。然后,C#层会测验考试先从缓存中查找reference,如果找到会增加其引用计数,找不到就将reference绑定到一个新的LuaFunction对象上:
  1. LuaBaseRef TryGetLuaRef(int reference)
  2. {            
  3.     WeakReference weak = null;
  4.     if (funcRefMap.TryGetValue(reference, out weak))
  5.     {
  6.         if (weak.IsAlive)
  7.         {
  8.             LuaBaseRef luaRef = (LuaBaseRef)weak.Target;
  9.             if (luaRef.IsAlive)
  10.             {
  11.                 luaRef.AddRef();
  12.                 return luaRef;
  13.             }
  14.         }               
  15.         funcRefMap.Remove(reference);               
  16.     }
  17.     return null;
  18. }
  19. public LuaFunction GetFunction(int reference)
  20. {
  21.     LuaFunction func = TryGetLuaRef(reference) as LuaFunction;
  22.     if (func == null)
  23.     {               
  24.         func = new LuaFunction(reference, this);
  25.         funcRefMap.Add(reference, new WeakReference(func));
  26.         if (LogGC) Debugger.Log(”Alloc LuaFunction name , id {0}”, reference);      
  27.     }
  28.     RemoveFromGCList(reference);
  29.     return func;
  30. }
复制代码
下面一种方式,lua.GetTable是将lua层的table取出,C#层进行缓存的方式。注意它跟前面调用的PushLuaTable的区别。PushLuaTable这个函数不会增加缓存,也不会增加引用,这是因为它对应的table只是个临时发生的变量;而这里的GetTable并分歧,因此获取的LuaTable会在lua层和C#层都进行一遍缓存,缓存的机制和前面类似,就不展开了。
  1. public LuaTable GetTable(string fullPath, bool beLogMiss = true)
  2. {
  3.     WeakReference weak = null;
  4.     if (funcMap.TryGetValue(fullPath, out weak))
  5.     {
  6.         if (weak.IsAlive)
  7.         {
  8.             LuaTable table = weak.Target as LuaTable;
  9.             CheckNull(table, ”{0} not a lua table”, fullPath);
  10.             if (table.IsAlive)
  11.             {
  12.                 table.AddRef();
  13.                 RemoveFromGCList(table.GetReference());
  14.                 return table;
  15.             }
  16.         }
  17.         funcMap.Remove(fullPath);
  18.     }
  19.     if (PushLuaTable(fullPath, false))
  20.     {
  21.         int reference = ToLuaRef();
  22.         LuaTable table = null;
  23.         if (funcRefMap.TryGetValue(reference, out weak))
  24.         {
  25.             if (weak.IsAlive)
  26.             {
  27.                 table = weak.Target as LuaTable;
  28.                 CheckNull(table, ”{0} not a lua table”, fullPath);
  29.                 if (table.IsAlive)
  30.                 {
  31.                     funcMap.Add(fullPath, weak);
  32.                     table.AddRef();
  33.                     RemoveFromGCList(reference);
  34.                     return table;
  35.                 }
  36.             }
  37.             funcRefMap.Remove(reference);
  38.         }
  39.         table = new LuaTable(reference, this);
  40.         table.name = fullPath;
  41.         funcMap.Add(fullPath, new WeakReference(table));
  42.         funcRefMap.Add(reference, new WeakReference(table));
  43.         if (LogGC) Debugger.Log(”Alloc LuaTable name {0}, id {1}”, fullPath, reference);     
  44.         RemoveFromGCList(reference);
  45.         return table;
  46.     }
  47.     if (beLogMiss)
  48.     {
  49.         Debugger.LogWarning(”Lua table {0} not exists”, fullPath);
  50.     }
  51.     return null;
  52. }
复制代码
C#层的LuaTable,具有和lua层的table类似的操作,第33-35行展示了操作下标访谒,对lua层的table读取和写入:



tolua源码分析(三) C#的LuaTable

LuaTable类的下标访谒操作符key的类型只撑持string和int,不外对于绝大部门情况已经完全够用。string类型和int类型的实现大同小异,这里以例子顶用到的string为例,来看看具体的实现:
  1. public object this[string key]
  2. {
  3.     get
  4.     {
  5.         int top = luaState.LuaGetTop();
  6.         try
  7.         {
  8.             luaState.Push(this);
  9.             luaState.Push(key);
  10.             luaState.LuaGetTable(top + 1);
  11.             object ret = luaState.ToVariant(top + 2);
  12.             luaState.LuaSetTop(top);
  13.             return ret;
  14.         }
  15.         catch (Exception e)
  16.         {
  17.             luaState.LuaSetTop(top);
  18.             throw e;                    
  19.         }               
  20.     }
  21.     set
  22.     {
  23.         int top = luaState.LuaGetTop();
  24.         try
  25.         {
  26.             luaState.Push(this);
  27.             luaState.Push(key);
  28.             luaState.PushVariant(value);
  29.             luaState.LuaSetTable(top + 1);
  30.             luaState.LuaSetTop(top);
  31.         }
  32.         catch (Exception e)
  33.         {
  34.             luaState.LuaSetTop(top);
  35.             throw e;
  36.         }
  37.     }
  38. }
复制代码
这个实现就是在C#层模拟了一遍对lua栈的操作,值得一提的是对LuaTable进行push,实际上等价于将lua层对应的table,push到lua栈顶,而不是真的push了一个C#对象到lua栈顶。因此,这里需要把LuaTable记录的reference取出,然后调用lua_getref到lua层取出真正的table:
  1. public void Push(LuaBaseRef lbr)
  2. {
  3.     if (lbr == null)
  4.     {               
  5.         LuaPushNil();
  6.     }
  7.     else
  8.     {
  9.         LuaGetRef(lbr.GetReference());
  10.     }
  11. }
复制代码
接着看例子的第37-41行,它展示了如安在C#层新建一个table。



tolua源码分析(三) C#层新建table

重点就在这个AddTable上,这个函数做的事情,其实也就是在C#层,模拟在lua栈上创建table的操作:
  1. public void AddTable(string name)
  2. {
  3.     int oldTop = luaState.LuaGetTop();
  4.     try
  5.     {
  6.         luaState.Push(this);
  7.         luaState.Push(name);
  8.         luaState.LuaCreateTable();               
  9.         luaState.LuaRawSet(oldTop + 1);
  10.         luaState.LuaSetTop(oldTop);
  11.     }
  12.     catch (Exception e)
  13.     {
  14.         luaState.LuaSetTop(oldTop);
  15.         throw e;
  16.     }
  17. }
复制代码
由于我们还需要把lua层新建的table转换成C#对应的LuaTable返回,因此这里还会用到之前提到的ToVarObject函数,在lua层生成reference,然后返回给C#层,查找对应的C#缓存,如果不存在就新建一个LuaTable对象。
  1. public static object ToVarObject(IntPtr L, int stackPos)
  2. {
  3.     LuaTypes type = LuaDLL.lua_type(L, stackPos);
  4.     switch (type)
  5.     {
  6.         case LuaTypes.LUA_TTABLE:
  7.             return ToVarTable(L, stackPos);
  8.         ...
  9.         default:
  10.             return null;
  11.     }
  12. }
复制代码
接着看例子,下一个是在C#层去访谒lua层设置的metatable。实现方式与前面所说的类似,还是C#层直接调用lua的API,模拟栈操作。



tolua源码分析(三) C#层访谒metatable

例子的最后,演示了LuaTable.ToArray方式。通过这个方式,可以便利地将lua层的table转换为C#的数组。



tolua源码分析(三) LuaTable.ToArray

类似地,该方式的实现,就是在C#层,调用lua的lua_objlen函数,得到当前table的长度,然后在C#层对该table进行遍历,把成果塞到C#的数组中:
  1. public object[] ToArray()
  2. {
  3.     int oldTop = luaState.LuaGetTop();
  4.     try
  5.     {
  6.         luaState.Push(this);
  7.         int len = luaState.LuaObjLen(-1);
  8.         List<object> list = new List<object>(len + 1);
  9.         int index = 1;
  10.         object obj = null;
  11.         while(index <= len)
  12.         {
  13.             luaState.LuaRawGetI(-1, index++);
  14.             obj = luaState.ToVariant(-1);
  15.             luaState.LuaPop(1);
  16.             list.Add(obj);
  17.         }               
  18.         luaState.LuaSetTop(oldTop);
  19.         return list.ToArray();
  20.     }
  21.     catch (Exception e)
  22.     {
  23.         luaState.LuaSetTop(oldTop);
  24.         throw e;
  25.     }
  26. }
复制代码
下一节我们将揭开lua层如何调用C#方式的面纱,先从C#的数组开始。
如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号我是真的想做游戏啊

Reference
[1] Learning Lua: 5 - Document for luaL_findtable() function

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

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

本版积分规则

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

GMT+8, 2024-11-21 21:31 , Processed in 0.100936 second(s), 28 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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