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

xlua源码分析

[复制链接]
发表于 2021-8-14 16:40 | 显示全部楼层 |阅读模式
xlua调用c#代码

在xlua中可以直接调用c#代码,例如:
  1. CS.UnityEngine.Debug.Log('hello world')
复制代码
CS是一个全局的Table,所以CS.UnityEngine可以当做是在一个名为CS的Table表中查询名为UnityEngine的值。获取其值是通过CS的元方法__index来实现的。其逻辑代码在创建LuaEnv时候调用下面的代码,进行CS表的初始化
  1. DoString(init_xlua, "Init");
复制代码
下面代码是截取了部分的init_xlua代码。
这部分描述的是_index元方法的实现元方法__index就是CS表中访问不存在的元素时候进行的操作。比如CS={'A='a','B'='b'},那么在Lua中直接访问CS.A就会返回a。但是如果访问C就会因为原来表中不存在这个记录,那么而调用__index这个方法。
代码实现注释如下:
  1. inti_xlua.lua
  2. local metatable = {}
  3. local rawget = rawget
  4. local setmetatable = setmetatable
  5. local import_type = xlua.import_type
  6. local import_generic_type = xlua.import_generic_type
  7. local load_assembly = xlua.load_assembly
  8. function metatable:__index(key)
  9.     --查询自己Key为'.fqn'的值,并且不触发__index元方法
  10.     local fqn = rawget(self,'.fqn')
  11.     --拼接'.fqn'的值和本次调用的key
  12.     fqn = ((fqn and fqn .. '.') or '') .. key
  13.     --尝试查询CS类型.
  14.     local obj = import_type(fqn)
  15.     if obj == nil then
  16.         -- It might be an assembly, so we load it too
  17.      --如果为空,有可能这个字段还是类名的一部分,那么创建一个table记录,然后缓存返回.
  18.         obj = { ['.fqn'] = fqn }
  19.         setmetatable(obj, metatable)
  20.     elseif obj == true then
  21.         return rawget(self, key)
  22.     end
  23.     -- Cache this lookup
  24.     rawset(self, key, obj)
  25.     return obj
  26. end
  27. CS = CS or {}
  28. setmetatable(CS, metatable)
复制代码
XLua中有两种方式来实现Lua调用CS中的方法,一种是反射来调用,一种是生成适配的代码。
在获取对应类的Lua表时候,使用的是import_type方法,也是在创建LuaEnv实例时候进行注册的代码如下。
  1. ObjectTranslator.cs
  2. public void OpenLib(RealStatePtr L) {
  3.     if (0 != LuaAPI.xlua_getglobal(L, "xlua")){  throw new Exception("call xlua_getglobal fail!" + LuaAPI.lua_tostring(L, -1));}
  4.     LuaAPI.xlua_pushasciistring(L, "import_type");
  5.     LuaAPI.lua_pushstdcallcfunction(L,importTypeFunction);
  6.     LuaAPI.lua_rawset(L, -3);
  7.     ...
  8. }
复制代码
上面代码中的importTypeFunction是一个C#委托当Lua中是调用import_type时候Lua会调用对应的C方法(Lua调用CFunction的原理,请查找Lua手册),最后会调用到对应的C#委托上来。
其中xlua全局table是在C中设置的代码如下:
  1. XLua.c
  2. LUA_API void luaopen_xlua(lua_State *L) {
  3.    luaL_openlibs(L);
  4. #if LUA_VERSION_NUM == 503
  5.    luaL_newlib(L, xlualib);
  6.    lua_setglobal(L, "xlua");
  7. #else
  8.    luaL_register(L, "xlua", xlualib);
  9.     lua_pop(L, 1);
  10. #endif
  11. }
复制代码
代码很简单,luaopen_xlua是一个c函数,属于xlua.dll在创建LuaEnv时候会调用。调用后会设置一个全局变量xlua,也就是ObjectTranslator类中获取的xlua变量。然后将键值对"import_type"=C#委托,压入xlua表中。这样就能在init_xlua.lua中调用import_type方法了。
在C#的ImportType方法中会尝试在缓存中获取对应的Type。如果Type为空,那么说明是第一次尝试引用对应的Type,代码就会判断是时使用生成适配代码还是反射模式,来生成对应的表。
  1. 省略部分代码
  2. ObjectTranslator.cs
  3. public static int ImportType(RealStatePtr L)
  4. {
  5.     try
  6.     {
  7.         ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
  8.         //需要查询的类名
  9.         string className = LuaAPI.lua_tostring(L, 1);
  10.         //查找C#对应的Type(此处还没去查找对应Lua的表)
  11.         Type type = translator.FindType(className);
  12.         if (type != null)
  13.         {
  14.             //这句查找Lua中Type对应的表
  15.             if (translator.GetTypeId(L, type) >= 0)
  16.             {
  17.                 LuaAPI.lua_pushboolean(L, true);
  18.             }
  19.             else
  20.             {
  21.                 return LuaAPI.luaL_error(L, "can not load type " + type);
  22.             }
  23.         }
  24.         else
  25.         {
  26.             LuaAPI.lua_pushnil(L);
  27.         }
  28.         return 1;
  29.     }
  30. }
  31. internal int getTypeId(RealStatePtr L, Type type, out bool is_first, LOGLEVEL log_level = LOGLEVEL.WARN)
  32. {
  33.     int type_id;
  34.     is_first = false;
  35.     //查询是否缓存中有Type对应的Lua表,有就直接返回
  36.     if (!typeIdMap.TryGetValue(type, out type_id)) // no reference
  37.     {
  38.         ...
  39.         is_first = true;
  40.         Type alias_type = null;
  41.         aliasCfg.TryGetValue(type, out alias_type);
  42.         LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
  43.         if (LuaAPI.lua_isnil(L, -1)) //no meta yet, try to use reflection meta
  44.         {
  45.             LuaAPI.lua_pop(L, 1);
  46.             //此处会去检查是使用反射还是生成适配代码的逻辑
  47.             if (TryDelayWrapLoader(L, alias_type == null ? type : alias_type))
  48.             {
  49.                 LuaAPI.luaL_getmetatable(L, alias_type == null ? type.FullName : alias_type.FullName);
  50.             }
  51.             else
  52.             {
  53.                 throw new Exception("Fatal: can not load metatable of type:" + type);
  54.             }
  55.         }
  56.         //循环依赖,自身依赖自己的class,比如有个自身类型的静态readonly对象。
  57.         if (typeIdMap.TryGetValue(type, out type_id))
  58.         {
  59.             LuaAPI.lua_pop(L, 1);
  60.         }
  61.         else
  62.         {
  63.             ...
  64.             LuaAPI.lua_pushvalue(L, -1);
  65.             type_id = LuaAPI.luaL_ref(L, LuaIndexes.LUA_REGISTRYINDEX);
  66.             LuaAPI.lua_pushnumber(L, type_id);
  67.             LuaAPI.xlua_rawseti(L, -2, 1);
  68.             LuaAPI.lua_pop(L, 1);
  69.             ...
  70.             //缓存type与其对应到lua中的表
  71.             typeIdMap.Add(type, type_id);
  72.         }
  73.     }
  74.     return type_id;
  75. }
  76. public bool TryDelayWrapLoader(RealStatePtr L, Type type)
  77. {
  78.             if (loaded_types.ContainsKey(type)) return true;
  79.             loaded_types.Add(type, true);
  80.             LuaAPI.luaL_newmetatable(L, type.FullName); //先建一个metatable,因为加载过程可能会需要用到
  81.             LuaAPI.lua_pop(L, 1);
  82.             Action<RealStatePtr> loader;
  83.             int top = LuaAPI.lua_gettop(L);
  84.             //此处如果已经缓存,那么就是生成适配代码注册,
  85.             //这边的逻辑也是为了用的时候才实例化对应的.
  86.             //这个delayWrap是个字典,他的键值对在XLua_Gen_Initer_Register__类实例化时候自动填充
  87.             if (delayWrap.TryGetValue(type, out loader))
  88.             {
  89.                 delayWrap.Remove(type);
  90.                 //将类方法,字段,成员等加载上来
  91.                 loader(L);
  92.             }
  93.             //那么这里就是反射的逻辑了
  94.             else
  95.             {
  96.                  ...
  97.                 //用反射将类方法,字段,成员等加载上来
  98.                 Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));
  99.                 ...
  100.             }
  101.             ...
  102.             ...
  103.             return true;
  104. }
