Programming in Lua, 2Nd Edition - Chapter 28: User-Defined Types in C

本文介绍如何在C中扩展Lua以实现自定义类型,以布尔数组为例,详细讲解了userdata、metatables及面向对象访问等概念。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

 

 

 

 

Chapter 28: User-Defined  Types in C

 

 

在前面的章节中我们已经看到如何通过编写新的C函数来扩展Lua。现在,我们将讨论如何通过编写C 代码来扩展Lua 以获得新类型

 

我们的例子是一个相当简单的类型:布尔数组。当然,我们可以使用lua 的表来实现,但是每一个entry C 来实现只需要一个bit,这比表实现节省3% 的内存。

 

定义布尔数组类型

 

-- lualib.h

#define LUA_ARRAYLIBNAME    "array"

int luaopen_array (lua_State *L);

-----------------------------------

-- linit.c

static const luaL_Reg lualibs[] = {

  {"", luaopen_base},

  {LUA_LOADLIBNAME, luaopen_package},

  {LUA_TABLIBNAME, luaopen_table},

  {LUA_IOLIBNAME, luaopen_io},

  {LUA_OSLIBNAME, luaopen_os},

  {LUA_STRLIBNAME, luaopen_string},

  {LUA_MATHLIBNAME, luaopen_math},

  {LUA_DBLIBNAME, luaopen_debug},

  {LUA_MATHLIBNAME, luaopen_my},

  {LUA_MATHLIBNAME, luaopen_array},  /* add a item */

  {NULL, NULL}

};

-----------------------------------

-- lmylib.c

#include <limits.h>

#define BITS_PER_WORD (CHAR_BIT*sizeof(unsigned int))

#define I_WORD(i) ((unsigned int)(i) / BITS_PER_WORD)

#define I_BIT(i) (1 << ((unsigned int)(i) % BITS_PER_WORD))

 

typedef struct NumArray {

     int size;

     unsigned int values[1]; /* variable part */

} NumArray;

 

static int newarray (lua_State *L) {

     int i, n;

     size_t nbytes;

     NumArray *a;

     n = luaL_checkint(L, 1);

     luaL_argcheck(L, n >= 1, 1, "invalid size");

     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; /* initialize array */

     return 1; /* new userdatum is already on the stack */

}

 

