lua C API(5) - 用户自定义类型

除了写C函数来扩展 Lua,我们也可以在 C 里面自定义一些类型。

userdata

lua_newuserdata 函数分配一块内存,并把对应的 userdatum 压入栈,并返回内存块地址。该内存的释放无需操心,当该内存后面不能被访问到时,就会被垃圾回收器回收。

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

下面是使用例子

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray {
    int size;
    double values[1];
} NumArray;

static int newarray (lua_State *L) {
     int n = luaL_checkint(L, 1);
     size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double); 
     NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
     a->size = n;
     return 1;
}

static int setarray (lua_State *L) {
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    int index = luaL_checkint(L, 2);
    double value = luaL_checknumber(L, 3);
    luaL_argcheck(L, a != NULL, 1, "`array' expected");
    luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range");
    a->values[index-1] = value;
    return 0;
} 

static int getarray (lua_State *L) {
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    int index = luaL_checkint(L, 2); 
    luaL_argcheck(L, a != NULL, 1, "'array' expected"); 
    luaL_argcheck(L, 1 <= index && index <= a->size, 2,"index out of range"); 
    lua_pushnumber(L, a->values[index-1]);
    return 1;
}

static int getsize (lua_State *L) {
    NumArray *a = (NumArray *)lua_touserdata(L, 1);
    luaL_argcheck(L, a != NULL, 1, "`array' expected"); 
    lua_pushnumber(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_openlib(L, "array", arraylib, 0); 
    return 1;
}
#编译成动态库(lua的c模块)
gcc myarray.c -fPIC -shared -o array.so
--添加动态库搜索路径
--如果tuple.so放在默认搜索路径则可以省略
package.cpath = "/root/c/?.so;"..package.cpath

array = require("array")

--a 为自定义类型
a = array.new(1000)

print(a)
print(array.size(a))

array.set(a, 1, 10)
print(array.get(a,1))
[root@192 c]# lua main.lua 
userdata: 0x1f90b78
1000
10

元表

辅助库提供了一些函数来操作元表

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);

加入元表控制,用来检测 userdata 是否正确。

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray {
    int size;
    double values[1];
} NumArray;

//判断第一个参数的元表是否为 registry["LuaBook.array"]
#define checkarray(L) (NumArray *)luaL_checkudata(L, 1, "LuaBook.array")

static int getindex (lua_State *L) {
    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 */
    return index;
}

static int newarray (lua_State *L) {
    int n = luaL_checkint(L, 1);
     
    //分配所需的内存
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
     
    //把这块分配的内存压入栈,并把地址存入 a。
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
    a->size = n;
    
    //把 registry["LuaBook.array"] 指向的元表压入栈顶
    luaL_getmetatable(L, "LuaBook.array");
    
    //把 registry["LuaBook.array"] 指向的元表弹出并设置为该 userdata 的元表
    lua_setmetatable(L, -2);
    
    return 1;
}

//设置
static int setarray (lua_State *L) {
    NumArray *a = checkarray(L);
    int index = getindex(L);
    double value = luaL_checknumber(L, 3);
    a->values[index] = value;
    return 0;
}

//获取
static int getarray (lua_State *L) {
    NumArray *a = checkarray(L);
    int index = getindex(L);
    
    lua_pushnumber(L, a->values[index]);
    return 1;
}

//获取 array 的大小
static int getsize (lua_State *L) {
    NumArray *a = checkarray(L);
    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) {
    //创建一个元表,并存入 registry 全局表格,键名为 "LuaBook.array"
    luaL_newmetatable(L, "LuaBook.array");
    
    //注册
    luaL_register(L, "array", arraylib);
    return 1;
}
--添加动态库搜索路径
--如果tuple.so放在默认搜索路径则可以省略
package.cpath = "/root/c/?.so;"..package.cpath

array = require("array")

--a 为自定义类型
a = array.new(1000)

print(a)
print(array.size(a))

array.set(a, 1, 10)
print(array.get(a,1))

print(array.get(io.stdin,10))
[root@192 c]# lua main.lua 
userdata: 0xad9c98
1000
10
lua: main.lua:16: bad argument #1 to 'get' (LuaBook.array expected, got userdata)
stack traceback:
	[C]: in function 'get'
	main.lua:16: in main chunk
	[C]: ?

面向对象访问

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray {
    int size;
    double values[1];
} NumArray;

//判断第一个参数的元表是否为 registry["LuaBook.array"]
#define checkarray(L) (NumArray *)luaL_checkudata(L, 1, "LuaBook.array")

static int getindex (lua_State *L) {
    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 */
    return index;
}

static int newarray (lua_State *L) {
    int n = luaL_checkint(L, 1);
     
    //分配所需的内存
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
     
    //把这块分配的内存压入栈,并把地址存入 a。
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
    a->size = n;
    
    //把 registry["LuaBook.array"] 指向的元表压入栈顶
    luaL_getmetatable(L, "LuaBook.array");
    
    //把 registry["LuaBook.array"] 指向的元表弹出并设置为该 userdata 的元表
    lua_setmetatable(L, -2);
    
    return 1;
}

