|
上一节我们讨论了C#是如何获取并调用到lua定义的函数,这一节我们更进一步,来看看如何让C#可以访谒lua定义的变量的。依旧从一个例子看起,此次是tolua自带的example04,主要代码如下:- new LuaResLoader();
- LuaState lua = new LuaState();
- lua.Start();
- lua[”Objs2Spawn”] = 5;
- string script =
- @”
- print('Objs2Spawn is: '..Objs2Spawn)
- var2read = 42
- varTable = {1,2,3,4,5}
- varTable.default = 1
- varTable.map = {}
- varTable.map.name = 'map'
- meta = {name = 'meta'}
- setmetatable(varTable, meta)
- function TestFunc(strs)
- print('get func by variable')
- end
- ”;
- lua.DoString(script);
- Debugger.Log(”Read var from lua: {0}”, lua[”var2read”]);
- Debugger.Log(”Read table var from lua: {0}”, lua[”varTable.default”]);
- LuaFunction func = lua[”TestFunc”] as LuaFunction;
- func.Call();
- func.Dispose();
- LuaTable table = lua.GetTable(”varTable”);
- Debugger.Log(”Read varTable from lua, default: {0} name: {1}”, table[”default”], table[”map.name”]);
- table[”map.name”] = ”new”;
- Debugger.Log(”Modify varTable name: {0}”, table[”map.name”]);
- table.AddTable(”newmap”);
- LuaTable table1 = (LuaTable)table[”newmap”];
- table1[”name”] = ”table1”;
- Debugger.Log(”varTable.newmap name: {0}”, table1[”name”]);
- table1.Dispose();
- table1 = table.GetMetaTable();
- if (table1 != null)
- {
- Debugger.Log(”varTable metatable name: {0}”, table1[”name”]);
- }
- object[] list = table.ToArray();
- for (int i = 0; i < list.Length; i++)
- {
- Debugger.Log(”varTable[{0}], is {1}”, i, list[i]);
- }
- table.Dispose();
- lua.CheckTop();
- lua.Dispose();
复制代码 第1-3行对lua虚拟机进行了初始化,这块内容我们在第一节里已经详细展开过,这里就不再赘述。通过第4行,可以发现C#的LuaState类重载了下标访谒的操作符,使其扮演了lua中_G的角色,可以直接往lua层写入一个名为Objs2Spawn的变量,它的值为5。这一点在接下来执行的lua代码可以得到验证:
tolua源码分析(三) C#写入lua变量
下面就来看看下标访谒操作符的具体实现,这里用到的是set,那么就先忽略掉get相关的实现:- public object this[string fullPath]
- {
- get
- {
- ...
- }
- set
- {
- int oldTop = LuaGetTop();
- int pos = fullPath.LastIndexOf('.');
- if (pos > 0)
- {
- string tableName = fullPath.Substring(0, pos);
- IntPtr p = LuaFindTable(LuaIndexes.LUA_GLOBALSINDEX, tableName);
- if (p == IntPtr.Zero)
- {
- string name = fullPath.Substring(pos + 1);
- LuaPushString(name);
- PushVariant(value);
- LuaSetTable(-3);
- }
- else
- {
- LuaSetTop(oldTop);
- int len = LuaDLL.tolua_strlen(p);
- string str = LuaDLL.lua_ptrtostring(p, len);
- throw new LuaException(string.Format(”{0} not a Lua table”, str));
- }
- }
- else
- {
- PushVariant(value);
- LuaSetGlobal(fullPath);
- }
- LuaSetTop(oldTop);
- }
- }
复制代码 第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类型措置:- public static void Push(IntPtr L, object obj)
- {
- if (obj == null || obj.Equals(null))
- {
- LuaDLL.lua_pushnil(L);
- return;
- }
- Type t = obj.GetType();
- if (t.IsValueType)
- {
- if (TypeChecker.IsNullable(t))
- {
- Type[] ts = t.GetGenericArguments();
- t = ts[0];
- }
- if (t.IsPrimitive)
- {
- double d = LuaMisc.ToDouble(obj);
- LuaDLL.lua_pushnumber(L, d);
- }
- ...
- }
- else
- {
- ...
- }
- }
复制代码 看完了set,我们回到例子中,第25-30行就用到了下标访谒操作符的get了,相关输出如下:
tolua源码分析(三) C#访谒lua变量
get的实现与set非常相似:- public object this[string fullPath]
- {
- get
- {
- int oldTop = LuaGetTop();
- int pos = fullPath.LastIndexOf('.');
- object obj = null;
- if (pos > 0)
- {
- string tableName = fullPath.Substring(0, pos);
- if (PushLuaTable(tableName))
- {
- string name = fullPath.Substring(pos + 1);
- LuaPushString(name);
- LuaRawGet(-2);
- obj = ToVariant(-1);
- }
- else
- {
- LuaSetTop(oldTop);
- return null;
- }
- }
- else
- {
- LuaGetGlobal(fullPath);
- obj = ToVariant(-1);
- }
- LuaSetTop(oldTop);
- return obj;
- }
- set
- {
- ...
- }
- }
复制代码 一个区别就是这里使用的是PushLuaTable,该函数的感化就是将C#指定的table压入到lua栈中,但是如果该table不存在,并不会新建,而是会返回false。- bool PushLuaTable(string fullPath, bool checkMap = true)
- {
- if (checkMap)
- {
- WeakReference weak = null;
- if (funcMap.TryGetValue(fullPath, out weak))
- {
- if (weak.IsAlive)
- {
- LuaTable table = weak.Target as LuaTable;
- CheckNull(table, ”{0} not a lua table”, fullPath);
- Push(table);
- return true;
- }
- else
- {
- funcMap.Remove(fullPath);
- }
- }
- }
- if (!LuaDLL.tolua_pushluatable(L, fullPath))
- {
- return false;
- }
- return true;
- }
复制代码 这个函数和上一节里说的PushLuaFunction类似,会先去查找C#缓存,如果缓存中存在,就直接通过缓存中记录的reference,调用lua_getref拿到对应的table,从而避免了每次要去解析字符串,递归查找table的过程。拿到table之后,再将key压入栈,就能得到value了。
同样地,这里有一个ToVariant,会按照lua栈上object的类型,调用分歧的转换函数得到C#的object。例子中value的类型为number和function,对于number来说,直接调用lua_tonumber即可,而对于function来说,则稍微复杂一些:- public static object ToVarObject(IntPtr L, int stackPos)
- {
- LuaTypes type = LuaDLL.lua_type(L, stackPos);
- switch (type)
- {
- case LuaTypes.LUA_TNUMBER:
- return LuaDLL.lua_tonumber(L, stackPos);
- case LuaTypes.LUA_TFUNCTION:
- return ToLuaFunction(L, stackPos);
- ...
- default:
- return null;
- }
- }
- public static LuaFunction ToLuaFunction(IntPtr L, int stackPos)
- {
- LuaTypes type = LuaDLL.lua_type(L, stackPos);
- if (type == LuaTypes.LUA_TNIL)
- {
- return null;
- }
- stackPos = LuaDLL.abs_index(L, stackPos);
- LuaDLL.lua_pushvalue(L, stackPos);
- int reference = LuaDLL.toluaL_ref(L);
- return LuaStatic.GetFunction(L, reference);
- }
复制代码 通过这种方式,从lua栈上获取lua函数时,需要在lua层和C#层都进行缓存,因为这个函数相当于同时被lua层和C#层引用了。上一节中我们提到,lua层缓存的方式走的是toluaL_ref,得到reference后返回给C#层。然后,C#层会测验考试先从缓存中查找reference,如果找到会增加其引用计数,找不到就将reference绑定到一个新的LuaFunction对象上:- LuaBaseRef TryGetLuaRef(int reference)
- {
- WeakReference weak = null;
- if (funcRefMap.TryGetValue(reference, out weak))
- {
- if (weak.IsAlive)
- {
- LuaBaseRef luaRef = (LuaBaseRef)weak.Target;
- if (luaRef.IsAlive)
- {
- luaRef.AddRef();
- return luaRef;
- }
- }
- funcRefMap.Remove(reference);
- }
- return null;
- }
- public LuaFunction GetFunction(int reference)
- {
- LuaFunction func = TryGetLuaRef(reference) as LuaFunction;
- if (func == null)
- {
- func = new LuaFunction(reference, this);
- funcRefMap.Add(reference, new WeakReference(func));
- if (LogGC) Debugger.Log(”Alloc LuaFunction name , id {0}”, reference);
- }
- RemoveFromGCList(reference);
- return func;
- }
复制代码 下面一种方式,lua.GetTable是将lua层的table取出,C#层进行缓存的方式。注意它跟前面调用的PushLuaTable的区别。PushLuaTable这个函数不会增加缓存,也不会增加引用,这是因为它对应的table只是个临时发生的变量;而这里的GetTable并分歧,因此获取的LuaTable会在lua层和C#层都进行一遍缓存,缓存的机制和前面类似,就不展开了。- public LuaTable GetTable(string fullPath, bool beLogMiss = true)
- {
- WeakReference weak = null;
- if (funcMap.TryGetValue(fullPath, out weak))
- {
- if (weak.IsAlive)
- {
- LuaTable table = weak.Target as LuaTable;
- CheckNull(table, ”{0} not a lua table”, fullPath);
- if (table.IsAlive)
- {
- table.AddRef();
- RemoveFromGCList(table.GetReference());
- return table;
- }
- }
- funcMap.Remove(fullPath);
- }
- if (PushLuaTable(fullPath, false))
- {
- int reference = ToLuaRef();
- LuaTable table = null;
- if (funcRefMap.TryGetValue(reference, out weak))
- {
- if (weak.IsAlive)
- {
- table = weak.Target as LuaTable;
- CheckNull(table, ”{0} not a lua table”, fullPath);
- if (table.IsAlive)
- {
- funcMap.Add(fullPath, weak);
- table.AddRef();
- RemoveFromGCList(reference);
- return table;
- }
- }
- funcRefMap.Remove(reference);
- }
- table = new LuaTable(reference, this);
- table.name = fullPath;
- funcMap.Add(fullPath, new WeakReference(table));
- funcRefMap.Add(reference, new WeakReference(table));
- if (LogGC) Debugger.Log(”Alloc LuaTable name {0}, id {1}”, fullPath, reference);
- RemoveFromGCList(reference);
- return table;
- }
- if (beLogMiss)
- {
- Debugger.LogWarning(”Lua table {0} not exists”, fullPath);
- }
- return null;
- }
复制代码 C#层的LuaTable,具有和lua层的table类似的操作,第33-35行展示了操作下标访谒,对lua层的table读取和写入:
tolua源码分析(三) C#的LuaTable
LuaTable类的下标访谒操作符key的类型只撑持string和int,不外对于绝大部门情况已经完全够用。string类型和int类型的实现大同小异,这里以例子顶用到的string为例,来看看具体的实现:- public object this[string key]
- {
- get
- {
- int top = luaState.LuaGetTop();
- try
- {
- luaState.Push(this);
- luaState.Push(key);
- luaState.LuaGetTable(top + 1);
- object ret = luaState.ToVariant(top + 2);
- luaState.LuaSetTop(top);
- return ret;
- }
- catch (Exception e)
- {
- luaState.LuaSetTop(top);
- throw e;
- }
- }
- set
- {
- int top = luaState.LuaGetTop();
- try
- {
- luaState.Push(this);
- luaState.Push(key);
- luaState.PushVariant(value);
- luaState.LuaSetTable(top + 1);
- luaState.LuaSetTop(top);
- }
- catch (Exception e)
- {
- luaState.LuaSetTop(top);
- throw e;
- }
- }
- }
复制代码 这个实现就是在C#层模拟了一遍对lua栈的操作,值得一提的是对LuaTable进行push,实际上等价于将lua层对应的table,push到lua栈顶,而不是真的push了一个C#对象到lua栈顶。因此,这里需要把LuaTable记录的reference取出,然后调用lua_getref到lua层取出真正的table:- public void Push(LuaBaseRef lbr)
- {
- if (lbr == null)
- {
- LuaPushNil();
- }
- else
- {
- LuaGetRef(lbr.GetReference());
- }
- }
复制代码 接着看例子的第37-41行,它展示了如安在C#层新建一个table。
tolua源码分析(三) C#层新建table
重点就在这个AddTable上,这个函数做的事情,其实也就是在C#层,模拟在lua栈上创建table的操作:- public void AddTable(string name)
- {
- int oldTop = luaState.LuaGetTop();
- try
- {
- luaState.Push(this);
- luaState.Push(name);
- luaState.LuaCreateTable();
- luaState.LuaRawSet(oldTop + 1);
- luaState.LuaSetTop(oldTop);
- }
- catch (Exception e)
- {
- luaState.LuaSetTop(oldTop);
- throw e;
- }
- }
复制代码 由于我们还需要把lua层新建的table转换成C#对应的LuaTable返回,因此这里还会用到之前提到的ToVarObject函数,在lua层生成reference,然后返回给C#层,查找对应的C#缓存,如果不存在就新建一个LuaTable对象。- public static object ToVarObject(IntPtr L, int stackPos)
- {
- LuaTypes type = LuaDLL.lua_type(L, stackPos);
- switch (type)
- {
- case LuaTypes.LUA_TTABLE:
- return ToVarTable(L, stackPos);
- ...
- default:
- return null;
- }
- }
复制代码 接着看例子,下一个是在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#的数组中:- public object[] ToArray()
- {
- int oldTop = luaState.LuaGetTop();
- try
- {
- luaState.Push(this);
- int len = luaState.LuaObjLen(-1);
- List<object> list = new List<object>(len + 1);
- int index = 1;
- object obj = null;
- while(index <= len)
- {
- luaState.LuaRawGetI(-1, index++);
- obj = luaState.ToVariant(-1);
- luaState.LuaPop(1);
- list.Add(obj);
- }
- luaState.LuaSetTop(oldTop);
- return list.ToArray();
- }
- catch (Exception e)
- {
- luaState.LuaSetTop(oldTop);
- throw e;
- }
- }
复制代码 下一节我们将揭开lua层如何调用C#方式的面纱,先从C#的数组开始。
如果你感觉我的文章有辅佐,欢迎存眷我的微信公众号我是真的想做游戏啊
Reference
[1] Learning Lua: 5 - Document for luaL_findtable() function |
本帖子中包含更多资源
您需要 登录 才可以下载或查看,没有账号?立即注册
×
|