|
上一篇主要介绍了xLua的一些基本的使用方法,从本篇开始,将介绍一些原理方面的东西,首先将介绍LuaEnv这个类。
以上两个链接分别是xLua中的xLua.c源码和Lua 5.3的参考手册
LuaEnv
XLua框架中最重要的一个类,那就是LuaEnv。它包含了lua中的状态机RealStatePrt。lua的G表,还有注册表LuaIndexes.LUA_REGISTRYINDEX等等,下面从LuaEnv的构造函数开始,看看这个类做了些什么事情
//节选LuaEnv构造函数部分代码
//拿到Lua中的注册表
LuaIndexes.LUA_REGISTRYINDEX = LuaAPI.xlua_get_registry_index
//创建Lua状态机
rawL = LuaAPI.luaL_newstate()
//十分重要的一个对象,用于c#和lua的交互
translator = new ObjectTranslator(this, rawL);
translator.createFunctionMetatable(rawL); //添加_gc元方法到注册表
translator.OpenLib(rawL); //将init_xlua中会用到的方法,全部定义出来
//添加CS,socket的回调
AddSearcher(StaticLuaCallbacks.LoadBuiltinLib, 2);
//添加自定义解析Lua文件的方法,对应的是LuaEnv.CustomLoader
AddSearcher(StaticLuaCallbacks.LoadFromCustomLoaders, 3);
#if !XLUA_GENERAL
AddSearcher(StaticLuaCallbacks.LoadFromResource, 4);
AddSearcher(StaticLuaCallbacks.LoadFromStreamingAssetsPath, -1);
#endif
//十分重要!! 初始化xLua
DoString(init_xlua, "Init");
#if !UNITY_SWITCH || UNITY_EDITOR
AddBuildin("socket.core", StaticLuaCallbacks.LoadSocketCore);
AddBuildin("socket", StaticLuaCallbacks.LoadSocketCore);
#endif
AddBuildin("CS", StaticLuaCallbacks.LoadCS);
注释写的很清楚了。translator部分下一篇会着重介绍。DoString(init_xlua,"Init")接下来会介绍。还有需要注意是调用AddBuildin的地方,会把c#中的回调映射到lua中,也就是说在lua中调用scoket.core,实际上调用的是StaticLuaCallbacks.LoadSocketCore方法.
DoString(init_xlua,"Init")
private string init_xlua = @"
local metatable = {}
local rawget = rawget
local setmetatable = setmetatable
local import_type = xlua.import_type
local import_generic_type = xlua.import_generic_type
local load_assembly = xlua.load_assembly
function metatable:__index(key)
local fqn = rawget(self,'.fqn')
fqn = ((fqn and fqn .. '.') or '') .. key
--fqn就是类型和命名空间名,通过import_type去获取对应的udata并且入栈
local obj = import_type(fqn)
if obj == nil then
-- It might be an assembly, so we load it too.
obj = { ['.fqn'] = fqn }
setmetatable(obj, metatable)
elseif obj == true then
--从自身这个类的元表中获取key对应的值
return rawget(self, key)
end
-- Cache this lookup
rawset(self, key, obj)
return obj
end
function metatable:__newindex()
error('No such type: ' .. rawget(self,'.fqn'), 2)
end
function metatable:__call(...)
local n = select('#', ...)
local fqn = rawget(self,'.fqn')
if n > 0 then
local gt = import_generic_type(fqn, ...)
if gt then
return rawget(CS, gt)
end
end
error('No such type: ' .. fqn, 2)
end
CS = CS or {}
setmetatable(CS, metatable)
typeof = function(t) return t.UnderlyingSystemType end
//...这一部分十分重要,简而言之就是定义了在Lua中访问c#对象时进行的具体操作.
因为在Lua中定义了全局的CS表,并且在lua中调用CS的时候,会先调用StaticLuaCallbacks.LoadCS获取到注册表中的CS表,然后在调用XXX的时候,如果访问到了CS表中不存在的元素,则会调用其元表,在元表中通过映射到c#的StaticLuaCallbacks.ImportType方法完成查找。这个查找的具体过程在下面会具体介绍。
//略过LuaEnv构造函数部分代码.....
//等价于LuaIndexes.LUA_REGISTRYINDEX[CSHARP_NAMESPACE] = CS Table
LuaAPI.xlua_pushasciistring(rawL, CSHARP_NAMESPACE)
if (0 != LuaAPI.xlua_getglobal(rawL, "CS"))
throw new Exception("get CS fail!");
LuaAPI.lua_rawset(rawL, LuaIndexes.LUA_REGISTRYINDEX);
if (0 != LuaAPI.xlua_getglobal(rawL, "_G"))
throw new Exception("get _G fail!");
translator.Get(rawL, -1, out _G);
LuaAPI.lua_pop(rawL, 1);
//生成预定义的类的wrap文件
if (initers != null)
{
for (int i = 0; i < initers.Count; i++)
{
initers(this, translator);
}
}
//给数组生成元方法
translator.CreateArrayMetatable(rawL);
//给委托生成元方法
translator.CreateDelegateMetatable(rawL);
//创建遍历迭代器的方法
translator.CreateEnumerablePairs(rawL);
注释写的很清楚了,要注意的一点就是initers,在调用ObjectTranslator的时候,会调用到XLua_Gen_Initer_Register__的构造函数,在这个构造函数内会添加XLua_Gen_Initer_Register__.Init,在这个Init方法内会添加生成中间代码的回调方法,把回调方法和类型type缓存到ObjectTranslator.delayWrap中
所以在这里调用initers(this, translator)的时候其实就是把内置c#对象生成中间代码的函数保存到ObjectTranslator中,方便后续在lua中访问c#对象时生成中间代码。
StaticLuaCallback.ImportType(RealStatePtr L)
//StaticLuaCallbacks.cs
[MonoPInvokeCallback(typeof(LuaCSFunction))]
public static int ImportType(RealStatePtr L)
{
try
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
string className = LuaAPI.lua_tostring(L, 1);
Type type = translator.FindType(className);
if (type != null)
{
//获取该类型对应的typeId
if (translator.GetTypeId(L, type) >= 0)
LuaAPI.lua_pushboolean(L, true);
else
return LuaAPI.luaL_error(L, &#34;can not load type &#34; + type);
}
else
{
LuaAPI.lua_pushnil(L);
}
return 1;
}
catch (System.Exception e)
{
return LuaAPI.luaL_error(L, &#34;c# exception in xlua.import_type:&#34; + e);
}
}
在init.lua中调用import_type(fqn)的时候,会把fqn传过来,fqn也是就类型的名称,然后通过translator,FindType查找该类型,如果能找到,则通过translator.GetTypeId()判断在Lua注册表中该类型是否有生成对应的元表,如果不存在则进行创建,创建成功之后,import_type返回true,否则返回nil。
至于translator.GetTypeId()具体的实现原理,可以查看下面这篇
接下来将介绍lua中访问c#对象的实现原理。
首先思考一个例子
local go = CS.UnityEngine.GameObject()
local transform = go:GetComponent(typeof(CS.UnityEngine.Transform))
上述lua代码将会创建一个gameObject对象,并且持有transform控件,那么lua代码是如何调用GetComponent从而获取到Transform这个控件的?
UnityEngineGameObjectWrap
在创建ObjectTranslator对象的时候,经过一系列初始化之后,会调用的部分内置类型的__Register方法,也就是下面这个方法。
public static void __Register(RealStatePtr L)
{
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
System.Type type = typeof(UnityEngine.GameObject);
//给GameObject这个类的非静态值创建元表,并且在元表中加入元方法_gc,__tostring. 然后再加入method, getter, setter这三个表
//meta: -4, method:-3, getter: -2, setter: -1
Utils.BeginObjectRegister(type, L, translator, 0, 12, 8, 3);
//在-3的表(method)表中加入&#34;GetComponent&#34;字段,对应c# 中的 _m_GetComponent方法
Utils.RegisterFunc(L, Utils.METHOD_IDX, &#34;GetComponent&#34;, _m_GetComponent);
...
//在-2的表(getter)表中加入&#34;transform&#34;字段,对应c#中的_g_get_transform属性
Utils.RegisterFunc(L, Utils.GETTER_IDX, &#34;transform&#34;, _g_get_transform);
...
//在-1的表(setter)表中加入&#34;layer&#34;字段,对应c#中的_s_set_layer属性
Utils.RegisterFunc(L, Utils.SETTER_IDX, &#34;layer&#34;, _s_set_layer);
...
//这个方法主要用于创建元方法__index和__newindex,通过绑定c闭包的方式实现,然后将设置好的元方法放到注册表中。
Utils.EndObjectRegister(type, L, translator, null, null, null, null, null);
//这个方法主要是给GameObject的静态值(静态方法,静态对象)设置元表,与上述非静态值做的事情差不多
Utils.BeginClassRegister(type, L, __CreateInstance, 6, 0, 0);
//在-4的表(类的静态表cls_idx)表中加入&#34;transform&#34;字段,对应c#中的_g_get_transform属性
Utils.RegisterFunc(L, Utils.CLS_IDX, &#34;CreatePrimitive&#34;, _m_CreatePrimitive_xlua_st_);
...
//给静态表设置元方法__index和__newindex,
Utils.EndClassRegister(type, L, translator);
}
上面节选代码,主要分成两部分,都是通过注册回调的方式实现的.
第一部分是给GameObject对象生成GetComponent等方法,再生成transform的get属性和layer的set属性。也就是上述代码中&#34;GetComponent&#34;对应_m_GetComponent方法。
第二部分给GameObject静态类生成CreatePrimitive静态方法和对应的静态get,set属性.
下面介绍类成员对象中方法和属性的生成原理,碍于篇幅原因,类的静态方法和属性生成实现原理单独开一篇介绍
Utils.BeginObjectRegister
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(&#34;Fatal: must provide a type of type_id&#34;);
LuaAPI.xlua_rawgeti(L, LuaIndexes.LUA_REGISTRYINDEX, type_id);
}
else
{
LuaAPI.luaL_getmetatable(L, type.FullName);
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);
//为元表设置__gc元方法
if ((type == null || !translator.HasCustomOp(type)) && type != typeof(decimal))
{
LuaAPI.xlua_pushasciistring(L, &#34;__gc&#34;);
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.GcMeta);
LuaAPI.lua_rawset(L, -3);
}
//为元表设置__tostring元方法
LuaAPI.xlua_pushasciistring(L, &#34;__tostring&#34;);
LuaAPI.lua_pushstdcallcfunction(L, translator.metaFunctions.ToStringMeta);
LuaAPI.lua_rawset(L, -3);
//创建methor元表
if (method_count == 0)
LuaAPI.lua_pushnil(L);
else
LuaAPI.lua_createtable(L, 0, method_count);
//创建getter元表
if (getter_count == 0)
LuaAPI.lua_pushnil(L);
else
LuaAPI.lua_createtable(L, 0, getter_count);
//创建setter元表
if (setter_count == 0)
LuaAPI.lua_pushnil(L);
else
LuaAPI.lua_createtable(L, 0, setter_count);
}
BeginObjectResigter方法主要是Lua注册表中给类添加元表,给该元表设置__gc和__tostring元方法,然后再加入methods表用于添加成员方法、getter表用于访问成员属性、setter表用于设置成员属性。
Utils.RegisterFunc
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);
}
RegisterFunc方法简单来说就是把name对应的func设置到索引为idx的表中,也就是meta, method,getter和setter其中之一。
比如 Utils.RegisterFunc(L, Utils.METHOD_IDX,&#34;GetComponent&#34;, _m_GetComponent);这一行代码,就会在method表中加入&#34;GetComponent&#34;字段和对应的_m_GetComponent方法,当我们在Lua中调用obj:GetComponent()的时候,就会访问到c#中的_m_GetComponent方法了
Utils.EndObjectRegister
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);
LuaAPI.xlua_pushasciistring(L, &#34;__index&#34;);
LuaAPI.lua_pushvalue(L, method_idx); // 1. methods入栈
LuaAPI.lua_pushvalue(L, getter_idx); // 2. getters入栈
if (csIndexer == null)
LuaAPI.lua_pushnil(L);
else
LuaAPI.lua_pushstdcallcfunction(L, csIndexer); // 3. csindexer入栈
translator.Push(L, type == null ? base_type : type.BaseType()); // 4. base入栈
LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX); // 5. indexfuncs表入栈
if (arrayIndexer == null)
LuaAPI.lua_pushnil(L);
else
LuaAPI.lua_pushstdcallcfunction(L, arrayIndexer); // 6. arrayindexer 入栈
LuaAPI.gen_obj_indexer(L); // 生成__index元方法, 实际上是一个c闭包closure,具体实现下面介绍
if (type != null)
{
LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
translator.Push(L, type);
LuaAPI.lua_pushvalue(L, -3);
LuaAPI.lua_rawset(L, -3); // 注册表[LuaIndexsFieldName][type] = closure函数
LuaAPI.lua_pop(L, 1);
}
LuaAPI.lua_rawset(L, meta_idx); //把closure保存到meta表中
//end index gen
//begin newindex gen
// __newindex生成方法大同小异,略过
LuaAPI.xlua_pushasciistring(L, &#34;__newindex&#34;);
//end new index gen
LuaAPI.lua_pop(L, 4);
}
上述方法首先调用 LuaAPI.gen_obj_indexer(L)生成一个c闭包,实现原理下面马上介绍.拿到这个闭包之后,再把这个闭包添加到注册表 LuaIndexes.LUA_REGISTRYINDEX[LuaIndexsFieldName][type]中,也就是和type一一对应起来,这个就是生成 __index元方法的具体实现了,后面的__newindex方法实现也是一样的原理。
下面介绍gen_obj_indexer的具体实现。这个方法内部会压入一个nil,再加上EndObjectRegister方法中另外6个压入栈中的一共7个参数传入lua_pushcclosure方法中,也就是 __index具体实现逻辑.
每当在Lua中访问某个c#对象方法或属性的时候,会依次在methods, getters, arrayindexer, csindexer表中查找,如果找不到,就会访问父类base表。__newindex实现原理也基本一致,就不在阐述
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) {
//在methods表中找
if (!lua_isnil(L, lua_upvalueindex(1))) {
lua_pushvalue(L, 2);
lua_gettable(L, lua_upvalueindex(1));
if (!lua_isnil(L, -1)) {//has method
return 1;
}
lua_pop(L, 1);
}
//在getters表中查找
if (!lua_isnil(L, lua_upvalueindex(2))) {
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);
}
//在arrayindexer表中查找,然后调用对应的方法
if (!lua_isnil(L, lua_upvalueindex(6)) && lua_type(L, 2) == LUA_TNUMBER) {
lua_pushvalue(L, lua_upvalueindex(6));
lua_pushvalue(L, 1);
lua_pushvalue(L, 2);
lua_call(L, 2, 1);
return 1;
}
//在csindexer表中查找
if (!lua_isnil(L, lua_upvalueindex(3))) {
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);
}
//查找base表
if (!lua_isnil(L, lua_upvalueindex(4))) {
lua_pushvalue(L, lua_upvalueindex(4));//base表入栈
while(!lua_isnil(L, -1)) {
lua_pushvalue(L, -1);//base表复制一份再入栈
lua_gettable(L, lua_upvalueindex(5));//indexfuncs[base]入栈,base出栈
if (!lua_isnil(L, -1)) // found
{
//baseindex = indexfuncs[base] ,然后再把indexfuncs[base]出栈
lua_replace(L, lua_upvalueindex(7));
lua_pop(L, 1);
break;
}
//如果indexfuncs中找不到base,则把indexfuncs[base]也就是nil出栈
lua_pop(L, 1);
//把base[BaseType]入栈,查找BaseType字段
lua_getfield(L, -1, &#34;BaseType&#34;)
//把base出栈
lua_remove(L, -2);
}
//没有找到
lua_pushnil(L);
lua_replace(L, lua_upvalueindex(4));//base = nil
}
//如果上一步能找到,也就是 baseindex = indexfuncs[base]这一步,那就调用此处的方法
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);
return 1;
} else {
return 0;
}
}
下面选择GameObjectWrap中的_m_GetComponent方法,说明Lua中是如何访问c#类成员方法的.
[MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
static int _m_GetComponent(RealStatePtr L)
{
try {
ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
//拿到gameObject对象
UnityEngine.GameObject gen_to_be_invoked = (UnityEngine.GameObject)translator.FastGetCSObj(L, 1);
//获取参数数量,这里关键,Lua中的重载主要通过这里实现
int gen_param_count = LuaAPI.lua_gettop(L);
if(gen_param_count == 2&& translator.Assignable<System.Type>(L, 2))
{
//获取对应的类型
System.Type _type = (System.Type)translator.GetObject(L, 2, typeof(System.Type));
UnityEngine.Component gen_ret = gen_to_be_invoked.GetComponent( _type );
//简单来说就是把找到的东西压入栈
translator.Push(L, gen_ret);
return 1;
}
if(gen_param_count == 2&& (LuaAPI.lua_isnil(L, 2) || LuaAPI.lua_type(L, 2) == LuaTypes.LUA_TSTRING))
{
string _type = LuaAPI.lua_tostring(L, 2);
UnityEngine.Component gen_ret = gen_to_be_invoked.GetComponent( _type );
translator.Push(L, gen_ret);
return 1;
}
} catch(System.Exception gen_e) {
return LuaAPI.luaL_error(L, &#34;c# exception:&#34; + gen_e);
}
return LuaAPI.luaL_error(L, &#34;invalid arguments to UnityEngine.GameObject.GetComponent!&#34;);
}
注释写的很清楚了,首先在Lua中调用obj:GetComponent(&#34;xxxx&#34;),这时候第一个参数就是obj,第二个参数是&#34;xxx&#34;,两个参数通过栈到了c#这边,看到此处主要判断第二个参数实现重载,如果是类型就通过translator.GetObject获取,如果是string就直接访问。到这里就已经完全介绍了在Lua中是访问c#对象方法的全部实现原理了。
看到这里可以思考一个问题——Lua中是如何持有c#对象的。这个问题将在下一篇中详细介绍。 |
|