Lohanry

宠辱不惊,绝不妄自菲薄

少年,你对力量一无所知


一个浪荡的程序猿

XLua 源码学习(二)

在XLua源码学习(一)中讲的主要是Lua对C#静态类的调用方法,本次将会讲到的是对对象方法的调用。

注意看代码中的注释,精华都在注释中。

类的静态字段方法实现

在XLua中对类的静态字段属性方法调用都是归属于对cls_table的映射操作。在生成适配代码的模式中,XLua将一个类的静态方法名都作为一个key存储在cls_table中,而对应的值是一个委托,这个委托将会调用到C#中对应的字段或者方法上,并且这个委托都是会在XLua的GenWrap代码阶段生成。但是在反射模式中XLua也是将一个类的静态方法名作为一个Key存储在cls_table中,但是其所有的值对应的是同一个委托,这个委托是一个闭包函数,闭包函数中会有一个值,它索引的是本次应该调用C#中的某个方法。

不过XLua对一个类的字段和属性的实现与方法不同,XLua会为字段名和属性名为Key存储在一个cls_getter的Table中,而对应的值是一个委托,这个委托会调用到C#对应的属性和字段,但是要注意的是这个clsgetter Table不会存储在cls_table中,而是作为闭包函数的一个参数保存起来,而且这个闭包函数会被作为cls_table的__index元方法。设置字段和字段也是同理。

这边剖析下代码:

public static void EndClassRegister(Type type, RealStatePtr L, ObjectTranslator translator)
{
    //获取栈顶Id
    int top = LuaAPI.lua_gettop(L);
    int cls_idx = abs_idx(top, CLS_IDX);//cls_table
    int cls_getter_idx = abs_idx(top, CLS_GETTER_IDX);//cls_get
    int cls_setter_idx = abs_idx(top, CLS_SETTER_IDX);//cls_set
    int cls_meta_idx = abs_idx(top, CLS_META_IDX);//cls_meta

    //begin cls index
    LuaAPI.xlua_pushasciistring(L, "__index");//压入__index 字符串,为设置__index元方法做准备
    LuaAPI.lua_pushvalue(L, cls_getter_idx);//压入cls_get Table
    LuaAPI.lua_pushvalue(L, cls_idx);//压入cls_table
    translator.Push(L, type.BaseType());//压入BaseType的地址
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);//压入全局变量Key
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//获取对应的全局变量值,并且弹出压入全局变量Key
    //生成cls_index方法,代码在下面一段分析,
    //只要知道这边会生成一个c方法,压入栈中
    //并且弹出之前压入的4个值(其实是弹出五个,还有一个是在gen_cls_index中压入的)。
    LuaAPI.gen_cls_indexer(L);
    //到这边其值栈中只有 top|__index|gen_cls_index,然后继续压入一个全局变量
    LuaAPI.xlua_pushasciistring(L, LuaClassIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
    //压入当前类型
    translator.Push(L, type);
    //压入-3的值,-3位置上现在是gen_cls_index
    LuaAPI.lua_pushvalue(L, -3);
    //设置键值对,其实就是 全局变量[type] = gen_cls_index,并且弹出type和栈顶gen_cls_index
    LuaAPI.lua_rawset(L, -3);
    //弹出全局变量
    LuaAPI.lua_pop(L, 1);
    //为cls_meta设置键值对 cls_meta[__index] = gen_cls_index,然后弹出键值对,
    //现在栈变为 top|
    LuaAPI.lua_rawset(L, cls_meta_idx);
    //end cls index
}

这边还需要着重提到的是gen_cls_indexer方法,这个方法实现的是对子类和父类的属性字段和方法的搜索实现比较重要.

代码如下:

LUA_API int gen_cls_indexer(lua_State *L) {
  //这里压入一个空的占位,给后面的cls_indexer放参数用
  lua_pushnil(L);
  //创建闭包函数,上文说到的实际弹出5个参数就是这边,
  //在C#端压入4个,还有一个就是这边压入的空的占位
  lua_pushcclosure(L, cls_indexer, 5);
  return 0;
}

