Lua 與 Cplusplus 之間的錯誤處理 |
|
[注意:此頁面算是進行中的專案。歡迎提供其他說明。]
如果您使用 C 編譯 Lua,您必須將 #include <lua.h>
放在 extern "C"
中,或使用等效的 #include <lua.hpp>
,否則您會得到連結器錯誤。
// alternately do #include <lua.hpp> extern "C" { #include <lua.h> }
Lua 本身通常在 C 下編譯,但也可以改在 C++ 下編譯。如果在 C 下編譯,Lua 會使用 [longjmp]' 來實作錯誤處理 (lua_error
)。如果在 C++ 下編譯,Lua 預設會使用 C++ 例外。請參閱 luaconf.h
中 LUAI_THROW
的宣告。同時也請參閱 LuaList:2007-10/msg00473.html 。
C++ 例外處理 (正確地展開堆疊並呼叫解構函數) 與僅拋出堆疊的 Lua longjmp
之間有衝突,因此如果 Lua 在 C 下編譯,您必須更小心,以確保所有必要的 C++ 解構函數 都已呼叫,避免記憶體或資源外洩。
當 C++ 將 Lua 呼叫為擴充功能時,Lua 作業通常 (但並非總是) 需要包裝在 pcall
中為 lua_CFunction
。例如,請參閱 [PIL 25.2] 或 PIL2 25.3。(關於這些條件的詳細說明,Rici 會在下面提供。) 通常這個 lua_CFunction
僅由一個呼叫者使用。因此,將 lua_CFunction 做為呼叫函式的區域變數 (如閉包) 可能很有用。在 C++ 中,lua_CFunction 可以定義在一個結構中,如下所示
int operate(lua_State * L, std::string & s, int x, int y) { std::string msg = "Calling " + s + "\n"; // can raise exception; must be destroyed cout << msg; // caution: this code by raise exceptions but not longjump. struct C { static int call(lua_State * L) { // caution: this code may longjump but not raise exceptions. C * p = static_cast<C*>(lua_touserdata(L, 1)); assert(lua_checkstack(L, 4)); lua_getglobal("add"); // can longjump assert(lua_isfunction(L, -1)); lua_pushstring(L, s); // can longjump lua_pushnumber(L, p->x); lua_pushnumber(L, p->y); lua_call(L, 3, 1); // can longjump p->z = lua_tonumber(L, -1); assert(lua_isnumber(L, -1)); return 0; } const char * s; int x; int y; int z; } p = {s.c_str(), x, y, 0}; int res = lua_cpcall(L, C::call, &p); // never longjumps if (res != 0) { handle_error(L); // do something with the error; can raise exception //note: we let handle_error do lua_pop(L, 1); } return p.z; }
現在,錯誤處理乍看之下有些棘手。lua_getglobal
、lua_pushstring
和 lua_call
呼叫可能會產生 lua_error()
,也就是說,如果 Lua 在 C 中編譯,就會產生 longjmp
。保護呼叫外部的 lua_cpcall
是安全的,因為它不會產生 lua_error()
(不像使用在 lua_pushcfunction
之後的 lua_pcall
,可能會在記憶體分配失敗時產生 lua_error
)。與 C++ 例外處理不同,longjmp
會略過堆疊中物件的任何解構函數 (通常用於 C++ 中的 RAII)。
另一個問題是如果 lua_cpcall
回傳失敗結果,我們該如何處理?我們有可能直接在原地處理錯誤、lua_pop
它,然後繼續執行。但更常需要在呼叫鏈中更淺的位置處理錯誤。一種更好的解決方案可能是將錯誤訊息留在 Lua 堆疊中,並確保在 catch 區塊中消耗時執行 lua_pop
#include <stdexcept> #include <boost/shared_ptr.hpp> /** * C++ exception class wrapper for Lua error. * This can be used to convert the result of a lua_pcall or * similar protected Lua C function into a C++ exception. * These Lua C functions place the error on the Lua stack. * The LuaError class maintains the error on the Lua stack until * all copies of the exception are destroyed (after the exception is * caught), at which time the Lua error object is popped from the * Lua stack. * We assume the Lua stack is identical at destruction as * it was at construction. */ class LuaError : public std::exception { private: lua_State * m_L; // resource for error object on Lua stack (is to be popped // when no longer used) boost::shared_ptr<lua_State> m_lua_resource; LuaError & operator=(const LuaError & other); // prevent public: // Construct using top-most element on Lua stack as error. LuaError(lua_State * L); LuaError(const LuaError & other); ~LuaError(); virtual const char * what() const throw(); }; static void LuaError_lua_resource_delete(lua_State * L) { lua_pop(L, 1); } LuaError::LuaError(lua_State * L) : m_L(L), m_lua_resource(L, LuaError_lua_resource_delete) { } LuaError::LuaError(const LuaError & other) : m_L(other.m_L), m_lua_resource(other.m_lua_resource) { } const char * LuaError::what() const throw() { const char * s = lua_tostring(m_L, -1); if (s == NULL) s = "unrecognized Lua error"; return s; } LuaError::~LuaError() { }
範例用法
for(int n=1; n < 100; n++) { try { string s = "123123123123123123"; // note: may throw bad_alloc // ... int res = lua_cpcall(L, call, NULL); if (res != 0) throw LuaError(L); } catch(exception & e) { cout << e.what() << endl; } }
另外一個例子是如果 Lua 執行一個 C 函式,而此函式執行一個呼叫 Lua 程式碼的 C++ 程式碼。在這種情況下,C++ 程式碼可能 pcall 成 Lua 並將任何錯誤訊息轉換成一個 C++ 例外,而這個例外會傳播到 C 函式。C 函式隨後需要將 C++ 例外轉換成一個「lua_error()」,而這個「lua_error()」會 longjmp 到 Lua。如果呼叫鏈中的 C++ 程式碼以 RAII 方式分配記憶體,才需要轉換成 C++ 例外。
DavidManura