zoukankan      html  css  js  c++  java
  • xlua中lua对象到c#对象的转型

    lua中的类型

    基础类型

    #define LUA_TNIL		0
    #define LUA_TBOOLEAN		1
    #define LUA_TLIGHTUSERDATA	2
    #define LUA_TNUMBER		3
    #define LUA_TSTRING		4
    #define LUA_TTABLE		5
    #define LUA_TFUNCTION		6
    #define LUA_TUSERDATA		7
    #define LUA_TTHREAD		8
    

    变体(或者说子类型)

    /*
    ** tags for Tagged Values have the following use of bits:
    ** bits 0-3: actual tag (a LUA_T* value)
    ** bits 4-5: variant bits
    ** bit 6: whether value is collectable
    */
    
    /*
    ** LUA_TFUNCTION variants:
    ** 0 - Lua function
    ** 1 - light C function
    ** 2 - regular C function (closure)
    */
    
    /* Variant tags for functions */
    #define LUA_TLCL	(LUA_TFUNCTION | (0 << 4))  /* Lua closure */
    #define LUA_TLCF	(LUA_TFUNCTION | (1 << 4))  /* light C function */
    #define LUA_TCCL	(LUA_TFUNCTION | (2 << 4))  /* C closure */
    
    
    /* Variant tags for strings */
    #define LUA_TSHRSTR	(LUA_TSTRING | (0 << 4))  /* short strings */
    #define LUA_TLNGSTR	(LUA_TSTRING | (1 << 4))  /* long strings */
    
    
    /* Variant tags for numbers */
    #define LUA_TNUMFLT	(LUA_TNUMBER | (0 << 4))  /* float numbers */
    #define LUA_TNUMINT	(LUA_TNUMBER | (1 << 4))  /* integer numbers */
    
    
    /* Bit mark for collectable types */
    #define BIT_ISCOLLECTABLE	(1 << 6)
    

      lua中的对象都是用TValue来描述的,TValue中的tt_成员变量代表着这个TValue的类型。关于类型的具体定义,上面贴的代码中的注释中已经讲的比较清楚了。
      一个lua对象的类型是由一个7位的bits描述的。比如一个整数,这个对象的类型就是0011000(24)表示这个对象是数字类型中的整形,是一个不可回收对象。

    C#如何获取lua对象

      和c语言和lua交互其实没啥本质区别,就是通过lua提供的c函数操作lua栈,直接从栈中取就可以了。区别在于如何把取到的值转换为c#认识的值。

    如何在C#端描述这些类型

    简介

      lua的类型中boolean、string、number这几个类型是clr所认识的类型,所以clr就可以直接把这些类型拿过来用。具体就是直接调用Lua提供的lua_tonumber之类的c接口。
      lightUserData、table、function、userData、thread是C#不认识的类,需要通过某种标识(lua自带的reference系统)来表示。

    boolean、string、number类

      这三个类上面已经说过了,直接用提供的接口转就可以,下面写几个需要注意的点:

    1. string虽然也是一个引用类型,但是clr在拿到这个string的指针时,还需要将这个string的数据直接复制进clr中才算转型结束(xlua也已经封装好了,不用我们自己去复制)。
    2. 大部分类型转型失败的时候都不会报错,而是会返回一个默认值。就拿将一个lua对象转为int来说,最终是通过lua_tointegerx函数调用的,当lua对象不是number类型时,返回0:
    LUA_API lua_Integer lua_tointegerx (lua_State *L, int idx, int *pisnum) {
      lua_Integer res;
      const TValue *o = index2addr(L, idx);
      int isnum = tointeger(o, &res);
      if (!isnum)
        res = 0;  /* call to 'tointeger' may change 'n' even if it fails */
      if (pisnum) *pisnum = isnum;
      return res;
    }
    
    1. 当一个number类型是浮点数时,转型整数不会进行取整操作,而是会直接返回0。因为lua默认对float转int的操作模式LUA_FLOORN2I是0,代表碰见float转int时返回0。
    /*
    ** try to convert a value to an integer, rounding according to 'mode':
    ** mode == 0: accepts only integral values
    ** mode == 1: takes the floor of the number
    ** mode == 2: takes the ceil of the number
    */
    int luaV_tointeger (const TValue *obj, lua_Integer *p, int mode) {
      TValue v;
     again:
      if (ttisfloat(obj)) {
        lua_Number n = fltvalue(obj);
        lua_Number f = l_floor(n);
        if (n != f) {  /* not an integral value? */
          if (mode == 0) return 0;  /* fails if mode demands integral value */
          else if (mode > 1)  /* needs ceil? */
            f += 1;  /* convert floor to ceil (remember: n != f) */
        }
        return lua_numbertointeger(f, p);
      }
      else if (ttisinteger(obj)) {
        *p = ivalue(obj);
        return 1;
      }
      else if (cvt2num(obj) &&
                luaO_str2num(svalue(obj), &v) == vslen(obj) + 1) {
        obj = &v;
        goto again;  /* convert result from 'luaO_str2num' to an integer */
      }
      return 0;  /* conversion failed */
    }
    

    userData

      userData主要是lua对c#对象的引用,这里只简单说一下。
      代表c#对象的userData主要分两种。

    1. 把c#对象存在ObjectTranslator中,用下标作为引用(类似于lua中的reference)。
    2. 经过GC优化的结构体和枚举,不存在ObjectTranslator中,而是把所有内容都打包到userdata中一起传入lua中。比如一个Vector3,那么xlua会把这个Vector3的x、y、z作为3个连续的float一起打包到userdata中。这样就避免了c#层的装箱、拆箱和gc操作。

    对table与function的引用简介

      这两个类型都是通过lua的reference系统来让c#持有对lua对象的引用。

    lua reference系统

      c#就是通过这个系统来持有不认识的lua对象的。
      一共就两个接口:

    1. luaL_ref:把栈顶元素加入一个lua的表中,并返回下标。
    2. luaL_unref:把一个下标所代表元素从表中删除。

      这样就可以用一个整数来让lua外的环境持有这个lua对象。
    具体可以看下官方说明lua References

    luaBase类

      所有lua对象在c#中的基类,在初始化时通过luaL_ref生成lua对象的引用,在析构时通过luaL_unref移除引用。

    对table的引用

    LuaTable

      一般情况下table在C#中被包装为LuaTable类,没啥特别的,只是在LuaBase的基础上增加了几个常用的函数。比如Get、Set之类的,让开发者可以避开一些不直观的栈操作。

    Array、List、Dictionary

      这几个都差不多。都是把table中的key和value全部拿出来,组成Array或Dictionaray。

    接口、其他类

      这两种转型是尝试把这个table看作对应的接口或类。
      比如将一个table转为IEnumberator就是把table转为SystemCollectionsIEnumeratorBridge类(继承了LuaBase、实现了IEnumerator的类,由Xlua生成),这个类实现了MoveNext和Reset。实现方法就是调用一下table中对应名称的函数。

    对function的引用

      lua函数在c#中有两种表示:

    LuaFunction

      LuaFunction和luaTable差不多,也是在LuaBase的基础上增加了几个常用函数,Call、Action之类的。

    DelegateBridge

      为什么已经有LuaFunction还要一个DelegateBridge类?
      因为我们在c#中拿到一个lua函数时,大多数时候是要作为一个委托来时用的。DelegateBridge就是用来化简这个转型操作的。
      DelegateBridge的功能就是在持有lua函数引用的同时,将这个函数包装成各种各样的委托,让整个转型过程对开发人员无感知。
      下面是一个不使用DelegateBridge,自己转型的例子,比较繁琐:

    //将一个LuaFunction作为一个Action<int>使用
    //其实LuaFunction.Cast就是干这个的,这里只是用简单的方式表达出来
    public static Action<int> LuaFunctionToActionInt(XLua.LuaFunction luaFunction)
    {
        //由于luaFunction已经提供了Call操作封装了函数调用的各种栈操作,所以我们这里只需要用一个Action<int>把这个操作包装起来即可
        return (x) =>
        {
            luaFunction.Call(x);
        };
    }
    
    public static void Test()
    {
        XLua.LuaEnv luaEnv = new XLua.LuaEnv();
        object[] rets = luaEnv.DoString("return function(x) CS.UnityEngine.Debug.LogError("print x: "..x) end");
        var luaFunction = (XLua.LuaFunction)rets[0];
        Action<int> actionInt = LuaFunctionToActionInt(luaFunction);
        actionInt(10);
    }
    

    DelegateBridge重要成员

    xlua在将lua函数转型的时候做了什么

    Tips

    1. 通过ObjectTranslator.getDelegateUsingGeneric生成委托时,会对返回值和参数进行不为值类型的约束。因为值类型在il2cpp下会有jit异常。这也是为什么我们发现有的委托类型不用注册也可以使用,但是有的就不行。
    2. 在编辑器模式下,没有进行代码生成时,会通过Emit直接生成一个XLuaGenDelegateImplx类,内容和通过代码生成后的DelegateBridge一样,而不是全部通过反射来进行转型。让没有进行代码生成时的环境和真机环境更接近。
    3. DelegateBridge一般不会被直接引用,而是被bindto中的委托生成的闭包引用和被delegate_bridges作为弱引用持有。当一个DelegateBridge的bindto中的委托没有被任何对象引用时,这个DelegateBridge就会在下次gc时被gc掉。

    其他

      这里主要写了常用lua类型转型的简介和一些关键点。可能不够全面和细节。
      如果有什么错误或者问题可以在下面留言。

  • 相关阅读:
    学习ESLint的规则配置,ESLint语法检测配置说明
    慎用Request.Params获取参数值
    [C#.NET 拾遗补漏]04:你必须知道的反射
    .NET Web应用中为什么要使用async/await异步编程
    Asp.Net Core Web Api 使用 Swagger 生成 api 说明文档
    网页打印尺寸设置
    Object 标签遮挡 Div 显示
    C# 之 批量插入数据到 SQLServer 中
    PrintDocument打印、预览、打印机设置和打印属性的方法
    JS 之 阻止事件冒泡,阻止默认事件,event.stopPropagation()和event.preventDefault(),return false的区别
  • 原文地址:https://www.cnblogs.com/blueberryzzz/p/13066922.html
Copyright © 2011-2022 走看看