复制代码
在生成完Type对应的Lua表后还需要设置到Lua上去
下面的代码简单来说就是用前面代码生成的table表设置到CS.UnityEngine[Debug]中
  1. //loader(L)和Utils.ReflectionWrap(L, type, privateAccessibleFlags.Contains(type));中都会调用此函数来设置CS.UnityEngine[Debug]
  2. public static void SetCSTable(RealStatePtr L, Type type, int cls_table)
  3. {
  4.    int oldTop = LuaAPI.lua_gettop(L);
  5.    cls_table = abs_idx(oldTop, cls_table);
  6.    LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
  7.    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
  8.    List<string> path = getPathOfType(type);
  9.    for (int i = 0; i < path.Count - 1; ++i)
  10.    {
  11.       LuaAPI.xlua_pushasciistring(L, path[i]);
  12.       if (0 != LuaAPI.xlua_pgettable(L, -2))
  13.       {
  14.          LuaAPI.lua_settop(L, oldTop);
  15.          throw new Exception("SetCSTable for [" + type + "] error: " + LuaAPI.lua_tostring(L, -1));
  16.       }
  17.       if (LuaAPI.lua_isnil(L, -1))
  18.       {
  19.          LuaAPI.lua_pop(L, 1);
  20.          LuaAPI.lua_createtable(L, 0, 0);
  21.          LuaAPI.xlua_pushasciistring(L, path[i]);
  22.          LuaAPI.lua_pushvalue(L, -2);
  23.          LuaAPI.lua_rawset(L, -4);
  24.       }
  25.       else if (!LuaAPI.lua_istable(L, -1))
  26.       {
  27.          LuaAPI.lua_settop(L, oldTop);
  28.          throw new Exception("SetCSTable for [" + type + "] error: ancestors is not a table!");
  29.       }
  30.       LuaAPI.lua_remove(L, -2);
  31.    }
  32.    LuaAPI.xlua_pushasciistring(L, path[path.Count - 1]);
  33.    LuaAPI.lua_pushvalue(L, cls_table);
  34.    LuaAPI.lua_rawset(L, -3);
  35.    LuaAPI.lua_pop(L, 1);
  36.    LuaAPI.xlua_pushasciistring(L, LuaEnv.CSHARP_NAMESPACE);
  37.    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
  38.    ObjectTranslatorPool.Instance.Find(L).PushAny(L, type);
  39.    LuaAPI.lua_pushvalue(L, cls_table);
  40.    LuaAPI.lua_rawset(L, -3);
  41.    LuaAPI.lua_pop(L, 1);
  42. }
