【问题标题】:luabind abort when trying to call object method with lua errors尝试使用 lua 错误调用对象方法时,luabind 中止
【发布时间】:2016-01-06 13:56:38
【问题描述】:

我已经使用http://www.rasterbar.com/products/luabind/docs.html#deriving-in-lua 中的示例在 c++ 中定义了一个可以在 lua 中派生的类:

class base
{
public:
    base(const char* s)
    { std::cout << s << "\n"; }

    virtual void f(int a)
    { std::cout << "f(" << a << ")\n"; }
};

struct base_wrapper : base, luabind::wrap_base
{
    base_wrapper(const char* s)
        : base(s)
    {}

    virtual void f(int a)
    {
        call<void>("f", a);
    }

    static void default_f(base* ptr, int a)
    {
        return ptr->base::f(a);
    }
};

...

module(L)
[
    class_<base, base_wrapper>("base")
        .def(constructor<const char*>())
        .def("f", &base::f, &base_wrapper::default_f)
];

然后我在 lua 中创建了一个派生类:

class 'base_derived' (base)

function base_derived:__init(str)
    base.__init(self,str)
end

function base_derived:f()
    this_function_doesnt_exist()
end

任何对 'f' 的调用都应该抛出一个 lua 错误,如果我在 lua 中这样做,它可以正常工作:

local x = base_derived("Test")
x:f() -- Throws "attempt to call a nil value..." error

我想做与此相同的操作,但在 c++ 中:

auto g = luabind::globals(l);
auto r = g["base_derived"];
if(r)
{
    luabind::object o = r("Test");
    auto gm = luabind::object_cast<base_wrapper*>(o);
    if(gm != nullptr)
    {
        try
        {
            luabind::call_member<void>(o,"f",5);
        }
        catch(luabind::error &e)
        {
            std::cout<<"[LUA] Error: "<<e.what()<<std::endl;
        }
    }
    o.push(l);
}

但是,'luabind::call_member' 调用会导致 'luabind/detail/call_member.hpp' 中止,第 258 行:

// Code snippet of luabind/detail/call_member.hpp
~proxy_member_void_caller()
{
    if (m_called) return;

    m_called = true;

    // don't count the function and self-reference
    // since those will be popped by pcall
    int top = lua_gettop(L) - 2;

    // pcall will pop the function and self reference
    // and all the parameters

    push_args_from_tuple<1>::apply(L, m_args);
    if (pcall(L, boost::tuples::length<Tuple>::value + 1, 0))
    {
        assert(lua_gettop(L) == top + 1);
#ifndef LUABIND_NO_EXCEPTIONS
////////////////////////////////////////////
        throw luabind::error(L); // LINE 258
////////////////////////////////////////////
#else
        error_callback_fun e = get_error_callback();
        if (e) e(L);

        assert(0 && "the lua function threw an error and exceptions are disabled."
            "If you want to handle this error use luabind::set_error_callback()");
        std::terminate();
#endif
    }
    // pops the return values from the function
    stack_pop pop(L, lua_gettop(L) - top);
}

实际上并没有抛出该行中的异常,但它是导致中止的原因。

但是,只有当 lua 函数导致 lua 错误时,才会发生中止。如果我评论 'this_function_doesnt_exist()' 调用,lua 和 c++ 版本都可以正常运行。

为什么是'throw luabind::error(L);'导致中止,即使存在潜在的 lua 错误,我该怎么做才能安全地从 c++ 调用该函数?

// 编辑: 这是中止时的调用堆栈(当调用 'luabind::call_member(o,"f",5);' 时):

>   vcruntime140d.dll!__CxxFrameHandler(EHExceptionRecord * pExcept, unsigned __int64 RN, _CONTEXT * pContext, _xDISPATCHER_CONTEXT * pDC) Line 213 C++
    ntdll.dll!RtlpExecuteHandlerForException()  Unknown
    ntdll.dll!RtlDispatchException()    Unknown
    ntdll.dll!KiUserExceptionDispatch() Unknown
    KernelBase.dll!RaiseException() Unknown
    vcruntime140d.dll!_CxxThrowException(void * pExceptionObject, const _s__ThrowInfo * pThrowInfo) Line 136    C++
    server.dll!luabind::detail::proxy_member_void_caller<boost::tuples::tuple<int const * __ptr64,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type> >::~proxy_member_void_caller<boost::tuples::tuple<int const * __ptr64,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type,boost::tuples::null_type> >() Line 258    C++

这是中止消息:

【问题讨论】:

  • 你说你没有得到那个异常但是得到了一些其他的中止?你得到什么中止
  • 我不太清楚,一旦到达 throw-call 就会发生中止(断言不是导致它)。我在问题中止时添加了部分调用堆栈。

标签: c++ exception lua abort luabind


【解决方案1】:

参见第 254 行:

if (pcall(L, boost::tuples::length<Tuple>::value + 1, 0))

在这里,pcall 要求 lua 执行您的 x:f(5) 等效调用。显然,这段代码返回了一个错误,因为 pcall() 返回的东西不是零。这是意料之中的,因为您确实通过调用this_function_doesnt_exist() 在lua 中创建了一个错误。这也解释了为什么在您评论 this_function_doesnt_exist() 调用时不会发生中止。

然后,到 C++ 代码:

     throw luabind::error(L);

这个错误是从析构函数中抛出的:~proxy_member_void_caller(),结果是throwing an exception from a destructor is bad practice。众所周知,这个问题是 luabind (see this question) 触发 abort 调用,甚至没有抛出。

解决办法是在~proxy_member_void_caller()的签名中加上noexcept(false),像这样:

~proxy_member_void_caller() noexcept(false)

【讨论】:

  • 太烦人了,我一直忘记做这个改变,你也应该在 ~proxy_member_caller() 等其他变体上设置 noexcept(false)
猜你喜欢
  • 2019-01-06
  • 2015-06-15
  • 2012-07-20
  • 2011-07-30
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多