《Programming in Lua 3》读书笔记(二十五)

2023-05-16

日期: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对象。

转载于:https://www.cnblogs.com/zhong-dev/p/4044558.html

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

《Programming in Lua 3》读书笔记(二十五) 的相关文章

  • ESP8266 NodeMCU 堆内存不足

    我正在尝试通过从我的笔记本电脑发送 POST 使用 node js 来使用 ESP8266 01 来切换 LED 我现在遇到内存问题 因为每当我发送 POST 请求时 ESP 中使用的内存就会增加 而堆内存会减少 并且当没有剩余内存时它会崩
  • 无法在cmake中使用find_package找到Lua标头

    我正在尝试使用 CMake 为我使用 Lua 的项目构建生成 make 文件 当我运行 make 时出现此错误 path to my project luaudio luaudio c 1 17 fatal error lua h No s
  • lua检查多个值是否相等

    我喜欢用 Roblox 制作游戏 并用 lua 编写代码 在编写游戏时 我发现自己经常问一个值是否等于另一个值 这可能会产生很长的代码行 并且可能非常重复 例如 如果 x ClassName 衬衫 或x ClassName 附件 或x Cl
  • 代码说“尝试比较数字<=实例”

    It says Players ninjafox56 PlayerGui Shop ShopGui LightSide ChooseSideL 5 尝试比较数字 Rank game Players LocalPlayer leadersta
  • 如何将 Lua 嵌入到 Python 3.x 中?

    是否可以将 Lua 嵌入到 Python 3 x 中 如果是这样 我如何在我的 Python 程序中运行和执行 Lua 脚本 语言之间的交互有多好 例如 Lua 是否可以访问 Python 的所有变量和类以及 Python 是否可以访问 L
  • 如何在表中表示 nil

    假设我想存储一个元素列表 包括一些零值 值的位置很重要 我需要表示列表中给定位置处不存在值 这里有一个问题 a 1 2 3 nil 4 for k v in ipairs a do print k v end print a 4 print
  • 在 lua 中使用相等运算符比较数字有多安全?

    在我的引擎中 我有一个用于脚本编写的 Lua VM 在脚本中 我写了这样的内容 stage stage 1 if stage 5 then end and objnum tonumber 5 if stage objnum 根据 Lua 来
  • LuaJit FFI 从 C 函数返回字符串到 Lua?

    假设我有这个 C 函数 declspec dllexport const char GetStr static char buff 32 Fill the buffer with some string here return buff 这
  • 如何更新lua中的时间以反映执行过程中系统时区的变化?

    Problem 我想修改awful widget textclockAwesome wm 中的小部件可以立即反映系统时区的更改 这个小部件和所有的 Awesome wm 配置都是用 lua 编写的 目前 如果系统时区发生更改 小部件将继续根
  • Lua中如何获取目录列表

    我需要 LUA 中的目录列表 假设我的目录路径为 C Program Files 我需要该特定路径中所有文件夹的列表以及如何搜索该列表中的任何特定文件夹 Example 需要路径 C Program Files 中所有文件夹的列表 以下是上
  • 如何解密Lua字节码?

    早上好 我正在尝试破译 Moon 字节码 但我无法以任何方式 有人可以帮助我吗 我有这个 例如 code 27 76 117 97 81 0 1 4 4 4 8 0 如何将此字节码解密为文本 我已经在这里搜索 http www asciit
  • Lua-迭代嵌套表

    我已经学习 Lua 几个星期了 这一次又一次成为我的症结所在 我尝试阅读有关该主题的帖子和书籍 我使用 Lua 查询软件监控系统 Nimsoft 我的数据以表格形式返回给我 我不会发布整个输出 但这里有一个我认为可以描述结构的片段 表参考是
  • lua_resume 的 from 参数的含义

    From Lua 5 2 参考手册 http www lua org manual 5 2 manual html lua resume int lua resume lua State L lua State from int nargs
  • Lua userdata:无法同时进行数组访问和方法

    我遇到了这个人的问题 Lua userdata数组访问及方法 https stackoverflow com questions 26970316 lua userdata array access and methods 其中 当我设置用
  • Lua :: 如何编写加载多个CPU的简单程序?

    我还无法用 Lua 编写一个可以加载多个 CPU 的程序 自从Lua通过协程支持这个概念 http www lua org pil 9 4 html 我相信这是可以实现的 我失败的原因可能是以下之一 这在Lua中是不可能的 我写不出来 an
  • 确定已编译Lua的编译器版本

    我有一些已编译的 LuaQ 我需要确定用于编译它的确切版本 有什么可能的方法吗 编译的脚本在文件开头有一个标头 4 bytes signature x1bLua 1 byte version 0x51 1 byte format 1 byt
  • Lua中如何获取表中的最大整数?

    Lua中如何获取表中的最大整数 在Lua 5 1及更早版本中 你可以使用 math max unpack 1 2 3 4 5 这受到Lua堆栈大小的限制 在 PUC Lua 5 1 上 该值的最大值可达 ca 8000 个数字 如果堆栈空闲
  • lua中的权限问题

    是否需要在 corona build settings 中设置一些特定权限才能将高分永久保存在文件中 每次运行代码时都会出现 权限被拒绝 的错误 如何纠正这个错误 这是我尝试过的代码 function read score local f1
  • 去掉尾随零和小数点

    使用 Lua 我将数字格式化为可变位数并去掉尾随零 小数点 例如 string format precision f value gsub 0 1 gsub 值的类型为数字 正数 负数 整数 小数 所以任务已经解决了 但出于美学 教育和性能
  • 在Lua中获取前一天的日期

    谁能告诉我如何使用 Lua 获取 YYYY MM DD 格式的前一天日期 即 一个片段 它将返回运行当天的前一天的日期 Try print os date Y m d os time 24 60 60 严格来说 这只能保证在 POSIX 系

