userdata类型是为了方便C/C++对Lua进行扩展,因为在用C/C++扩展时,我们经常会自定义数据类型,如:
typedef struct epoll_fd { int epfd; size_t size; struct epoll_event *events; }epoll_fd_t;
我们要想在Lua中使用此类型的对象,就必须使用userdata类型来表示。
对于Lua而言,所有C/C++自定义的类型都是userdata类型,无法区分,那么在实际的应用中userdata都会设置metatable,通过metattable来区分不同的userdata类型。
metatable其实也是table,metatable中的方法则称为“元方法”。对于userdata的metatable,当我们访问userdata对象的成员时,lua会调用metatable中的__index()元方法,如果__index也是table,则从__index table中查找对应的成员。当lua要回收userdata对象时,会调用metatable中的__gc()元方法。还有很多元方法,可以查看文档说明。
- userdata常用的Lua C API
1) 创建userdata for lua
void *lua_newuserdata (lua_State *L, size_t size); This function allocates a new block of memory with the given size, pushes onto the stack a new full userdata with the block address, and returns this address. Userdata represent C values in Lua. A full userdata represents a block of memory. It is an object (like a table): you must create it, it can have its own metatable, and you can detect when it is being collected. A full userdata is only equal to itself (under raw equality). When Lua collects a full userdata with a gc metamethod, Lua calls the metamethod and marks the userdata as finalized. When this userdata is collected again then Lua frees its corresponding memory.
从上面的说明,可以知道返回的就是内存地址,我们只需要强制转换为我们的自定义类型即可使用。
2) 创建metatable:
int luaL_newmetatable (lua_State *L, const char *tname); If the registry already has the key tname, returns 0. Otherwise, creates a new table to be used as a metatable for userdata, adds it to the registry with key tname, and returns 1. In both cases pushes onto the stack the final value associated with tname in the registry.
新创建的metatable会存放于registry中,其实registry也是一个table.
要从registry中获取指定的metatable:
void luaL_getmetatable (lua_State *L, const char *tname); Pushes onto the stack the metatable associated with name tname in the registry
3) 设置metatable:
int lua_setmetatable (lua_State *L, int index); Pops a table from the stack and sets it as the new metatable for the value at the given acceptable index.
metatable其实也是table,所以所有对table操作的方法,均可用在metatable上,包括为metatable设置元方法等。
4) 从栈中获取userdata:
void *luaL_checkudata (lua_State *L, int narg, const char *tname); Checks whether the function argument narg is a userdata of the type tname. It will throws an error when the given value is not a userdata of the expected type.
2. 对lua-epoll模块进行简单的修改,
在上一篇博文的基础上,对其源码进行修改如下:
// lua_f_epoll.c #include "lua_f_base.h" #include "epoll_fd.h" #define EPOLL_METATABLE_NAME "Epoll.MT" #define check_epoll_fd(L) luaL_checkudata(L, 1, EPOLL_METATABLE_NAME); static int lua_f_epoll_close(lua_State *L) { epoll_fd_t *self = (epoll_fd_t *)check_epoll_fd(L); epoll_fd_release(self); return 0; } static int lua_f_epoll_register(lua_State *L) { if(lua_gettop(L) < 3){ RETERR("invalid args"); } epoll_fd_t *self = (epoll_fd_t *)check_epoll_fd(L); int fd = luaL_checkint(L, 2); int event = luaL_checkint(L, 3); if(fd < 0){ RETERR("invalid fd"); } if(epoll_fd_add(self, fd, event) < 0){ RETERR("epoll_fd_add fail"); } lua_pushboolean(L, 1); return 1; } static int lua_f_epoll_modify(lua_State *L) { if(lua_gettop(L) < 3){ RETERR("invalid args"); } epoll_fd_t *self = (epoll_fd_t *)check_epoll_fd(L); int fd = luaL_checkint(L, 2); int event = luaL_checkint(L, 3); if(fd < 0){ RETERR("invalid fd"); } if(epoll_fd_mod(self, fd, event) < 0){ RETERR("epoll_fd_mod fail"); } lua_pushboolean(L, 1); return 1; } static int lua_f_epoll_unregister(lua_State *L) { if(lua_gettop(L) < 2){ RETERR("invalid args"); } epoll_fd_t *self = (epoll_fd_t *)check_epoll_fd(L); int fd = luaL_checkint(L, 2); if(fd < 0){ RETERR("invalid fd"); } if(epoll_fd_del(self, fd) < 0){ RETERR("epoll_fd_del fail"); } lua_pushboolean(L, 1); return 1; } static int lua_f_epoll_wait(lua_State *L) { epoll_fd_t *self = (epoll_fd_t *)check_epoll_fd(L); int timeout = luaL_optint(L, 2, -1); int n = epoll_fd_wait(self, timeout); if(n < 0){ RETERR("epoll_fd_wait fail"); } lua_newtable(L); int i; for(i = 0; i < n; ++i){ lua_pushinteger(L, self->events[i].events); lua_rawseti(L, -2, self->events[i].data.fd); } return 1; } static const struct luaL_Reg lua_f_epoll_func[] = { {"close", lua_f_epoll_close}, {"register", lua_f_epoll_register}, {"modify", lua_f_epoll_modify}, {"unregister", lua_f_epoll_unregister}, {"wait", lua_f_epoll_wait}, {NULL, NULL}, }; static int lua_f_epoll_create(lua_State *L) { size_t size = luaL_optint(L, 1, EPOLL_DEFAULT_SIZE); int epfd = epoll_create(size); if(epfd < 0){ RETERR("epoll_create fail"); } epoll_fd_t *self = (epoll_fd_t*)lua_newuserdata(L, sizeof(epoll_fd_t)); if(epoll_fd_init(self, epfd, size) < 0){ RETERR("epoll_fd_init fail"); } luaL_getmetatable(L, EPOLL_METATABLE_NAME); lua_setmetatable(L, -2); return 1; } static int lua_f_epoll_version(lua_State *L) { const char *ver = "Lua-Epoll V0.0.1 by wenhao.ye"; lua_pushstring(L, ver); return 1; } static const struct luaL_Reg lua_f_epoll_mod[] = { {"create", lua_f_epoll_create}, {"version", lua_f_epoll_version}, {NULL, NULL}, }; /* mt = { __gc = lua_f_epoll_close, __index = { close = lua_f_epoll_close, register = lua_f_epoll_register, modify = lua_f_epoll_modify, unregister = lua_f_epoll_unregister, wait = lua_f_epoll_wait, }, }; epoll = { create = lua_f_epoll_create, version = lua_f_epoll_version, EPOLLIN = EPOLLIN, EPOLLPRI = EPOLLPRI, EPOLLOUT = EPOLLOUT, EPOLLRDNORM = EPOLLRDNORM, EPOLLRDBAND = EPOLLRDBAND, EPOLLWRNORM = EPOLLWRNORM, EPOLLWRBAND = EPOLLWRBAND, EPOLLMSG = EPOLLMSG, EPOLLERR = EPOLLERR, EPOLLHUP = EPOLLHUP, EPOLLRDHUP = EPOLLRDHUP, EPOLLONESHOT = EPOLLONESHOT, EPOLLET = EPOLLET, }; */ int luaopen_epoll(lua_State *L) { luaL_newmetatable(L, EPOLL_METATABLE_NAME); LTABLE_ADD_CFUNC(L, -1, "__gc", lua_f_epoll_close); lua_newtable(L); luaL_register(L, NULL, lua_f_epoll_func); lua_setfield(L, -2, "__index"); lua_pop(L, 1); luaL_register(L, "epoll", lua_f_epoll_mod); #define SETCONST(EVENT) lua_pushnumber(L, EVENT); lua_setfield(L, -2, #EVENT) // push const values into epoll table. SETCONST(EPOLLIN); SETCONST(EPOLLPRI); SETCONST(EPOLLOUT); SETCONST(EPOLLRDNORM); SETCONST(EPOLLRDBAND); SETCONST(EPOLLWRNORM); SETCONST(EPOLLWRBAND); SETCONST(EPOLLMSG); SETCONST(EPOLLERR); SETCONST(EPOLLHUP); SETCONST(EPOLLRDHUP); SETCONST(EPOLLONESHOT); SETCONST(EPOLLET); return 1; }
上述代码用到的关于epoll的操作,我封装在epoll_fd,h文件中:
// epoll_fd.h #ifndef _EPOLL_FD_H_ #define _EPOLL_FD_H_ #ifdef __cplusplus extern "C"{ #endif #include "def.h" #include "io_tools.h" // for fd_set_nonblock() #include <fcntl.h> #include <sys/epoll.h> #include <string.h> #include <errno.h> #include <unistd.h> #define EPOLL_DEFAULT_SIZE 1024 typedef struct epoll_fd { int epfd; size_t size; struct epoll_event *events; }epoll_fd_t; inline int epoll_fd_init(epoll_fd_t *self, int epfd, size_t size) { self->epfd = epfd; self->size = size; self->events = (struct epoll_event *)MALLOC(sizeof(struct epoll_event) * size); if(self->events == NULL) return -1; return 0; } inline int epoll_fd_release(epoll_fd_t *self) { safe_close(self->epfd); safe_free(self->events); return 0; } inline int epoll_fd_add(epoll_fd_t *self, int fd, int event) { if(fd_set_nonblock(fd) < 0){ ERR("fd_set_nonblock fail"); return -1; } struct epoll_event ev; ev.data.fd = fd; ev.events = event; if(epoll_ctl(self->epfd, EPOLL_CTL_ADD, fd, &ev) < 0){ ERR("epoll_ctl_add fail"); return -1; } return 0; } inline int epoll_fd_mod(epoll_fd_t *self, int fd, int event) { struct epoll_event ev; ev.data.fd = fd; ev.events = event; if(epoll_ctl(self->epfd, EPOLL_CTL_MOD, fd, &ev) < 0){ ERR("epoll_ctl_mod fail"); return -1; } return 0; } inline int epoll_fd_del(epoll_fd_t *self, int fd) { if(epoll_ctl(self->epfd, EPOLL_CTL_DEL, fd, NULL) < 0){ ERR("epoll_ctl_del fail"); return -1; } return 0; } inline int epoll_fd_wait(epoll_fd_t *self, int timeout) { return epoll_wait(self->epfd, self->events, self->size, timeout); } #ifdef __cplusplus } #endif #endif
代码很简单,也就不过多说明了,上述代码如果直接拿来用会编译不过,因为这些代码是在我的整个工程中,会用到其他的一些宏或者函数,工程代码地址: https://git.oschina.net/yewenhao/libutils
如果不想拿整个工程,只需要把DBG(), ERR(), 换成简单的打印函数printf即可,其他的函数如下:
#define RETERR(_e) DBG("err: %s", (_e)); lua_pushnil(L); lua_pushfstring(L, "%s: %s", strerror(errno), (_e)); return 2 #define LTABLE_ADD_CFUNC(L, index, name, func) lua_pushcfunction(L, (func)); lua_setfield(L, (index)-1, (name)); int fd_set_nonblock(int fd) { int val; if((val = fcntl(fd, F_GETFL, 0)) == -1){ perror("fcntl get"); return -1; } val |= O_NONBLOCK; if(fcntl(fd, F_SETFL, val) == -1){ perror("fcntl set"); return -1; } return 0; }
修改后的epoll模块的使用参考:
local epoll = require(‘epoll‘) print(epoll.version()) local epfd = epoll.create() epfd:register(fd, epoll.EPOLLIN) while true do local events, err= epfd:wait() if not err then -- handle events end -- ... end epfd:unregister(fd) epfd:close() print(‘done‘)