在Lua中我们调用 Object.name时候,就会调用到Object对应的cls_table,然后name字段是不存在的,接着会调用__index方法,上文中的__index方法对应的其实就是cls_indexer。 实现如下:

//upvalue --- [1]:getters, [2]:feilds, [3]:base, [4]:indexfuncs, [5]:baseindex
//param   --- [1]: obj, [2]: key
LUA_API int cls_indexer(lua_State *L) { 
  //检查cls_get是否有值,没有值就代表当前类型没有get方法,然后就会尝试在父类查找
  if (!lua_isnil(L, lua_upvalueindex(1))) {
    //注意这边压入的不是upvalue2而是当前栈的2,是key,也就是name
    lua_pushvalue(L, 2);
    //在cls_get Table中查找name的值,
    lua_gettable(L, lua_upvalueindex(1));
    if (!lua_isnil(L, -1)) {//has getter
      //我们已经知道值都是委托方法,所以这边直接调用获取值
      lua_call(L, 0, 1);
      return 1;
    }
  }
  //如果是当前类这边的upvalue2是cls_table所以肯定找不到字段和属性的,
  //但是如果已经是父类的话是会进行查找对应的静态方法的
  if (!lua_isnil(L, lua_upvalueindex(2))) {
    lua_pushvalue(L, 2);
    lua_rawget(L, lua_upvalueindex(2));
    if (!lua_isnil(L, -1)) {//has feild
      return 1;
    }
    lua_pop(L, 1);
  }
  //这个方法是一个Lazy的模式,如果需要在父类查找对应的字段和属性,
 //第一次会对upvalue5的设置好父类的index,然后自己置空,减少查询次数,直接使用upvalue5来进行查询
  if (!lua_isnil(L, lua_upvalueindex(3))) {
    lua_pushvalue(L, lua_upvalueindex(3));
    while(!lua_isnil(L, -1)) {
      lua_pushvalue(L, -1);
      lua_gettable(L, lua_upvalueindex(4));
      if (!lua_isnil(L, -1)) // found
      {
        lua_replace(L, lua_upvalueindex(5)); //baseindex = indexfuncs[base]
        lua_pop(L, 1);
        break;
      }
      lua_pop(L, 1);
      lua_getfield(L, -1, "BaseType");
      lua_remove(L, -2);
    }
    lua_pushnil(L);
    lua_replace(L, lua_upvalueindex(3));//base = nil
  }
  //查询父类的属性和字段upvalue5如果有值就是一个方法,所以参数就是obj,key然后查询。
  if (!lua_isnil(L, lua_upvalueindex(5))) {
    lua_settop(L, 2);
    lua_pushvalue(L, lua_upvalueindex(5));
    lua_insert(L, 1);
    lua_call(L, 2, 1);
    return 1;
  } else {
    lua_pushnil(L);
    return 1;
  }
}

在反射模式中实现也是一样的,在此就不再赘述,字段属性的set也是与get一致,无非_index元方法变成了__newindex方法而已,也不再赘述。

对象字段方法实现