随机推荐

  • [Unity3D]矢量数学:向量的点乘(内积)和叉乘(外积)

    Unity使用左手坐标系 xff1a 拇指X轴 xff0c 食指Y轴 xff0c 中指Z轴 计算公式 xff1a 设 A Ax xff0c Ay xff0c Az B Bx xff0c By xff0c Bz xff0c 则 1 向量的模
  • itext 用的pom插件

    lt dependency gt lt groupId gt com itextpdf lt groupId gt lt artifactId gt itext asian lt artifactId gt lt version gt 5
  • Rplidar学习(三)—— ROS下进行rplidar调试

    一 建立工作空间 编译包 mkdir p catkin rplidar src 创建目录 cd catkin rplidar src 打开目录 下载rplidar ros数据包 xff0c 进行移动 git clone https gith
  • 数据包嗅探工具:HTTP请求/响应分析工具

    HTTPNetworkSniffer
  • RoboMaster 2017:机器人版的「王者农药」,工程师们的竞技时代

    8月6日晚 xff0c 第十六届全国大学生机器人大赛 RoboMaster 2017机甲大师赛在华润深圳湾体育中心 春茧 体育馆举行 xff0c 关于这个比赛的盛况已经无需赘述 xff0c 去年雷锋网参加上届比赛时 xff0c 报道的是 像
  • python popen.stdout.read阻塞 解决办法

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 需求 xff1a 利用python的subprocess模块结合logging模块实现监控子程序运行情况 代码如下 程序阻塞在stdout readz这里 xff0c 日志
  • Windows云服务器CPU使用率高的问题一例

    作者 xff1a 声东 大家好 xff0c 今天跟大家分享一例Windows云服务器CPU使用率高的问题 问题症状 客户购买了一台Windows 2016云服务器 xff0c 登录之后发现这台服务器的CPU使用率一直保持在90 以上 问题分
  • java 类知识_Java类基础知识

    同时按住Java中的Alt键和 39 39 键 xff0c Eclipse会给你代码提示 java 的几个基本概念 1 JVM java 虚拟机 运行java 程序的根本 2 JRE java 运行环境 xff0c java 虚拟机 43
  • UDP程序设计

    UDP套接口是无连接的 不可靠的数据报协议 xff1b 既然他不可靠为什么还要用呢 xff1f 其一 xff1a 当应用程序使用广播或多播时只能使用UDP协议 xff1b 其二 xff1a 由于他是无连接的 xff0c 所以速度快 因为UD
  • Linux下读写芯片的I2C寄存器

    要想在Linux下读写芯片的I2C寄存器 xff0c 一般需要在Linux编写一份该芯片的I2C驱动 xff0c 关于Linux下如何编写I2C驱动 xff0c 前一篇文章 手把手教你写Linux I2C设备驱动 已经做了初步的介绍 xff
  • linux centos 7上运行teamviewer与找不到ID问题处理办法

    以前在raspberryPi上搞过teamviewer xff0c 现在用了CentOS服务器 xff0c 搞了一个vpn xff0c 访问还有点问题 xff0c 时间紧张 xff0c 就先给teamviewer 而centos7 上安装也
  • 如何传集合型参数

    想传入查询参数到存储过程中 xff0c 但参数代表一个集合 不知该如何实现 首先是参数用什么类型 xff1f 然后是在PL SQL中查询语句的条件该如何写 xff1f 期望的SQL查询是类似这样的 xff1a select from aaa
  • Vue SSR Nuxt axios封装

    安装 npm install axios save span class copy code btn 复制代码 span 使用 nuxt config js 引入插件 xff0c 启动中间件 plugins span class hljs
  • 重新解读DDD领域驱动设计(一)

    回顾 十年前 xff0c 还未踏入某校时 xff0c 便听闻某学长一毕业就入职北京某公司 xff0c 月薪过万 对于一个名不见经传的小学院 xff0c 一毕业能拿到这个薪水还是非常厉害的 听闻他学生期间参与开发了一款股票软件 xff0c 股
  • ubuntu sudo apt-get update无法解析域名

    问题 sudo apt get update时提示如下 xff1a 然后cat etc resolv conf 查看dns server发现里面是空的 解决办法 xff1a 1 永久有效 sudo vi etc resolvconf res
  • IDEA 报错These modules have been removed from Maven stucture

    2019独角兽企业重金招聘Python工程师标准 gt gt gt 当我们从IDEA中删除一个module后 xff0c 我再新建同名的module时发现提示 These modules have been removed from Mav
  • nginx: [emerg] BIO_new_file("/etc/nginx/ssl_key/server.crt") failed (SSL: error:02001002:syste

    Centos 7 5 nginx 43 web集群配置https报错 报错信息 root 64 lb01 conf d nginx t nginx emerg BIO new file 34 etc nginx ssl key server
  • 永久关闭swap分区

    参考文章 xff1a https blog 51cto com 6923450605400 735323 xff08 1 xff09 临时关闭swap分区 重启失效 swapoff a xff08 2 xff09 永久关闭swap分区 se
  • querySelector() 方法

    返回文档中匹配指定 CSS 选择器的一个元素 虽然IE8中没有getElementsByClassName 但可以用querySelector 代替 注意 xff1a querySelector 方法仅仅返回匹配指定选择器的第一个元素 如果
  • 《Programming in Lua 3》读书笔记(二十五)

    日期 xff1a 2014 8 11 Part The C API 29 User Defined Types in C 在之前的例子里 xff0c 已经介绍过如果通过用C写函数来扩展Lua 在本章 xff0c 将会介绍通过用C写新的类型来