この最初の版は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. | ![]() |