烦恼一般都是想太多了。

0%

PIL.32.Lua中的资源管理

PIL.31.C中的自定义用户定义类型 中,我们实现的 Boolean 数组不用担心资源管理的事情。这些数组只需要内存空间。每个代表一个数组的 Full Userdata 都有其自己的内存空间,这些内存空间是由 Lua 进行管理的。当一个数组变成垃圾(就是说程序不可访问了),Lua 最终会回收和释放那些内存。

但事情不是总是这么简单啊。有的时候,有些对除了内存之外,可能还需要其他的资源,比如说文件描述符,窗口句柄等等。(这些资源也是内存,但不过是由其他部分的系统进行管理)。在这种情况下,当这个对象被回收的时候,与之相应的其他资源也应该被释放。

Lua 提供了一个元方法 __gc 来进行资源的释放。为了演示这个元方法的使用,本章会为外部特性开发两个 Lua 绑定。第一个例子就是实现一个遍历目录的函数。第二个例子就是个比较大的,绑定到 Expat,一个开源 XML 解析器。

目录遍历

我们将会像下面一样使用我们的实现函数:

for fname in dir.open(".") do
print(fname)
end

dir.open 会返回一个迭代器,然后可用其来遍历整个目录,在 C 中,我们需要一个 DIR 结构(典型的是 POSIX 系统)。

DIR 结构的实例通过 opendir 来建立,其必须显式的通过 closedir 进行释放。在我们新的实现中,我们无法将 DIR 的实例保存在一个本地变量中,因为其必须在几个调用间查询这个值。更深入一点,我们不能只是在获取了最后一个名字后就关闭目录;如果程序跳出了循环,迭代器将永远也不会获取到最后一个名字。因此,为了确保 DIR 实例总是能被释放,我们将 DIR 实例的地址放到一个 userdata 内,并用这个 Full Userdata 的 __gc 元方法来释放目录结构实例。

尽管在这个过程中 Full Userdata 的角色很重要,但其并不一定需要对 Lua 可见。 dir.open 会返回一个迭代函数,这就是 Lua 所看到的。这个目录(Full Userdata)可以是迭代函数的上值。这样的话,迭代函数就对这个结构有直接的访问权限,但 Lua 代码却是不能的。

总的来说,我们需要三个 C 函数。

  1. dir.open 一个工厂函数,Lua 调用它来建立迭代器;其必须打开一个 DIR 结构,同时将此结构作为上日照和迭代器一起作为一个闭包。
  2. 迭代函数
  3. __gc 元方法。

通常,我们也会需要一个额外的函数来进行初始化,例如是建立和初始化目录的元表。

#include <dirent.h>
#include <errno.h>
#include <string.h>

#include "lua.h"
#include "lauxlib.h"

/* forward declaration for the iterator function */
static int dir_iter (lua_State *L);

static int l_dir (lua_State *L) {
const char *path = luaL_checkstring(L, 1);

/* create a userdata to store a DIR address */
DIR **d = (DIR **)lua_newuserdata(L, sizeof(DIR *));

/* pre-initialize it */
*d = NULL;

/* set its metatable */
luaL_getmetatable(L, "LuaBook.dir");
lua_setmetatable(L, -2);

/* try to open the given directory */
*d = opendir(path);
if (*d == NULL) /* error opening the directory? */
luaL_error(L, "cannot open %s: %s", path, strerror(errno));

/* creates and returns the iterator function;
its sole upvalue, the directory userdata,
is already on the top of the stack */
lua_pushcclosure(L, dir_iter, 1);
return 1;
}

函数中有个需要注意的地方就是我们必须在打开目录前建立 Full Userdata。如果不这样做,如果我们先打开了目录,结果在 建立 Full Userdata 的时候出现错误,那么就会造成这个目录结构的泄漏。我们按照这样的顺序进行操作,一旦 DIR 结构建立,那么就会立即与 Userdata 关联起来,无论之后发生什么,都可以通过 Userdata 的 __gc 元方法来回收内存。

还有一个需要注意的地方就是 Userdata 的一致性。一旦我们设置了它的元表,那么 __gc 元方法无论如何都会被调用。所以,在设置元表前,我们要先初始化 Userdata 为 NULL 来保证其有一个定义良好的值。

static int dir_iter (lua_State *L) {
DIR *d = *(DIR **)lua_touserdata(L, lua_upvalueindex(1));
struct dirent *entry = readdir(d);
if (entry != NULL) {
lua_pushstring(L, entry->d_name);
return 1;
}
else return 0; /* no more values to return */
}
static int dir_gc (lua_State *L) {
DIR *d = *(DIR **)lua_touserdata(L, 1);
if (d) closedir(d);
return 0;
}

static const struct luaL_Reg dirlib [] = {
{"open", l_dir},
{NULL, NULL}
};

int luaopen_dir (lua_State *L) {
luaL_newmetatable(L, "LuaBook.dir");

/* set its __gc field */
lua_pushcfunction(L, dir_gc);
lua_setfield(L, -2, "__gc");

/* create the library */
luaL_newlib(L, dirlib);
return 1;
}

代码很直观,迭代器从其上值获取 DIR 结构的地址,然后调用 readdir 来读取目录。

dir_gc 函数给 __gc 元方法使用。这个元方法会关闭一个目录。我们需要注意一个情况:如果在初始化的时候,目录可能会是 NULL。

XML 解析器

有空再看了。