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

2018-09-01 20:03:10

除了写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;
}


备注:

1.本文只是对模块加载机制做简单的介绍,如果有疑问可以给我留言
2.lua的版本为5.1,运行环境centos7 64位
3.原文地址http://www.freecls.com/a/2712/104


©著作权归作者所有
收藏
推荐阅读
  • lua C API(4) - 编写C函数其他技术

    数组处理数组处理可以用处理表格的方法来处理比如 lua_settable,lua_gettable,因为数组也是表格。但是出于性能考虑和简便性,lua api 提供了额外几个专门处理数组的函数。voi...

  • lua C API(3) - Lua 调用 C函数

    从Lua中调用C函数,必须遵循一些协议来传递参数和获得返回结果。另外,从Lua调用C函数我们必须注册函数,也就是说,我们必须把C函数的地址以一个适当的方式传递给Lua解释器。每一个函数都有他自己的私有...

  • lua C API(2) - C 调用 Lua

    Lua 可以作为程序库用来扩展应用的功能。同时,Lua 程序中可以注册有其他语言实现的函数,这些函数可能由C语言(或其他语言)实现,可以增加一些不容易由 Lua 实现的功能。这使得 Lua 是可扩展的...

  • lua C API(1) - 函数汇总

    在调用C API时有几个重要的头文件:lua.h:基础函数库,lua_ 前缀。lauxlib.h辅助库,luaL_ 前缀,利用 lua.h 实现的更高层的抽象。lualib.h为了保持Lua的苗条,所...

  • lua模块安装

    lua模块安装可以利用 luarocks 命令,在centos7 上执行以下来安装。yum install epel-release yum install luarocks此时我们安装模块的的时候可...

  • nginx模块 ngx_http_headers_module

    ngx_http_headers_module 模块是用来增加 Expires 和 Cache-control,或者是任意的响应头。Syntax: add_header name value [alw...

  • nginx模块 ngx_http_gunzip_module、ngx_http_gzip_module、ngx_http_gzip_static_module

    ngx_http_gunzip_module 模块将文件解压缩后并在响应头加上 "Content-Encoding: gzip" 返回给客户端。为了解决客户端不支持gzip压缩。编译的时候带上 --w...

  • nginx模块 ngx_http_flv_module、ngx_http_mp4_module

    ngx_http_flv_module模块提供了对 flv 视频的伪流支持。编译的时候带上 --with-http_flv_module。它会根据指定的 start 参数来指定跳过多少字节,并在返回数...

  • nginx模块 ngx_http_fastcgi_module

    ngx_http_fastcgi_module 模块使得nginx可以与 fastcgi 服务器通信。比如目前要使得 nginx 支持 php 就得使用 fastcgi技术,在服务器上装上 nginx...

  • nginx模块 ngx_http_autoindex_module

    ngx_http_autoindex_module 模块可以将uri以 / 结尾时,列出里面的文件和目录。Syntax: autoindex on | off; Default: autoindex ...

简介
天降大任于斯人也,必先苦其心志。