static int setarray (lua_State *L) {

     NumArray *a = (NumArray *)lua_touserdata(L, 1);

     int index = luaL_checkint(L, 2) - 1;

     luaL_checkany(L, 3);

     luaL_argcheck(L, a != NULL, 1, "'array' expected");

     luaL_argcheck(L, 0 <= index && index < a->size, 2,

         "index out of range");

     if (lua_toboolean(L, 3))

         a->values[I_WORD(index)] |= I_BIT(index); /* set bit */

     else

         a->values[I_WORD(index)] &= ~I_BIT(index); /* reset bit */

     return 0;

}

 

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 of range");

     lua_pushboolean(L, a->values[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;

}

 

static const struct luaL_Reg arraylib [] = {

     {"new", newarray},

     {"set", setarray},

     {"get", getarray},

     {"size", getsize},

     {NULL, NULL}

};

 

int luaopen_array (lua_State *L) {

     luaL_register(L, LUA_ARRAYLIBNAME, arraylib);

     return 1;

}

-----------------------------------

-- a.lua

a = array.new(1000)

print(a) --> userdata: 00471230

print(array.size(a)) --> 1000

for i=1,1000 do

array.set(a, i, i%5 == 0)

end

print(array.get(a, 10)) --> true

 


 

I_WORD 返回某个bit 对应的int 数组元素的索引(想像一下用int 数组表示bit 数组的情形,一个int 元素对应32 bit 元素)I_BIT 计算一个掩码以在这个word 的内部访问正确的bit

 

数组以这个结构来表示:

 

typedef struct NumArray {

     int size;

     unsigned int values[1]; /* variable part */

} NumArray;

 

size 1 的数组values 只是用来占位,因为C89 标准不允许size 0 的数组当分配数组的时侯我们将会定义真实的size 下面这个表达式对一个具有n 元素的数组给出这个size

 

sizeof(NumArray) + I_WORD(n - 1)*sizeof(unsigned int)

 

(不需要给I_WORD 添加一个,因为原始结构已经包含一个元素的空间)

 

28.1 Userdata

 

函数lua_newuserdata 为给定的size 分配一块内存,将相应的userdatum 压栈,并返回块地址:

 

void *lua_newuserdata (lua_State *L, size_t size);

 

newarray  使用lua_newuserdata 创建bool 数组

 

static int newarray (lua_State *L) {

     int i, n;

     size_t nbytes;

     NumArray *a;

    

     int tmp;

    

     n = luaL_checkint(L, 1);

     luaL_argcheck(L, n >= 1, 1, "invalid size");

     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; /* initialize array */

     return 1; /* new userdatum is already on the stack */

}

 

luaL_checkint 宏只是一个类型转换luaL_checkinteger

 

创建新给组的语法是a=array.new(1000) ,往数组存一个元素用array.set(array,index,value)稍后我们将看到如何使用元表来支持传统语法:array[index]=value

 

static int setarray (lua_State *L) {

     NumArray *a = (NumArray *)lua_touserdata(L, 1);

     int index = luaL_checkint(L, 2) - 1;

     luaL_checkany(L, 3);

     luaL_argcheck(L, a != NULL, 1, "'array' expected");

     luaL_argcheck(L, 0 <= index && index < a->size, 2,

         "index out of range");

     if (lua_toboolean(L, 3))

         a->values[I_WORD(index)] |= I_BIT(index); /* set bit */

     else

         a->values[I_WORD(index)] &= ~I_BIT(index); /* reset bit */

     return 0;

}

 

因为Lua 接受任意值作为一个boolean,我们使用luaL_checkany 来检查第三个参数。它只保证这里有一个值(任意值)。

 

 

28.2 Metatables

 

我们的实现有一个主要的漏洞。假设用户写类似这样的代码array.set(io.stdin,1,false)io.stdin 是一个userdatum,一个指向流的指针(FILE*)。因为是一个userdatumarray.set 高兴地接受它作为有效参数;造成的结果可能是内存corruption

 

区分一个userdata 类型和另一个userdata 类型的不同通常是为那个类型创建一个唯一元表我们每次创建一个userdata 都使用相应的元表来标记。而每次获得一个userdata 我们检查它是否拥有正确的元表。因为Lua 代码不能更改一个userdatum 的元表,它不能欺骗我们的代码。

 

有三个地方可以存储元表:registryenvironment,或upvalueregistry 中注册一个新的C 类型习惯是用类型名作为索引,元表作为值

 

auxiliary 库有几个辅助函数:

 

int luaL_newmetatable (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);

 

luaL_newmetatable 创建一个新表(被用作元表)放在栈顶,并在registry 中将这个表关联到一个给定的名字

 

luaL_getmetatable registry获取与tname 相关联的元表。最后,luaL_checkudata 检查栈中给定位置上的一个对象是否是一个带有正确元表名字的userdatum。如果没有正确的元表或不是一个userdata 就引发错误,否则返回userdata 的地址

 

 

现在开始实现。第一步是改变luaopen_array。新版本必须为arrays 创建元表:

 

int luaopen_array (lua_State *L) {

     luaL_newmetatable(L, "LuaBook.array");

     luaL_register(L, "array", arraylib);

     return 1;

}

 

static int newarray (lua_State *L) {

     int i, n;

     size_t nbytes;

     NumArray *a;

    

     int tmp;

    

     n = luaL_checkint(L, 1);

     luaL_argcheck(L, n >= 1, 1, "invalid size");

     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; /* initialize array */

     luaL_getmetatable(L, "LuaBook.array");

    lua_setmetatable(L, -2);

     return 1; /* new userdatum is already on the stack */

}

(完整代码见后面)

 

现在,这个语句array.get(io.stdin,10) 会得到这样的错误:

 

error: bad argument #1 to 'getarray' ('array' expected)

 


 

28.3 Object-Oriented Access

 

-- lualib.h

#define LUA_ARRAYLIBNAME    "array"

int luaopen_array (lua_State *L);

-----------------------------------

-- linit.c

static const luaL_Reg lualibs[] = {

  {"", luaopen_base},

  {LUA_LOADLIBNAME, luaopen_package},

  {LUA_TABLIBNAME, luaopen_table},

  {LUA_IOLIBNAME, luaopen_io},

  {LUA_OSLIBNAME, luaopen_os},

  {LUA_STRLIBNAME, luaopen_string},

  {LUA_MATHLIBNAME, luaopen_math},

  {LUA_DBLIBNAME, luaopen_debug},

  {LUA_MATHLIBNAME, luaopen_my},  /* add a item */

  {LUA_MATHLIBNAME, luaopen_array},  /* add a item */

  {NULL, NULL}

};

-----------------------------------

-- larraylib.c

#include "lua.h"

#include "lauxlib.h"

#include "lualib.h"

#include <limits.h>

 

#define BITS_PER_WORD (CHAR_BIT*sizeof(unsigned int))

#define I_WORD(i) ((unsigned int)(i) / BITS_PER_WORD)

#define I_BIT(i) (1 << ((unsigned int)(i) % BITS_PER_WORD))

 

typedef struct NumArray {

     int size;

     unsigned int values[1]; /* variable part */

} NumArray;

 

#define checkarray(L) /

     (NumArray *)luaL_checkudata(L, 1, "LuaBook.array")

 

 

unsigned int *getindex (lua_State *L,unsigned int *mask);

 

static int newarray (lua_State *L) {

     int i, n;

     size_t nbytes;

     NumArray *a;

 

     int tmp;

 

     n = luaL_checkint(L, 1);

     luaL_argcheck(L, n >= 1, 1, "invalid size");

 

     tmp = I_WORD(n - 1);

 

     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; /* initialize array */

 

     luaL_getmetatable(L, "LuaBook.array");

     lua_setmetatable(L, -2);

 

     return 1; /* new userdatum is already on the stack */

}

 

static int setarray (lua_State *L) {

     unsigned int mask;

     unsigned int *entry = getindex(L, &mask);

     luaL_checkany(L, 3);

     if (lua_toboolean(L, 3))

         *entry |= mask;

     else

         *entry &= ~mask;

     return 0;

}

 

static int getarray (lua_State *L) {

     unsigned int mask;

     unsigned int *entry = getindex(L, &mask);

     lua_pushboolean(L, *entry & mask);

     return 1;

}

 

static int getsize (lua_State *L) {

     NumArray *a = checkarray(L);

     luaL_argcheck(L, a != NULL, 1, "'array' expected");

     lua_pushinteger(L, a->size);

     return 1;

}

 

static unsigned int *getindex (lua_State *L,unsigned int *mask) {

     NumArray *a = checkarray(L);

     int index = luaL_checkint(L, 2) - 1;

     luaL_argcheck(L, 0 <= index && index < a->size, 2,

         "index out of range");

     /* return element address */

     *mask = I_BIT(index);

     return &a->values[I_WORD(index)];

}

 

 

int array2string (lua_State *L) {

     NumArray *a = checkarray(L);

     lua_pushfstring(L, "array(%d)", a->size);

     return 1;

}

 

static const struct luaL_Reg arraylib_f [] = {

     {"new", newarray},

     {NULL, NULL}

};

static const struct luaL_Reg arraylib_m [] = {

     {"__newindex", setarray},

     {"__index", getarray},

     {"__len", getsize},

     {"__tostring", array2string},

     {NULL, NULL}

};

int luaopen_array (lua_State *L) {

     luaL_newmetatable(L, "LuaBook.array");

     luaL_register(L, NULL, arraylib_m);

     luaL_register(L, "array", arraylib_f);

     return 1;

}

-----------------------------------

-- a.lua

a = array.new(1000)

a[10] = true -- setarray

print(a[10]) -- getarray --> true

print(#a) -- getsize --> 1000

 

 

28.5 Light Userdata

 

到目前为到我们看到的userdata 被称为full userdataLua 还支持另一种userdata,称为light userdata

 

Light userdatum 是一个表示C 指针的值(就是void* 的值)。要将一个light userdattum 入栈,调用lua_pushlightuserdata

 

void lua_pushlightuserdata (lua_State *L, void *p);

 

light userdata 不是buffers,而是一个指针。它们没有元表。像数值一样不被垃圾收集器所管理

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值