复制代码
现在可以看到在调用到CS.UnityEngine.Debug时候,我们在Lua中已经获取到了这个类对应Lua Table了,那么接下来调用CS.UnityEngine.Debug.Log("hello world"),大提上与之前的获取Type类是一致的。不过要注意的是调用static的方法字段和对象的方法字段使用的是不同的table。这次文章都讨论的是静态方法的调用.
上面的已经提到XLua中有两种方式来实现Lua调用CS中的方法,一种是反射来调用,一种是生成适配的代码.
使用生成适配代码调用
在XLua中生成适配代码后会在Gen目录生成代码如下
  1. UnityEngineDebugWrap.cs
  2. public class UnityEngineDebugWrap
  3. {
  4.      public static void __Register(RealStatePtr L)
  5.      {
  6.           ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
  7.           System.Type type = typeof(UnityEngine.Debug);
  8.           //注册成员方法等
  9.           Utils.BeginObjectRegister(type, L, translator, 0, 0, 0, 0);
  10.           Utils.EndObjectRegister(type, L, translator, null, null,null, null, null);
  11.           //注册类方法等即Static
  12.           Utils.BeginClassRegister(type, L, __CreateInstance, 17, 3, 1);
  13.           ...
  14.           //注册一个名为Log的回调
  15.           Utils.CLS_IDX(L, Utils.CLS_IDX, "Log", _m_Log_xlua_st_);
  16.           ...
  17.           Utils.EndClassRegister(type, L, translator);
  18.      }
  19.     [MonoPInvokeCallbackAttribute(typeof(LuaCSFunction))]
  20.     static int _m_Log_xlua_st_(RealStatePtr L)
  21.     {
  22.         //根据Log方法的参数数量来生成各种调用
  23.        try {
  24.           ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
  25.           int gen_param_count = LuaAPI.lua_gettop(L);
  26.           if(gen_param_count == 1&& translator.Assignable<object>(L, 1))
  27.           {
  28.               object _message = translator.GetObject(L, 1, typeof(object));
  29.               UnityEngine.Debug.Log( _message );
  30.               return 0;
  31.           }
  32.           if(gen_param_count == 2&& translator.Assignable<object>(L, 1)&& translator.Assignable<UnityEngine.Object>(L, 2))
  33.           {
  34.               object _message = translator.GetObject(L, 1, typeof(object));
  35.               UnityEngine.Object _context = (UnityEngine.Object)translator.GetObject(L, 2, typeof(UnityEngine.Object));
  36.               UnityEngine.Debug.Log( _message, _context );
  37.               return 0;
  38.           }
  39.       } catch(System.Exception gen_e) {
  40.           return LuaAPI.luaL_error(L, "c# exception:" + gen_e);
  41.       }
  42.       return LuaAPI.luaL_error(L, "invalid arguments to UnityEngine.Debug.Log!");
  43.    }
  44. }
  45. 注册代码如下
  46. Utils.cs
  47. public static void RegisterFunc(RealStatePtr L, int idx, string name, LuaCSFunction func)
  48. {
  49.   //这里的idx指的是就是CLS_IDX,就是cls_table,也就是SetCSTable设置的表
  50.    idx = abs_idx(LuaAPI.lua_gettop(L), idx);
  51.    //压入方法名
  52.    LuaAPI.xlua_pushasciistring(L, name);
  53.    //压入C#委托指针
  54.    LuaAPI.lua_pushstdcallcfunction(L, func);
  55.    LuaAPI.lua_rawset(L, idx);
  56. }
