第一版是為 Lua 5.0 所寫。儘管在很大程度上仍然適用於後來的版本,但仍有一些差異。
第四版針對 Lua 5.3,可在 Amazon 和其他書店購買。
購買書籍,您也可以協助 支援 Lua 專案。
![]() |
用 Lua 程式設計 | ![]() |
第 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 程式碼沒有(也不需要)。
總而言之,我們需要三個 C 函數。首先,我們需要 dir
函數,一個 Lua 用來建立迭代器的工廠;它必須開啟一個 DIR
結構,並將其作為迭代器函數的上值。其次,我們需要迭代器函數。第三,我們需要 __gc
元方法,用來關閉 DIR
結構。照例,我們也需要一個額外的函數來進行初始安排,例如建立目錄的元表並初始化此元表。
讓我們從 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
元方法會關閉目錄,但它必須採取一項預防措施:因為我們在開啟目錄之前建立使用者資料,所以無論 opendir
的結果如何,這個使用者資料都會被收集。如果 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 __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 程式無法存取這個函數:它只儲存在目錄的元表中,而 Lua 程式永遠不會存取那些目錄。
版權所有 © 2003–2004 Roberto Ierusalimschy。保留所有權利。 | ![]() |