【问题标题】:How to handle C++ exceptions when calling functions from Lua?从 Lua 调用函数时如何处理 C++ 异常?
【发布时间】:2011-06-04 16:37:27
【问题描述】:

我有一个可以从 Lua 调用的 C++ 函数。为了演示我的问题,这里举个例子:

int PushHello(lua_State *L){
    string str("Hello");
    lua_pushlstring(L, str.data(), str.length());
    return 1;
}

注意:我知道我不必在那里使用字符串变量,但它是用来演示问题的。

这是我的两个问题:

  1. 当我从 Lua 字符串构造函数调用此函数时,可能会抛出异常。那是问题吗? Lua 会处理它并正确展开 Lua 堆栈吗?我不这么认为。我该如何解决?我是否需要在所有此类代码周围添加try/catch 并将异常转换为 lua_error?没有更好的解决方案吗?

  2. 另一个我可能通过将 Lua 编译为 C++ 解决的问题是,如果使用 longjmp,lua_pushlstring() 调用 lua_error() 字符串析构函数将不会被调用。问题是否通过编译为 C++ 并抛出异常而不是使用 longjmp 来解决?

为了澄清,我可以看到问题 1 的可能解决方案是:

int PushHello(lua_State *L){
    string str;
    try{
        str.assign("Hello");
    catch(exception &e){
        luaL_error(L, e.what());
    }
    lua_pushlstring(L, str.data(), str.length());
    return 1;
}

但这非常丑陋且容易出错,因为try/catch 需要添加到许多地方。它可以作为一个宏来完成,并放置所有可以抛出的命令,但这不会更好。

【问题讨论】:

  • @Blaho 我认为这个堆栈交换proposal 可能会引起您的兴趣。如果它是显示您的支持并帮助它进入测试版! :)

标签: c++ exception lua


【解决方案1】:

我找到了一个合理的解决方案。问题是它是否正确。而不是导出(或通过 lua_cpcall 调用)原始函数 int PushHello(lua_State *L) 一个包装器 int SafeFunction<PushHello>(lua_State *L) 被导出/调用。包装器看起来像:

template<lua_CFunction func>
int SafeFunction(lua_State *L){
    int result = 0;
    try{
        result = func(L);
    }
    // transform exception with description into lua_error
    catch(exception &e){
        luaL_error(L, e.what());
    }
    // rethrow lua error - C++ Lua throws lua_longjmp*
    catch(lua_longjmp*){
        throw;
    }
    // any other exception as lua_error with no description
    catch(...){
        luaL_error(L, "Unknown error");
    }

    return result;
}

你怎么看?有什么问题吗?

【讨论】:

  • 你不应该从 catch 块调用 luaL_error。这将泄漏异常对象。您应该将错误消息推送到堆栈中,并稍后调用 lua_errorthese lines.
  • @Ignacio:我不明白异常对象是如何泄露的。从catch 块中抛出异常应该是安全的。我已将 Lua 编译为 C++。
  • 对不起。刚看到你的评论。你说得对。由于您已将 Lua 编译为 C++,因此 luaL_error 会抛出,因此不会泄漏任何对象。但是如果你将 Lua 编译为 C,luaL_error 将 longjmp,所以你捕获的异常不会被销毁(除其他外)。
  • 我已经发布了该模板功能的增强版。
【解决方案2】:

Juraj Blaho 的回答很棒。但是它有一个缺点:对于您使用int SafeFunction&lt;PushHello&gt;(lua_State *L) 导出的每个函数,编译器都会从模板生成所有 代码的副本,就像它是一个宏一样。当导出大量的小函数时,这将是一种浪费。

您可以通过定义一个通用的static 函数来执行所有工作来轻松避免该问题,而template 函数只是调用该通用函数:

static int SafeFunctionCommon(lua_State *L, lua_CFunction func){
    int result = 0;
    try{
        result = func(L);
    }
    // transform exception with description into lua_error
    catch(exception &e){
        luaL_error(L, e.what());
    }
    // rethrow lua error - C++ Lua throws lua_longjmp*
    catch(lua_longjmp*){
        throw;
    }
    // any other exception as lua_error with no description
    catch(...){
        luaL_error(L, "Unknown error");
    }

    return result;
}