复制代码
使用反射式调用
  1. static int FixCSFunction(RealStatePtr L)
  2. {
  3.     try
  4.     {
  5.         ObjectTranslator translator = ObjectTranslatorPool.Instance.Find(L);
  6.         //这边获取闭包中的upvalue值
  7.         int idx = LuaAPI.xlua_tointeger(L, LuaAPI.xlua_upvalueindex(1));
  8.         //GetFixCSFunction很简单就是return fix_cs_functions[index];
  9.         //fix_cs_functions这个是在PushFixCSFunction时候添加的,PushFixCSFunction是在之前ReflectionWrap中调用的
  10.         LuaCSFunction func = (LuaCSFunction)translator.GetFixCSFunction(idx);
  11.         return func(L);
  12.     }
  13.     catch (Exception e)
  14.     {
  15.         return LuaAPI.luaL_error(L, "c# exception in FixCSFunction:" + e);
  16.     }
  17. }
复制代码
那么到现在为止所有代码设置都已经完成,就差调用了。
当DoString到CS.UnityEngine.Debug.Log("hello world")时候,先从CS.UnityEngine.Debug[Log]获取到对应的value,在lua中这个值是一个function,那么就执行call,压入参数然后就开始调用了。如果是生成是适配代码的方式的话其对应的C#委托就是 _m_Log_xlua_st_(RealStatePtr L)了。但是如果是反射式调用的话,其对应的C#委托永远都是StaticLuaCallbacks.FixCSFunctionWraper这个委托,就是上面代码的FixCSFunction。当调用FixCSFunction后会从中取出upvalue,这个值是一个数字,是个索引。索引的是之前生成wrap时候缓存的方法。然后直接进行调用。
懒得打字嘛,点击右侧快捷回复 【右侧内容,后台自定义】
您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

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

GMT+8, 2024-11-24 11:37 , Processed in 0.090008 second(s), 25 queries .

Powered by Discuz! X3.5 Licensed

© 2001-2024 Discuz! Team.

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