同类型的对象其对字段属性方法的查找都是使用的同一个元表,元表存储在全局变量中,并且在每次创建对象的时候,根据对象类型,查找对应的元表,然后将对应的lua对象的元表设置为查找到的元表即可,完成操作映射。 代码如下,因为与类的静态属性方法实现类似,就简单注释:

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);//obj_meta
    int method_idx = abs_idx(top, METHOD_IDX);//obj_method
    int getter_idx = abs_idx(top, GETTER_IDX);//obj_get
    int setter_idx = abs_idx(top, SETTER_IDX);//obj_set
    //begin index gen
    //压入__index,准备__index元方法
    LuaAPI.xlua_pushasciistring(L, "__index");
    //压入值
    LuaAPI.lua_pushvalue(L, method_idx);
    //压入值
    LuaAPI.lua_pushvalue(L, getter_idx);
    //C#中的索引器的实现(比如List类型的索引器),暂时先忽略后续有时间,在来讨论实现
    if (csIndexer == null){LuaAPI.lua_pushnil(L);}
    else{LuaAPI.lua_pushstdcallcfunction(L, csIndexer);}
    //压入类型
    translator.Push(L, type == null ? base_type : type.BaseType());
    //压入全局变量
    LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
    LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);
    //对象数组的索引,注意跟上面的csIndex区别, 这边一样忽略,下次有时间讲
    if (arrayIndexer == null){LuaAPI.lua_pushnil(L);}
    else{LuaAPI.lua_pushstdcallcfunction(L, arrayIndexer);}
    //跟类中一样,也是生成闭包方法,弹出上面的6个值,还有一个是nil在C中压入的跟类中一样
    LuaAPI.gen_obj_indexer(L);
    if (type != null){
        //将键值对 type=gen_obj_index缓存到全局变量表中
        LuaAPI.xlua_pushasciistring(L, LuaIndexsFieldName);
        LuaAPI.lua_rawget(L, LuaIndexes.LUA_REGISTRYINDEX);//store in lua indexs function tables
        translator.Push(L, type);
        LuaAPI.lua_pushvalue(L, -3);
        LuaAPI.lua_rawset(L, -3);
        LuaAPI.lua_pop(L, 1);
    }
    //obj_meta[__index] = gen_obj_index
    LuaAPI.lua_rawset(L, meta_idx);
    //end index gen
}

gen_obj_indexer也是跟之前的一样,这个方法实现的是对子类和父类搜索,也是压入一个nil的值,给后续查找父类的索引器的占一个位置。

这边主要来看下objindexer方法,也就是gen_obj_indexr中生成的闭包方法. 代码如下:

//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) { 
  //直接根据key查找对应的方法体然后返回
  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);
  }
  //这边与上文的类获取get类似
  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);
  }
  //其实意思就是如果key是数字那么就根据下标去调用array函数获取对应值,看点主要在arrayIndex方法中
  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;
  }
  //与arrayIndex类似
  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);
  }
  //父类indexfuncs查找不赘述
  if (!lua_isnil(L, lua_upvalueindex(4))) {
    lua_pushvalue(L, lua_upvalueindex(4));
    while(!lua_isnil(L, -1)) {
      lua_pushvalue(L, -1);
      lua_gettable(L, lua_upvalueindex(5));
      if (!lua_isnil(L, -1)) // found
      {
        lua_replace(L, lua_upvalueindex(7)); //baseindex = indexfuncs[base]
        lua_pop(L, 1);
        break;
      }
      lua_pop(L, 1);
      lua_getfield(L, -1, "BaseType");
      lua_remove(L, -2);
    }
    lua_pushnil(L);
    lua_replace(L, lua_upvalueindex(4));//base = nil
  }
  //递归查找父类
  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;
  }
}

这样XLua对对象操作和类操作的映射原理已经全部讲完,下次将分析热更技术实现的原理。

最近的文章

XLua 源码学习(三)

前面两篇文章已经分析了Lua调用C#中对象的实现方式。接下来将要分析的是XLua中最重要的功能:“热更”。毕竟Lua调用C#对象已经有各种版本的实现了。让C#代码支持热更的流程 Generate Code 这一步主要根据是根据C#类中需要支持热更的方法生成其对应的委托方法,但是并不是每个方法对应一个委托,而是根据调用参数和返回参数公用委托。 Hotfix Inject 这一步主要是对Unity编译出的Dll中的C#类添加判断条件,以此来选择调用Lua中的修复方法还是直接执行C#代码...…

继续阅读
更早的文章

XLua 源码学习(一)

现在工作中经常用到XLua修复Bug或者使用Lua作为逻辑脚本,是时候学习一下源码看看如何实现这些方法的了。那么XLua中是如何在Lua代码中调用Unity中的C#方法的呢?Lua调用C#方法CS.UnityEngine.Debug.Log('hello world')CS是一个全局的Table,所以CS.UnityEngine可以当做是在一个名为CS的Table表中查询名为UnityEngine的值。获取其值是通过CS的元方法__index来实现的。其逻辑代码在创建LuaEnv时候调用下...…

继续阅读