//设置
static int setarray (lua_State *L) {
    NumArray *a = checkarray(L);
    int index = getindex(L);
    double value = luaL_checknumber(L, 3);
    a->values[index] = value;
    return 0;
}

//获取
static int getarray (lua_State *L) {
    NumArray *a = checkarray(L);
    int index = getindex(L);
    
    lua_pushnumber(L, a->values[index]);
    return 1;
}

//获取 array 的大小
static int getsize (lua_State *L) {
    NumArray *a = checkarray(L);
    lua_pushinteger(L, a->size);
    return 1;
}

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 [] = {
    {"set", setarray},
    {"get", getarray},
    {"size", getsize},
    {"__tostring", array2string},
    {NULL, NULL}
};
 
int luaopen_array (lua_State *L) {
    //创建元表 t
    luaL_newmetatable(L, "LuaBook.array");
    
    lua_pushvalue(L, -1); //复制元表
    
    //把栈顶的元表弹出并设置
    //t['__index'] = t
    lua_setfield(L, -2, "__index");
    
    //把 set,get,size 3个方法注册到 t 元表
    luaL_register(L, NULL, arraylib_m);
    
    //把 new函数 注册到 package.loaded['array'] 对应的表格
    luaL_register(L, "array", arraylib_f);
    return 1;
}
--添加动态库搜索路径
--如果tuple.so放在默认搜索路径则可以省略
package.cpath = "/root/c/?.so;"..package.cpath

array = require("array")

--a 为自定义类型
a = array.new(1000)

--调用 a 的元表的 __tostring 方法
print(a)

--等同于a.size(a)
--由于 userdata a 没有 size 方法,那么会查找 a 的元表是否有 size 方法
--如果有则调用 a 的元表的 size 方法
print(a:size())

--下面的同理
a:set(1, 10)
print(a:get(1))

数组操作

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

typedef struct NumArray {
    int size;
    double values[1];
} NumArray;

//判断第一个参数的元表是否为 registry["LuaBook.array"]
#define checkarray(L) (NumArray *)luaL_checkudata(L, 1, "LuaBook.array")

static int getindex (lua_State *L) {
    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 */
    return index;
}

static int newarray (lua_State *L) {
    int n = luaL_checkint(L, 1);
     
    //分配所需的内存
    size_t nbytes = sizeof(NumArray) + (n - 1)*sizeof(double);
     
    //把这块分配的内存压入栈,并把地址存入 a。
    NumArray *a = (NumArray *)lua_newuserdata(L, nbytes);
    a->size = n;
    
    //把 registry["LuaBook.array"] 指向的元表压入栈顶
    luaL_getmetatable(L, "LuaBook.array");
    
    //把 registry["LuaBook.array"] 指向的元表弹出并设置为该 userdata 的元表
    lua_setmetatable(L, -2);
    
    return 1;
}

//设置
static int setarray (lua_State *L) {
    NumArray *a = checkarray(L);
    int index = getindex(L);
    double value = luaL_checknumber(L, 3);
    a->values[index] = value;
    return 0;
}

//获取
static int getarray (lua_State *L) {
    NumArray *a = checkarray(L);
    int index = getindex(L);
    
    lua_pushnumber(L, a->values[index]);
    return 1;
}

//获取 array 的大小
static int getsize (lua_State *L) {
    NumArray *a = checkarray(L);
    lua_pushinteger(L, a->size);
    return 1;
}

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;
}
--添加动态库搜索路径
--如果tuple.so放在默认搜索路径则可以省略
package.cpath = "/root/c/?.so;"..package.cpath

array = require("array")

--a 为自定义类型
a = array.new(1000)

--调用a元表的 __tostring 方法
print(a)

--__newindex
a[1] = 1

--__index
print(a[1])

--__len
print(#a)
[root@192 c]# lua main.lua 
array(1000)
1
1000

light userdata

userdata 也叫 full userdata,lua里面还有另外一种 userdata 叫做 light userdata。

light userdata 代表的是 C 指针(void *),它没有元表,也不是内存缓冲,有些时候我们用 light userdata 代替 userdata,因为 light userdata 开销会少很多。

要使用 light userdata,那么内存必须自己管理,因为它不会被垃圾回收。

资源管理

我们知道,当我们申请内存给 userdata 时是不需要操心内存的释放的。但是假设该内存绑定了一些其他资源比如打开文件描述符等,则需要在内存被释放之前关闭它。

lua 元表里有一个 __gc 域,在 userdata 要被回收之前会调用该域指向的函数,这样就可以释放任意跟它关联的资源。

设置方式大致如下

int luaopen_dir (lua_State *L) {
    //创建元表
    luaL_newmetatable(L, "LuaBook.dir");

    //设置元表的 __gc 指向 dir_gc 函数
    lua_pushstring(L, "__gc");
    lua_pushcfunction(L, dir_gc);
    lua_settable(L, -3);

    //下面只是设置一个全局函数
    lua_pushcfunction(L, l_dir);
    lua_setglobal(L, "dir");
    return 0;
}


上一篇: lua C API(4) - 编写C函数其他技术
下一篇: lua C API(6) - 协程和state
作者邮箱: 203328517@qq.com