欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Lua_第28章 资源管理(上)

程序员文章站 2022-07-02 20:39:10
在前面一章介绍的数组实现方法,我们不必担心如何管理资源,只需要分配内存。 每一个表示数组的 userdatum 都有自己的内存,这个内存由 lua 管理。当数组变为垃圾 (也...

在前面一章介绍的数组实现方法,我们不必担心如何管理资源,只需要分配内存。 每一个表示数组的 userdatum 都有自己的内存,这个内存由 lua 管理。当数组变为垃圾 (也就是说,当程序不需要)的时候,lua 会自动收集并释放内存。

生活总是不那么如意。有时候,一个对象除了需要物理内存以外,还需要文件描述符、窗口句柄等类似的资源。(通常这些资源也是内存,但由的其他部分来管理)。 在这种情况下,当一个对象成为垃圾并被收集的时候,这些相关的资源也应该被释放。一些面向对象的语言为了这种需要提供了一种特殊的机制(称为 finalizer或者析构器)。lua以_gc 元方法的方式提供了 finalizers。这个元方法只对 userdata 类型的值有效。当一个userdatum 将被收集的时候,并且usedatum 有一个_gc 域,lua 会调用这个域的值 (应该是一个函数):以userdatum 作为这个函数的参数调用。这个函数负责释放与 userdatum 相关的所有资源。

为了阐明如何将这个元方法和 api作为一个整体使用,这一章我们将使用 lua扩展应用的方式,介绍两个例子。第一个例子是前面己经介绍的遍历一个目录的函数的另一种实现。第二个例子是一个绑定 expatcexpat 开源的 xml 解析器)实现的 xml 解析 器。

29.1 目录迭代器

前面我们实现了一个 dir 函数,给定一个目录作为参数,这个函数以一个table的方 式返回目录下所有文件。我们新版本的dir函数将返回一个迭代子,每次调用这个迭代 子的时候会返回目录中的一个入口(entry)。按新版本的实现方式,我们可以使用循环 来遍历整个目录:

 

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

 

在 c 语言中,我们需要 dir这种结构才能够迭代一个目录。通过 opendir 才能创建 一个 dir 的实例,并且必须显式的调用 closedir 来释放资源。我们以前实现的 dir 用一个 本地变量保存 dir 的实例,并且在获取目录中最后一个文件名之后关闭实例。但我们新实现的 dir中不能在本地变量中保存 dir 的实例,因为有很多个调用都要访问这个值,另外,也不能仅仅在获取目录中最后一个文件名之后关闭目录。如果程序循环过程中中断退出,迭代子根本就不会取得最后一个文件名,所以,为了保证 dir 的实例一定能够被释放掉,我们将它的地址保存在一个 userdatum 中,并使用这个 userdatum 的 gc的 元方法来释放目录结构。

尽管我们实现中userdatum的作用很重要,但这个用来表示一个目录的userdatum,并不需要在lua可见范围之内。dir函数返回一个迭代子函数,迭代子函数需要在lua的可见 范围之内。目录可能是迭代子函数的一个upvalue。这样一来,迭代子函数就可以直接访问这个结构(指目录结构,即userdatum),但是lua不可以(也不需要)访问这个结构。

总的来说,我们需要三个 c 函数第一,dir 函数,一个 lua 调用他产生迭代器的 工厂,这个函数必须打开 dir结构并将他作为迭代函数的 upvalue。第二,我们需要一 个迭代函数。第三,_gc 元方法,负责关闭 dir 结构。一般来说,我们还需要一个额外的函数来进行一些初始的操作,比如为目录创建 metatable,并初始化这个 metatable。

首先看我们的 dir 函数:

 

#include 
#include 
 
/* forward declaration for the iteratorfunction */
static int dir_iter (lua_state *l);
 
static int l_dir (lua_state *l) {
   const char *path = lual_checkstring(l, 1);
 
     /* create auserdatum to storea dir address */
    dir **d = (dir**)lua_newuserdata(l, sizeof(dir *));
 
     /* set its metatable */ 
     lual_getmetatable(l, "luabook.dir"); 
     ua_setmetatable(l, -2);
 
      /* try toopen the givendirectory */
      *d =opendir(path);
       if (*d == null)/* error openingthe directory? */
       lual_error(l, "cannot open %s: %s", path, strerror(errno));
 
         /* createsand returns theiterator function (its sole upvalue,the directory userdatum, is already on the stacktop */
         lua_pushcclosure(l, dir_iter, 1);
         return 1;
}

这儿有一点需要注意的,我们必须在打开目录之前创建 userdatum。如果我们先打开 目录,然后调用 lua_newuserdata 会抛出错误,这样我们就无法获取dir 结构。按照正确 的顺序,dir结构一旦被创建,就会立刻和 userdatum 关联起来;之后不管发生什么,_gc元方法都会自动的释放这个结构。

第二个函数是迭代器:

 

static int dir_iter (lua_state *l) {
    dir *d = *(dir**)lua_touserdata(l, lua_upvalueindex(1));
     struct dirent *entry;
     if ((entry = readdir(d)) != null) { 
      lua_pushstring(l, entry->d_name);
      return 1;
}
else return 0; /* no morevalues to return*/
}

gc 元方法用来关闭目录,但有一点需要小心:因为我们在打开目录之前创建 userdatum,所以不管 opendir 的结果是什么userdatum 将来都会被收集。如果 opendir 失败,将来就没有什么可以关闭的了:

 

static int dir_gc (lua_state *l) {
   dir *d = *(dir**)lua_touserdata(l, 1);
   if (d) closedir(d);
   return 0;
}

最后一个函数打开这个只有一个函数的库:

 

int luaopen_dir (lua_state *l) {
    lual_newmetatable(l, "luabook.dir");
   /* set its  gcfield */ 
   lua_pushstring(l, "_gc"); 
   lua_pushcfunction(l, dir_gc); 
   lua_settable(l,-3);

   /* register the  dirfunction */ 
   lua_pushcfunction(l, l_dir); 
   lua_setglobal(l, "dir");
 
return 0;
}

整个例子有一个注意点。开始的时候,dir_gc 看起来应该检查他的参数是否是一个目录。否则,一个恶意的使用者可能用其他类型的参数(比如,文件)调用这个函数导 致严重的后果。然而,在 lua 程序中无法访问这个函数:他被存放在目录的 metatable 中,lua 程序从来不会访问这些目录。