《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》读书笔记(二十五) 的相关文章

  • Lua中如何获取目录列表

    我需要 LUA 中的目录列表 假设我的目录路径为 C Program Files 我需要该特定路径中所有文件夹的列表以及如何搜索该列表中的任何特定文件夹 Example 需要路径 C Program Files 中所有文件夹的列表 以下是上
  • 什么更快?循环或多个 if 条件

    我想知道什么更快 是只用一条指令 即 1 1 执行 9 次 for 循环还是执行 9 个 if 条件时 我认为 if 更快 因为您不需要检查循环中的指令 它应该几乎相同 因为for循环本质上是检查if条件为真并运行一段代码 非常类似于if声
  • 用于嵌入式服务器的 Web 技术

    我最近开始了一个针对嵌入式设备的新 Web 开发项目 并希望征求一些有关使用技术的建议 该设备将提供 HTML 页面 其中包括用于从 JSON 服务器检索数据的 AJAX 代码 我们暂时使用 Cherokee 作为 Web 服务器 但我们并
  • 确定已编译Lua的编译器版本

    我有一些已编译的 LuaQ 我需要确定用于编译它的确切版本 有什么可能的方法吗 编译的脚本在文件开头有一个标头 4 bytes signature x1bLua 1 byte version 0x51 1 byte format 1 byt
  • 为什么 LuaJIT 这么好?

    编辑 不幸的是 LuaJIT 已从下面链接的比较中删除 This 比较 http shootout alioth debian org u64 which programming languages are fastest php编程语言的
  • 使用 FastCGI 运行 Lua 脚本

    我目前正在尝试找出使用 FastCGI 与 lighttpd 或 Nginx 一起运行 Lua 脚本的方法 我唯一能挖到的是WSAPI http keplerproject github com wsapi 开普勒计划的一部分 但我想知道是
  • VB6 - Lua 集成

    我想知道是否有人有任何集成 Lua 和 VB6 的技巧 我正在运行一个小型在线角色扮演游戏 添加一些脚本会很棒 嗯 这是可行的 我曾经为 Lua 5 0 2 做过 但找不到文件 在您拥有的选项中 您可以 将 Lua 封装在公开 Lua AP
  • 如何在我的 Lua 脚本中添加“睡眠”或“等待”?

    我正在尝试通过更改一天中的时间来为游戏制作一个简单的脚本 但我想快速完成 这就是我要说的 function disco hour minute setTime 1 0 SLEEP setTime 2 0 SLEEP setTime 3 0
  • 如何通过 C API 在自己的环境中执行不受信任的 Lua 文件

    我想通过调用在其自己的环境中执行不受信任的 lua 文件lua setfenv http pgl yoyo org luai i lua setfenv这样它就不会影响我的任何代码 该函数的文档仅解释了如何调用函数 而不解释如何执行文件 目
  • 在 Awesome-wm 中为特定应用程序设置窗口布局

    如何配置很棒 以便它可以启动两个窗口对齐的新应用程序 如下所示 xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx xxxxxxxxxx 其中 x 是 pidgin 中的对话窗口 是好友列表窗口 一般来说 我想指定右窗口的宽度
  • Lua 上的 For 循环

    我的作业是如何执行 for 循环 我已经从数字上弄清楚了 但无法从名称上弄清楚 我想创建一个 for 循环来运行名称列表 以下是我到目前为止所拥有的 names John Joe Steve for names 1 3 do print n
  • Lua 的标准(或最好支持的)大数(任意精度)库是什么?

    我正在处理大量无法四舍五入的数字 使用 Lua 的标准数学库 似乎没有方便的方法来保持精度超过某些内部限制 我还看到有几个库可以加载以处理大数字 http oss digirati com br luabignum http oss dig
  • 在 Lua 中只归档一次

    我想知道是否有一种方法可以只执行一次 lua 文件 并且后续尝试执行该 lua 文件将导致无操作 我已经考虑过做一些类似于 C header 的 if else endif 技巧的事情 我想知道是否有一个标准方法来实现这一点 James w
  • 如何从 Lua 内部运行另一个脚本?

    我需要从另一个 Lua 脚本中执行一个 Lua 脚本 有多少种方法 我该如何使用它们 通常您会使用以下内容 dofile filename lua 但你可以通过以下方式做到这一点require 很好 例子 foo lua io write
  • 在Windows上使用gcc 5.3.0编译Lua 5.2.4模块

    我需要用 gcc 5 3 0 编译 Lua 5 2 4 模块 在 Windows 上 在此之前 我按照以下步骤构建 Lua 5 2 4 gcc c DLUA BUILD AS DLL c ren lua o lua obj ren luac
  • 适用于 IEEE 802.15.4 的 Wireshark Lua 解析器 - DissectorTable 名称?

    我正在lua中开发wireshark解析器来解析基于802 15 4的自定义协议 不幸的是我无法找出正确的 DissectorTable 名称 table DissectorTable get wpan wpan does not work
  • 如何从表成员中引用lua表成员?

    我在 lua 有一张表 enUS LOCALE STHOUSANDS Thousands separator e g comma patNumber d LOCALE STHOUSANDS d regex to find a number
  • 模式 ^u.meta(\.|$) 未按预期工作

    我有这个模式 u meta 预期行为 u meta 将匹配所有角色 例如 u meta u meta admin u meta admin system u meta 它不应该匹配如下所示的内容 u meta admin u meta ad
  • luajit2.0.0 -- 分段错误:11

    我使用一个简单的例子http lua users org wiki SimpleLuaApiExample http lua users org wiki SimpleLuaApiExample进行测试 该示例可以成功静态链接libluaj
  • 如何在 Lua-C API 5.2 中创建类对象?

    我正在使用 Lua 封装 C 函数 使用 Lua 5 2 的 Lua C API include

随机推荐

  • [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写新的类型来