template<lua_CFunction func>
int SafeFunction(lua_State *L){
    return SafeFunctionCommon(L, func);
}

【讨论】:

  • lua_longjmp* 来自哪里?尝试实现您的 try/catch,但在我能找到的任何地方都没有定义 lua_longjmp
  • @MintyAnt lua_longjmp 是在ldo.c (LUA 5.2.4) 中定义的内部结构。
【解决方案3】:

Lua 不会捕获 C++ 异常。如果你没有捕捉到它,它将向上传递调用堆栈,直到它被其他代码块捕捉或导致程序崩溃(未处理的异常)。如果你暴露给 Lua 的函数调用了可以抛出异常的函数,你应该在那个函数中处理它们。

【讨论】:

  • 我知道(即使 Lua 编译为 C++ 时也不完全正确),但问题是我怎样才能很好地捕捉到这些异常?
  • True(我假设您使用 Lua 作为脚本来调用用 C++ 代码编写的函数 - 也就是说,实际上并未编译您的 Lua 代码)。我会捕获所有已知的可能从您调用的函数中抛出的异常(例如,如果一个函数可以抛出 OutOfBoundsException 并且您可以优雅地处理它,则捕获它),并使用catch (...) 捕获其他所有内容并退出该函数优雅地(执行任何需要的清理等)。编辑:基本上,我总是将我暴露给脚本语言的任何函数视为nothrow 函数。
  • 问题是,lua_error 也会发出异常,所以你不能 catch(...)。
  • 我不会使用 lua_error 将异常从 C++ 推送到 Lua。我更喜欢尽可能处理它们创建区域中的异常。
【解决方案4】:

我不会使用 lua_error 来表示在 lua 功能之外发生的错误。如果您要添加可以在 lua 范围内调用的其他 lua 函数,则将使用 lua_error。如果在执行该函数时发生错误,那么 lua_error 将是合适的。

这也是Stack unwinding in C++ when using Lua的副本

编辑

如果你担心字符串析构函数被调用为什么不这样做:

try
{
    string str("Hello");
    lua_pushlstring(L, str.data(), str.length());
}
catch (exception& e)
{
     luaL_error(L, e.what());
}

我知道这与您的建议略有不同,但有所不同。如果抛出异常,try{} 内的堆栈上的任何内容都会破坏。只要确保您想要破坏的任何东西都在该尝试范围内。

【讨论】:

  • 我不认为它是重复的。我可能对 C++ 堆栈展开没有问题。问题是如果抛出异常,Lua 会捕获它并正确展开它的堆栈吗?正如我所说,该函数是从 Lua 调用的。我会在我的问题中澄清这一点。
  • @Juraj 你是对的,你确实说过它会在 lua 脚本本身中被调用。我看错了。但是,我仍然认为另一个问题回答了您的问题。它建议使用 Luabind 处理任何可能引发的异常。然后 Lua 将处理该异常并正确展开。我认为主要区别在于您询问 Lua 堆栈本身是否会正确展开,而不是当前函数的堆栈。根据给出的答案,如果您使用 luabind,lua 会捕获它
  • 是的,这是一个解决方案,但我不想使用 LuaBind。
【解决方案5】:

如果你将 Lua 编译为 C++,那么它们将使用 C++ 异常作为错误,而如果你编译为 C,它们将使用 longjmp/setjmp。这基本上意味着抛出这样的异常没有什么大不了的。

【讨论】:

  • 问题不是扔,而是抓。当 C++ 代码(如字符串构造函数)抛出异常时,Lua 将如何处理呢?错误消息我会得到什么?
猜你喜欢
  • 2021-08-27
  • 1970-01-01
  • 1970-01-01
  • 2015-07-14
  • 2020-12-28
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2014-09-01
相关资源
最近更新 更多