Lua 和 C++ 之间的错误处理 |
|
[ 注意:此页面尚未完工。欢迎添加评论。 ]
如果您在 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 函数,该函数调用 C++ 代码,该代码又调用 Lua 代码。在这种情况下,C++ 代码可能会 pcall 到 Lua 并将任何错误消息转换为 C++ 异常,该异常会传播到 C 函数。然后,C 函数需要将 C++ 异常转换为 `lua_error()`,该异常会 `longjmp` 到 Lua。只有当调用链中的 C++ 代码以 RAII 方式分配内存时,才需要这种转换为 C++ 异常。
__gc
元方法的正确元表附加它。然后,您应该创建可能需要释放的对象并将其放入用户数据中。这将避免资源泄漏,因为如果随后抛出错误,__gc
方法最终会被调用。一个很好的例子是标准的 liolib.c
,它使用这种策略来避免泄漏文件描述符。-- RiciLake