|
在上一篇文章中介绍了XLua是如何给类对象生成方法和属性的,碍于篇幅,给类的静态方法和属性生成代码的实现原理将在这篇文章中详细介绍.
BeginClassRegister()
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);
//创建一个空table
LuaAPI.lua_createtable(L, 0, class_field_count);
//这个地方用于Lua中的typeof
LuaAPI.xlua_pushasciistring(L, "UnderlyingSystemType");
translator.PushAny(L, type);
LuaAPI.lua_rawset(L, -3);
int cls_table = LuaAPI.lua_gettop(L);
//在注册表中注册对应的CS.UnityEngine.type
SetCSTable(L, type, cls_table);//重要 !!
//在创建一个空表,用于加入__call方法,getter和setter
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);
if (static_setter_count == 0)
LuaAPI.lua_pushnil(L);
else
LuaAPI.lua_createtable(L, 0, static_setter_count);
//最后为该类型设置元表
LuaAPI.lua_pushvalue(L, meta_table);
LuaAPI.lua_setmetatable(L, cls_table);
}
注释中写的很清楚了,主要就是为该类型生成一个空表,首先加入typeof方法,然后调用SetCSTable在注册表中添加CS.UnityEngine.XXXX表,用于在Lua中直接当成静态类来访问(具体实现下面马上介绍)。然后又创建一个空表,加入了__call方法,然后又创建setter和getter表,最后为该类型创建的表设置元表
SetCSTable()
public static void SetCSTable(RealStatePtr L, Type type, int cls_table)
{
// oldTop = cls_table
int oldTop = LuaAPI.lua_gettop(L);
cls_table = abs_idx(oldTop, cls_table);
// 重要!! 在Lua的注册表中拿到CS表,也就是LuaEnv.InitLua中创建的那个CS全局表
LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
//拿到className和spaceName
List<string> path = getPathOfType(type);
//假如有A.B.C
//在for循环中将会创建出 注册表[CS][A][B][C] = {}
for (int i = 0; i < path.Count - 1; ++i)
{
LuaAPI.xlua_pushasciistring(L, path);
if (0 != LuaAPI.xlua_pgettable(L, -2))
{
var err = LuaAPI.lua_tostring(L, -1);
LuaAPI.lua_settop(L, oldTop);
throw new Exception(&#34;SetCSTable for [&#34; + type + &#34;] error: &#34; + err);
}
//如果注册表[CS]没有path, 则创建一个然后空table入栈
if (LuaAPI.lua_isnil(L, -1))
{
LuaAPI.lua_pop(L, 1);
LuaAPI.lua_createtable(L, 0, 0);
LuaAPI.xlua_pushasciistring(L, path);
LuaAPI.lua_pushvalue(L, -2);
LuaAPI.lua_rawset(L, -4);
}
else if (!LuaAPI.lua_istable(L, -1))
{
LuaAPI.lua_settop(L, oldTop);
hrow new Exception(&#34;SetCSTable for [&#34; + type + &#34;] error: ancestors is not a table!&#34;);
}
LuaAPI.lua_remove(L, -2);
}
// 注册表[CS][A][B][C] = cls_table
LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]);
LuaAPI.lua_pushvalue(L, cls_table);
LuaAPI.lua_rawset(L, -3);
LuaAPI.lua_pop(L, 1);
//注册表[该type对应的userData] = cls_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);
}
看注释得知SetCSTable方法主要是给c#类在 注册表[CS] 中定义出相关类型的表,包括类的命名空间在内,最后再把该类对应的userData指向上面定义的cls_table。所以我们就可以在Lua中直接使用CS.AAAA.BBB的方式访问到对应的类,前提是这个类必须要有生成代码.
经过上述的两个方法之后,还需要再调用EndClassRegister()。
EndClassRegister()
public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator)
{
//top = 4
//BeginClassRegister方发中压入的,从上到下依次是setter/getter/meta_table/cls_table
int top = LuaAPI.lua_gettop(L);
//cls_idx = 4 + (-4) + 1 = 1
int cls_idx = abs_idx(top, CLS_IDX);
//cls_getter_idx = 4 + (-2) + 1 = 3
int cls_getter_idx = abs_idx(top, CLS_GETTER_IDX);
//cls_setter_idx = 4 + (-1) + 1 = 4
int cls_setter_idx = abs_idx(top, CLS_SETTER_IDX);
//cls_meta_idx = 4 + (-3) + 1 = 2
int cls_meta_idx = abs_idx(top, CLS_META_IDX);
//begin cls index
LuaAPI.xlua_pushasciistring(L, &#34;__index&#34;); //压入__index
LuaAPI.lua_pushvalue(L, cls_getter_idx);//压入getter
LuaAPI.lua_pushvalue(L, cls_idx); //压入cls_idx
translator.Push(L, type.BaseType()); //压入基类
LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); //拿到 注册表[LuaClassIndexsFieldName]
LuaAPI.gen_cls_indexer(L);//创建__index闭包
LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//拿到 注册表[LuaClassIndexsFieldName]
translator.Push(L, type); //压入类型对应的userData
LuaAPI.lua_pushvalue(L, -3);//压入__index闭包
LuaAPI.lua_rawset(L, -3); // 注册表[LuaClassIndexsFieldName][userData] = __index闭包
LuaAPI.lua_pop(L, 1);//弹出 注册表[LuaClassIndexsFieldName]
LuaAPI.lua_rawset(L, cls_meta_idx);//把__index闭包设置到基类表
//end cls index
//begin cls newindex
LuaAPI.xlua_pushasciistring(L, &#34;__newindex&#34;); //压入__newindex
LuaAPI.lua_pushvalue(L, cls_setter_idx); //压入setter
translator.Push(L, type.BaseType()); // 压入基类
LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); //拿到注册表[LuaClassNewIndexsFieldName]
LuaAPI.gen_cls_newindexer(L); //创建__newindex闭包
LuaAPI.xlua_pushasciistring(L, LuaClassNewIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); //拿到注册表[LuaClassNewIndexsFieldName]
translator.Push(L, type); //压入类型对应的userData
LuaAPI.lua_pushvalue(L, -3); //压入__newindex闭包
LuaAPI.lua_rawset(L, -3); // 注册表[LuaClassNewIndexsFieldName][userData] = __newindex闭包
LuaAPI.lua_pop(L, 1); //弹出 注册表[LuaClassNewIndexsFieldName]
LuaAPI.lua_rawset(L, cls_meta_idx); //把__newindex闭包设置到基类表
//end cls newindex
LuaAPI.lua_pop(L, 4);
}
注释写的很清楚了,概括来说就是创建两个个c函数闭包,把需要用的参数一并传入,然后把闭包设置成元方法__index和__newindex。
最后总结一下就是首先在BeginClassRegister方法中创建该类型对应的表cls_table,然后调用SetCSTable方法,为该类型在注册表中创建出对应的表(例如某个类全称是AAA.BBB.CCC,则会在注册表中创建出CS.AAA,CS.AAA.BBB, CS.AAA.BBB.CCC这三个表)。然后再把cls_table设置为CS.AAA.BBB.CCC对应的值,然后就可以在Lua中通过CS.AAA.BBB.CCC访问到这个类了。经过这两步操作之后,会调用EndClassRegister为该类型对应的表设置元方法_index和__newindex。
至此已经将类的静态方法和静态属性的生成和访问原理完全介绍了,并且介绍了前面文章提到的问题——为什么可以在Lua中通过http://CS.UnityEngine.XXX直接访问到XXX类。 |
|