Подтвердить что ты не робот

Как обрабатывать исключения С++ при вызове функций из Lua?

У меня есть работающая функция С++, которую я могу вызвать из Lua. Для демонстрации моей проблемы здесь приведен пример:

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

Примечание. Я знаю, что мне не нужно использовать строковую переменную, но она должна продемонстрировать проблему.

Вот мои две проблемы:

  • Когда я вызываю эту функцию из конструктора строки Lua, может возникнуть исключение. Это проблема? Будет ли Lua обрабатывать его и правильно раскрутить Lua-стек? Я так не думаю. Как я могу это решить? Нужно ли добавлять try/catch вокруг всего такого кода и преобразовывать исключение в lua_error? Разве нет лучшего решения?

  • Еще одна проблема, которую я, вероятно, решил, компилируя Lua как С++, - это когда lua_pushlstring() вызывает lua_error() string destructor не будет вызываться, если был использован longjmp. Является ли проблема решена путем компиляции как С++ и исключения исключений вместо использования 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 нужно будет добавить во многие места. Это может быть сделано как макрос и помещаться вокруг каждой команды, которая может бросать, но это было бы не намного приятнее.

4b9b3361

Ответ 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;
}

Что вы думаете об этом? Вы видите какие-либо проблемы?

Ответ 2

Ответ Juraj Blaho велик. Однако он имеет недостаток: для каждой функции, которую вы экспортируете с помощью int SafeFunction<PushHello>(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);
}

Ответ 3

Я бы не использовал lua_error для обозначения ошибки, возникающей вне функциональности lua. lua_error будет использоваться, если вы добавляете дополнительные функции lua, которые могут быть вызваны в пределах lua. Тогда lua_error будет уместным, если при выполнении этой функции возникнет ошибка.

Также это дубликат Stack unwinding в С++ при использовании Lua

Edit

Если вы беспокоитесь о том, что деструктор строки вызывается, почему бы не сделать это:

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

Я понимаю, что это тонкое изменение того, что вы предложили, но есть разница. Если выбрано исключение, то что-либо из стека в try{} будет разрушено. Просто убедитесь, что все, что вы хотите уничтожить, находится внутри этой попытки.

Ответ 4

Lua не поймает исключение С++. Если вы не поймаете его, он будет передан в стек вызовов, пока он не будет пойман каким-либо другим блоком кода или не сработает сбой программы (необработанное исключение). Если функции, которые вы выставляете для вызовов Lua, которые могут генерировать исключения, вы должны обрабатывать их в этой функции.

Ответ 5

Если вы скомпилируете Lua как С++, тогда они будут использовать исключения С++ как ошибки, тогда как если вы скомпилируете C, то они будут использовать longjmp/setjmp. Это в основном означает, что нечего делать это исключение.