zoukankan      html  css  js  c++  java
  • 《Programming in Lua 3》读书笔记(二十五)

    日期:2014.8.11
    PartⅣ The C API 
     
    29 User-Defined Types in C

         在之前的例子里,已经介绍过如果通过用C写函数来扩展Lua。在本章,将会介绍通过用C写新的类型来扩展Lua,将会使用到元方法等特性来实现这个功能。
         以一个例子来介绍本章将要介绍的,例子实现的功能是实现了一个简单的类型:boolean arrays。实现这个功能主要是这种方法不需要太复杂的算法,因此可以将精力放在API的讨论上。当然我们可以在Lua中用一个table来实现,但是用一个C来实现,where we store each entry in one single bit(指的是用一个位数来表现boolean值?).,比用table来实现节省了3%的内存开销。
         实现这个类型首先是需要做一些定义:
    #include <limits.h>
    #define BITS_PER_WORD (CHAR_BIT*sizeof(unsigned int))
    #define I_WORD(i)             ((unsigned int)(i) / BIT_PER_WORD)
    #define I_BIT(i)                  (1 << ((unsigned int)(i) % BIT_PER_WORD))
         BITS_PER_WORD 表示一个无符整型数中的位的数量。宏I_WORD 计算给定的数中位的数量,宏I_BIT 则计算了求一个数正数位的掩码。
         以下面的struct代表我们定义的类型:
    e.g.
    typedef struct NumArray
    {
         int size;
         unsigned int values[1];
    } NumArray;
         定义数组values的大小为1,实现一个占位符,因为C 89 不允许数组的大小为0.当我们allocate 这个数组的时候将会重新设定其实际大小。下面的表达式则计算了n个元素数组的实际大小:
    e.g.
         sizeof(NumArray) + I_WORD( n -1 ) * sizeof(unsigned int)

    29.1 UserData
         首先要考虑的是在Lua中用什么来代表NumArray这个数据结构。Lua提供了一个基础的类型:userdata。一个userdatum提供了一块内存区域,没有做任何预定义的操作,因此可以用这个类型存储任何东西。
         函数lua_newuserdata 根据给定的大小分配了一块内存区域,将相应的userdatum推进栈中,然后返回这个内存块的地址:
     void* lua_newuserdata(lua_State *L,size_t size);
         而如果需要以其他用途来分配内存,使用给定大小的指针创建一个userdatum,然后用一个指针存储至实际的内存块上是非常容易的。在后面的章节会介绍这个。
         结合使用lua_newuserdata ,那么创建一个新的boolean arrays 将会是这样实现的:
    e.g.
    static int newarray(lua_State *L)
    {
         int i;
         size_t nbytes;
         NumArray *a;
    
         int n = luaL_checkint(L,1);
         luaL_argcheck(L,n >= 1,1,"invalid siez");
         nbytes = sizeof(NumArray) + I_WORD(n -1)*sizeof(unsigned int);
         a = (NumArray*)lua_newuserdata(L,nbytes);
    
         a->size = n;
         for(i = 0;i <= I_WORD(n -1); i++)
              a->values[i] = 0;
    
              return 1;
    
    }
         一旦newarray注册到了Lua中,那么就可以通过如a = array.new(1000) 来创建新的array了。
         而使用arrar.set(a,index,value) 来存储一个条目(一个元素?)。而且与Lua中其余数据结构一致,新的arrary的index值从1开始,下面的函数设定一个数组给定index的值
    static int setarray(lua_State *L)
    {
         NumArray *a = (NumArray*)lua_touserdata(L,1);
         int index = luaL_checkint(L,2) - 1;
    
         luaL_argcheck(L,a != NULL,1,"'array' expected");
         luaL_argcheck(L,0 <= index && index < a->size,2,"index out of range");
         luaL_checkany(L,3);
    
         if(lua_toboolean(L,3))
              a->values[I_WORD(index)] != I_BIT(index);
         else
              a->values[I_WORD(index)] &= ~I_BIT(index);
    
         return 0;
    }
         函数中要到了一些位运算,感觉看起来好吃力。因为了Lua中的boolean变量接受任何类型的值,因此在这里使用luaL_checkany 来检测三个参数:保证每个参数都有一个对应的值,(函数要三个参数,那么就需要有三个参数)。如果不满足条件,那么就会报错:
    e.g.
    array.set(0,11,0)
         --stdin:1: bad argument #1 to 'set' ('array' expected) 这里的意思是第一个参数必须是数组'array'类型的
    array.set(a,1)
         --stdin:1: bad argument #3 to 'set' (value expected) 这里函数只传递两个参数,因此报错的是少了第三个参数,第三个参数必须有值。
         下面的这个函数从数据中得到值:
    static int getarray(lua_State *L)
    {
         NumArray *a = (NumArray*)lua_touserdata(L,1);
         int index = luaL_checkint(L,2) - 1;
    
         luaL_argcheck(L, a != NULL,1,"'array' expected");
         luaL_argcheck(L,0 <= index && index < a->size,2,"index out range");
    
         lua_pushboolean(L,a->value[I_WORD(index)] & I_BIT(index));
    
         return 1;
    }
         下面这个函数用来得到数组的大小:
    static int getsize (lua_State *L)
    {
         NumArray *a = (NumArray*)lua_touserdata(L,1);
         luaL_argcheck(L, a != NULL,1"'array' expected");
         lua_pushinteger(L,a->size);
         return 1;
    }
         处理完以上的操作之后,便是初始化库,然后加入到Lua中去:
    static const struct luaL_Reg arraylib [] =
    {
         {"new",newarray},
         {"set",setarray},
         {"get",getarray},
         {"size",getsize},
         {NULL,NULL}
    };
    
    
    int luaopen_array(lua_State *L)
    {
         luaL_newlib(L,arraylib);
         return 1;
    }
         从上面的操作可以看出,使Lua支持用C定义的类型,就是用到了自定义库的特性。用C写好库,然后注册到Lua中,再在Lua中使用就可以了。
         打开了自定义的库之后,便可以通过以下方式使用我们新写的类型了:
    a = array.new(1000)
    print(a)               --> user data
    print(array.size(a))     --> 1000
    for i = 1,1000 do
         array.set(a,i,i % 5 == 0)     --设定
    end
    print(array.get(a,10))          --true     --得到


     
    29.2 Metatables
         上部分实现的功能有一个安全隐患。假如用户使用array.set(io.stdin,1,false) 来设定了一个值。此时userdatum指针指向的是一个流(FILE*),因为其类型是一个userdatum ,array.set 会接受这个值。此时会造成内存冲突的问题(而其实得到的error meg是告诉你index溢出了)。这是不被Lua的库所接受的。
         通常,为我们新定义的类型创建一个独特的metatable作为唯一标识符,用来与其它的userdata区分开来是不错的方法。每次我们创建了一个userdata,都会用与之相对应的metatable来标记这个userdata;而每次我们得到一个userdata,则都会检测是否是正确的metatable。因为Lua的代码是不能修改userdatum的metatabel的,所以不用担心这会影响我们的代码。
         下一步需要注意的就是,如何存储我们这里要用到的metatable。在上一章中提到了两种存储数据的方式:registry 和 upvalue。通常在Lua中,要将任意定义的C Type注册至registry中,会使用类型名字作为index,然后以metatable作为value,同时我们也需要考虑到命名冲突的问题,因此在这里使用"LuaBook.array"作为名字。
         然后就是使用辅助库中的函数来实现我们这里需要的功能了:
    int luaL_newtatable(lua_State *L,const char *tname);
    void luaL_getmetatable(lua_State *L,const char *tname);
    void *luaL_checkudata(lua_State *L,int index,const char *tname);
         第一个函数将会创建一个新的table(用来作为metatable),将创建好的table放在栈顶,然后以给定的名字存储至rigistry中;第二个函数,从rigistry中根据给定的名字得到一个metatable;第三个函数,检测给定index位置的对象的metatable 与 名为tname 的metatable是否相等,如果不相同,将会引发错误,相同的话就会返回userdata的位置。
         修改打开库的函数,添加创建新的metatable的功能:
    int luaopen_array(lua_State *L)
    
    {
         luaL_newmetatable(L,"LuaBook.array");          /* 这里加入了创建metatable 的功能 */
         luaL_newlib(L,arraylib);
         return 1;
    }
         再修改创建array的函数,为每次创建的array设置metatable:
    static int newarray(lua_State *L)
    {
         //new
         luaL_getmetatable(L,"LuaBook.array");
         lua_setmetatable(L,-2);
    
         return 1;
    }
         函数lua_setmetatable 从栈中推出一个table,然后将其设定为给定index对象的metatable。
         然后在使用setarray , getarray ,getsize 的时候就需要对第一个参数做检测了。
         之后,如果第一个参数错误了,如:array.get(io.stdin,10),那么编译器将会抛出错误:
    error: bad argument #1 to 'get' ('array' expected)


     
    29.3 Object-Oriented Access
         这部分将要实现的是,将我们新实现的这个类型转换为一个对象,使得我们可以用面向对象的语法(object-oriented syntax)来对其进行操作,如:
    a = array.new(1000)
    print(a:size())          --使用了冒号操作符
    a:set(10,true)
    …
         这里的a:size() 相当于 a.size(a) ,实现这个功能的关键在与使用了__index 元方法。在table中 ,如果没有找到给定key的value,那么lua就会调用这个元方法。而对于userdata来说,因为其根本就没有key,所以每次都会调用这个元方法。
         示例:
    local metaarray = getmetatable(array.new(1))
    metaarray.__index = metarray
    metaarray.set = array.set
    metaarray.get = array.get
    metaarray.size = array.size
         第一行代码的功能主要是:创建一个新的array,然后得到它的metatable,赋值给metaarray(尽管lua中不能给userdata设置metatable,但是可以得到metatable)。后面的代码就是设置metatable的相关元方法。当我们调用a.size 计算size的时候,Lua不能从对象a中找到“size”这个key,就会从字段 __index 中去寻找这个值,而此时 __index 对应的便是metaarry本身,而我们设定了metaarray.size = array.size ,所以a.size(a)返回结果便是array.size(a),如我们所愿了。
         我们也可用用C来实现上述的特性,并且在C中可以做的更好了:因为此时array是一个对象了,对象有其自己内部封装好的操作,因此我们就不必要将这些如getsize 等的操作放至要注册的列表中了。只需要将创建新对象的函数放至列表即可。所有的其他操作函数都成为了对象的方法。
         之前实现的getsize ,getarray,setarray 等方法在实现上不需要做额外的改变,而需要改变的是我们如何注册这些函数。因此,我们需要修改我们打开库的方法,首先需要两个分开的列表:第一个给普通的函数使用而第二个给元方法来使用。
    static const struct luaL_Reg arraylib_f [] =
    {
         {"new",newarray},
         {NULL,NULL}
    };
    
    static const struct luaL_Reg arraylib_m [] =
    {
         {"set",setarray},
         {"get",getarray},
         {"size",getsize},
         {NULL,NULL}
    };
         相应的,打开库的函数luaopen_array 此时就需要创建metatable,然后将其自身赋值为 __index ,注册其余的操作函数等,最后创建array table:
    int luaopen_array(lua_State *L)
    {
         luaL_newmetatable(L,"LuaBook.array");
    
         lua_pushvalue(L,-1);
         lua_setfield(L,-2,"__index");
    
         luaL_setfuncs(L,arraylib_m,0);
         luaL_newlib(L,arraylib_f);
         return 1;
    }
         在这里使用luaL_setfuncs 将arraylib_m列表内的函数配置到metatable中,然后使用luaL_newlib 创建一个新的table,然后注册arraylib_f中的函数。

     
    29.4 Array Access
    数组中实现面向对象的语法形式,还可以使用通常数组的语法形式来实现。如,相比于使用a:get(i),我们也可以用a[i]来实现。我们可以通过定义一些元方法来实现我们的需求:
    e.g.
    local metaarray = getmetatable(array.new(1))
    metaarray.__index = array.get
    metaarray.__newindex = array.set
    metaarray.__len = array.size
    这样我们就可以用数组语法来实现我们需要的功能了:
    a = array.new(1000)
    a[10] = true          --     'setarray'
    print(a[10])           --     'getarray'
    print(#a)               --     'getsize'
    同样的,我们也需要将这些元方法在C中进行注册,也是通过修改初始化函数来实现。

     
    29.5 Light Userdata
         我们之前使用到的Userdata被称之为full userdata,除此之外,还有另一种类型的userdata,称之为light userdata.
         一个light userdatum 是一个代表C指针的value(一个 void* 的value)。light userdata 是一个value,而不是一个对象;因此是不能被创建的。使用函数lua_pushlightuserdata 来将一个light userdatum 推进至栈中:
         void lua_pushlightuserdata(lua_State *L,void *p);
         尽管都称为userdata,但是light userdata 和 full userdata 是不同的两个概念。light userdata不是buffers,而仅是指针而已。light user data没有metatable,而与number一样,light uesrdata 是不被garbage collector管理的。
         有的时候,light userdata 执行的是full userdata的轻量化替代工作。但是这也不是绝对化的。首先,light userdata 没有metatable,因此没有办法知道它们的类型;其次,full userdata也并不是占用很大开销的。
         真正上使用light userdata是用来做对比的。因为full userdata是对象,只与自己相比才会相等。而light userdata代表的是一个C指针,因此它会与任意代表同一个指针的userdata相等。所以我们可以使用light userdata在Lua中寻找到C对象。
  • 相关阅读:
    C#托盘图标
    线程相关整理
    Quartz.NET 快速入门
    (转)IE内存泄露,iframe内存泄露造成的原因和解决方案
    美化console.log的文本(转载)
    mongoDB学习资料整理
    EF7学习资料整理
    Oracle常用
    Node.js学习资料整理
    【大型网站技术实践】初级篇:借助Nginx搭建反向代理服务器(转)
  • 原文地址:https://www.cnblogs.com/zhong-dev/p/4044558.html
Copyright © 2011-2022 走看看