2011-01-06 16 views
21

Tengo una función de C++ en funcionamiento a la que puedo llamar desde Lua. Para demostrar mi problema aquí es un ejemplo:¿Cómo manejar las excepciones de C++ al llamar a funciones desde Lua?

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

Nota: Yo sé que no tengo que usar variable de cadena allí, pero está ahí para demostrar el problema.

Aquí están mis dos problemas:

  1. Cuando llamo a esta función desde Lua constructor de cadena puede producir una excepción. ¿Es eso un problema? ¿Lo manejará Lua y desenrollará correctamente la pila Lua? No lo creo. ¿Cómo puedo resolver eso? ¿Debo agregar try/catch a todo este código y convertir la excepción a lua_error? ¿No hay una mejor solución?

  2. Otro problema que probablemente he resuelto al compilar Lua como C++ es cuando lua_pushlstring() llama a lua_error() cadena destructor no se llamaría si se usara longjmp. ¿El problema se resuelve compilando como C++ y lanzando excepciones en lugar de usar longjmp?

Para aclarar, posible solución que puedo ver a un problema 1 sería la siguiente:

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; 
} 

Pero eso es muy feo y propenso a errores como try/catch tendría que ser añadido a muchos lugares. Se podría hacer como una macro y poner cada comando que pueda arrojar, pero eso no sería mucho mejor.

+0

@Blaho creo que esta pila de intercambio [propuesta] (http : //area51.stackexchange.com/proposals/11464/code-review? referrer = aWNm_PdciyFqjFW8CUacGw2 "revisión de código") puede ser de su interés. ¡Si es muestra tu apoyo y ayuda a ponerlo en beta! :) – greatwolf

Respuesta

8

He encontrado una solución razonable. La pregunta es si es correcto. En lugar de exportar (o llamar a través de lua_cpcall) la función original int PushHello(lua_State *L) se exporta/llama un contenedor int SafeFunction<PushHello>(lua_State *L).El contenedor se ve así:

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; 
} 

¿Qué opina sobre eso? ¿Ves algún problema?

+0

No debe llamar a _luaL_error_ desde el bloque catch. Eso filtrará el objeto de excepción. Debería insertar el mensaje de error en la pila y llamar a _lua_error_ más tarde. Algo a lo largo de [estas líneas] (https://github.com/ignacio/LuaCppBridge51/blob/master/lcbBaseObject.h#L245). – Ignacio

+0

@Ignacio: No entiendo cómo se puede filtrar el objeto de excepción. Debería ser seguro lanzar una excepción desde el bloque 'catch'. He compilado Lua como C++. –

+0

Lo siento. Acabo de ver tu comentario. Tienes razón. Como tiene Lua compilado como C++, lanzará luaL_error, por lo que no se filtrará ningún objeto. Pero si tiene Lua compilada como C, luaL_error hará una pausa larga, por lo que la excepción que haya atrapado no se destruirá (entre otras cosas). – Ignacio

1

No utilizaría lua_error para denotar un error que ocurra fuera de la funcionalidad lua. lua_error se usaría si está agregando funciones lua adicionales que se pueden invocar dentro de los límites de lua. Entonces lua_error sería apropiado si se produce un error al ejecutar esa función.

También este es un duplicado de Stack unwinding in C++ when using Lua

Editar

Si usted está preocupado por el destructor cadena que se llama por qué no hacer esto:

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

me di cuenta que es una sutil cambie a lo que sugirió, pero hay una diferencia. Si se lanza una excepción, cualquier cosa en la pila dentro del try{} se destruirá. Solo asegúrate de que todo lo que quieras destruir esté dentro de ese intento.

+0

No creo que sea un duplicado. Probablemente no tenga problemas con el desenrollado de la pila C++. El problema es si se lanza una excepción, ¿la atrapará Lua y desenrollará su pila correctamente? Y como dije, la función SE llama desde Lua. Voy a aclarar eso en mi pregunta. –

+0

@Juraj tienes razón, dijiste que se llamaría dentro de un script lua. Leí mal. Sin embargo, sigo pensando que la otra pregunta responde a tu pregunta. Recomienda usar Luabind para cualquier excepción que se lanzaría. Lua manejará esa excepción y se relajará correctamente. Creo que la principal diferencia es que estás preguntando si la pila Lua se desenrollará correctamente en comparación con la pila de la función actual. Según la respuesta dada sí lua lo detectará si usa luabind –

+0

Sí, esa es una solución, pero no quiero usar LuaBind. –

0

Si compila Lua como C++, entonces usarán excepciones de C++ como errores, mientras que si compila como C, usarán longjmp/setjmp. Esto básicamente significa que no hay gran problema arrojando tal excepción.

+0

El problema no es tirar, sino atrapar. Cuando un código de C++ (como el constructor de cadenas) arroja una excepción, ¿cómo manejará Lua esto? ¿Qué obtengo por un mensaje de error? –

1

Lua no detectará la excepción de C++. Si no lo captas, se pasará por la pila de llamadas hasta que sea capturado por algún otro bloque de código o provoque la falla del programa (excepción no controlada). Si las funciones que expone a Lua llaman a funciones que pueden arrojar excepciones, debe manejarlas en esa función.

+0

Sé (incluso que no es exactamente cierto cuando Lua se compila como C++), pero el problema es ¿cómo puedo captar esas excepciones muy bien? –

+0

Verdadero (suponía que estaba usando Lua como script para llamar a una función escrita en su código C++, es decir, no compila realmente su código Lua). Capturaría todas las excepciones posibles conocidas que pueden arrojarse de las funciones que llama (por ejemplo, si una función puede arrojar una excepción OutOfBoundsException y puede manejarla con gracia, atraparla), y usar 'catch (...)' para atrapar todo else y salir de la función con gracia (realizar cualquier limpieza necesaria, etc.). EDITAR: Básicamente, siempre trato cualquier función que exponga a un lenguaje de scripting como una función 'nothrow'. –

+0

El problema es que el lua_error también emite una excepción, por lo que no se puede capturar (...). –

4

Juraj Blaho la respuesta es genial. Sin embargo, tiene un inconveniente: para cada función que exporta con int SafeFunction<PushHello>(lua_State *L), el compilador generará una copia de todo el código de la plantilla, como si fuera una macro. Cuando se exportan numerosas funciones pequeñas, esto será un desperdicio de espacio.

Usted puede evitar el problema mediante la definición de una función común static realizar todo el trabajo, y la función template simplemente llama a esa función común:

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); 
} 
+0

¿De dónde viene el lua_longjmp *? Intentando implementar su try/catch, pero lua_longjmp no está definido en ningún lugar. Puedo encontrar – MintyAnt

+1

@MintyAnt 'lua_longjmp' es una estructura interna definida en [' ldo.c'] (https://github.com/lua/lua/blob /5.2.4/src/ldo.c#L76) (LUA 5.2.4). – Lekensteyn

Cuestiones relacionadas