この最初の版はLua 5.0用に書かれました。依然として以降のバージョンにも概ね関連がありますが、いくつかの相違点があります。
第4版はLua 5.3を対象とし、Amazonやその他の書店で入手できます。
書籍を購入することで、Luaプロジェクトをサポートすることにもなります。
![]() |
Programming inLua | ![]() |
| 第4部. C API 第29章. リソースの管理 |
以前、特定のディレクトリにあるすべてのファイルを格納したテーブルを返すdir関数を実装しました。新しい実装では、呼び出すたびに新しいエントリを返すイテレータを返します。この新しい実装により、以下のようにループを使ってディレクトリを走査できるようになります。
for fname in dir(".") do print(fname) end
Cでディレクトリを反復処理するには、DIR構造体が必要です。DIRのインスタンスはopendirによって作成され、closedirを呼び出して明示的に解放する必要があります。以前のdirの実装では、ローカル変数としてDIRインスタンスを保持し、最後のファイル名を取得した後にそのインスタンスを閉じました。新しい実装では、このDIRインスタンスをローカル変数に保持することはできません。複数の呼び出しでこの値を照会する必要があるためです。さらに、最後の名前を取得した後にのみディレクトリを閉じることができません。プログラムがループを中断した場合、イテレータはこの最後の名前を決して取得しません。したがって、DIRインスタンスが確実にいつでも解放されるように、そのアドレスをユーザーデータに格納し、ユーザーデータの__gcメタメソッドを使用してディレクトリ構造体を解放します。
実装における中心的な役割にもかかわらず、ディレクトリを示すこのユーザーデータはLuaからは可視である必要はありません。dir関数はイテレータ関数を返します。Luaに表示されるのはこれです。ディレクトリはイテレータ関数のアップバリューになります。こうしたことから、イテレータ関数はこの構造体に直接アクセスできますが、Luaコードはできません(そしてその必要もありません)。
全体として、3つのC関数が必要です。最初に、Luaがイテレータを作成するために呼び出すファクトリであるdir関数が要ります。DIR構造体をオープンしてイテレータ関数のアップバリューとして設定する必要があります。次に、イテレータ関数が必要です。3番目に、DIR構造体を閉じる__gcメタメソッドが必要です。通常通り、ディレクトリ向けのメタテーブルを作成したり、このメタテーブルを初期化したりするなど、初期の手配をするための追加の関数も必要です。
dir関数からコードを開始しましょう。
#include <dirent.h>
#include <errno.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 userdatum to store a DIR address */
DIR **d = (DIR **)lua_newuserdata(L, sizeof(DIR *));
/* 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 userdatum,
is already on the stack top */
lua_pushcclosure(L, dir_iter, 1);
return 1;
}
ここで微妙な点は、ディレクトリを開く前にユーザーデータを作成する必要があることです。最初にディレクトリを開き、その後lua_newuserdataの呼び出しでエラーが発生した場合、DIR構造体が失われます。正しい順序では、DIR構造体は作成されるとすぐにユーザーデータに関連付けられます。その後何が起こっても、__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 more values to return */
}
__gcメタメソッドはディレクトリを閉じますが、1つの注意が必要です。ディレクトリを開く前にユーザーデータを作成するため、このユーザーデータはopendirの結果にかかわらず収集されます。opendirが失敗した場合、閉じるべきものは何もありません。
static int dir_gc (lua_State *L) {
DIR *d = *(DIR **)lua_touserdata(L, 1);
if (d) closedir(d);
return 0;
}
最後に、この1つの関数のライブラリを開く関数があります。
int luaopen_dir (lua_State *L) {
luaL_newmetatable(L, "LuaBook.dir");
/* set its __gc field */
lua_pushstring(L, "__gc");
lua_pushcfunction(L, dir_gc);
lua_settable(L, -3);
/* register the `dir' function */
lua_pushcfunction(L, l_dir);
lua_setglobal(L, "dir");
return 0;
}
この例は最初から、興味深い繊細さを持っています。一見すると、dir_gcは自身が引数かどうかチェックする必要があるように思えるかもしれません。そうしなかった場合、悪意のあるユーザーは他の種類のユーザーデータ(例えばファイル)で呼び出しをして、ひどい結果になる可能性があります。しかし、Luaプログラムはこの関数にアクセスする方法がありません。dirのmetatableのみに格納されていて、Luaプログラムがそれらのdirにアクセスすることは決してありません。
| Copyright © 2003–2004 Roberto Ierusalimschy. All rights reserved. | ![]() |