2011-10-25 10 views
5

Ok, esta puede no ser la mejor decisión de diseño, y realmente no quiero usar algo como LuaBind ... Tenía curiosidad si lo siguiente es posible en C++ 03 (C++ 11 lo hace posible con plantillas variadic). Además, estoy seguro de que esto se ha preguntado antes, ¡pero no pude encontrar una respuesta directa!C++ llamar a la función lua con parámetros variables

Decir que tengo un método de ayuda para llamar a funciones de Lua de código:

void CallFunction(char* functionName, ...); 

que potencialmente puede aceptar un número N de argumentos (usando va_arg o cualquier otro método de múltiples args)

¿Cómo puedo , si es posible, resuelva el tipo de cada parámetro y páselo al lua_push {type}() apropiado; función antes de llamar a la función lua deseada?

No estoy seguro si esto se puede hacer con var_arg, porque tiene que saber el tipo cuando agarra el parámetro, traté de agarrarlo con void * y pasarlo a una plantilla especializada, pero intenta pasarlo a la plantilla.

¡Espero que alguien mucho mejor en C++ tenga un truco o dos! Gracias montones

+0

No es posible. 'va_arg' es básicamente una lista enlazada glorificada de' void * ''s. Simplemente no hay información de tipo adicional adjunta a 'void *'. – GManNickG

+0

¿Está realmente usando C o C++? Estás hablando de C99, pero has etiquetado esto como C++ (¿quizás estás usando C++ 03)? Incluso sin plantillas variadic, podría proporcionar un conjunto de sobrecargas de plantilla de función para hacer frente a esto. Normalmente, esto sería muy tedioso ya que para reenviar los parámetros, debe sobrecargar cada parámetro para la referencia const y non-const (en C++ 03), pero en su caso, las funciones lua_push * nunca deberían necesitar una versión no const ref, por lo que solo necesita sobrecargas N (donde N es la cantidad máxima de parámetros que desea admitir). –

+0

Ah, sí lo siento Es C++ 03, mi error. Gracias, tu sugerencia parece ser la mejor (un colega mío lo hizo de esa manera, pero tenía curiosidad por saber si había una forma más genérica). – GracelessROB

Respuesta

9

Consideraría envolver su funcionalidad para llamar a funciones lua en una clase. Tiene varios beneficios que le mostraré en un segundo, pero primero aquí hay una posible idea de implementación para ello. Tenga en cuenta que no he probado este código (o incluso intenté compilarlo), fue algo que rápidamente escribí desde lo alto de mi cabeza en base a mis intentos anteriores de hacer lo mismo.

namespace detail 
{ 
    // we overload push_value instead of specializing 
    // because this way we can also push values that 
    // are implicitly convertible to one of the types 

    void push_value(lua_State *vm, lua_Integer n) 
    { 
     lua_pushinteger(vm, n); 
    } 

    void push_value(lua_State *vm, lua_Number n) 
    { 
     lua_pushnumber(vm, n); 
    } 

    void push_value(lua_State *vm, bool b) 
    { 
     lua_pushboolean(vm, b); 
    } 

    void push_value(lua_State *vm, const std::string& s) 
    { 
     lua_pushstring(vm, s.c_str()); 
    } 

    // other overloads, for stuff like userdata or C functions 

    // for extracting return values, we specialize a simple struct 
    // as overloading on return type does not work, and we only need 
    // to support a specific set of return types, as the return type 
    // of a function is always specified explicitly 

    template <typename T> 
    struct value_extractor 
    { 
    }; 

    template <> 
    struct value_extractor<lua_Integer> 
    { 
     static lua_Integer get(lua_State *vm) 
     { 
      lua_Integer val = lua_tointeger(vm, -1); 
      lua_pop(vm, 1); 
      return val; 
     } 
    }; 

    template <> 
    struct value_extractor<lua_Number> 
    { 
     static lua_Number get(lua_State *vm) 
     { 
      lua_Number val = lua_tonumber(vm, -1); 
      lua_pop(vm, 1); 
      return val; 
     } 
    }; 

    template <> 
    struct value_extractor<bool> 
    { 
     static bool get(lua_State *vm) 
     { 
      bool val = lua_toboolean(vm, -1); 
      lua_pop(vm, 1); 
      return val; 
     } 
    }; 

    template <> 
    struct value_extractor<std::string> 
    { 
     static std::string get(lua_State *vm) 
     { 
      std::string val = lua_tostring(vm, -1); 
      lua_pop(vm, 1); 
      return val; 
     } 
    }; 

    // other specializations, for stuff like userdata or C functions 
} 

// the base function wrapper class 
class lua_function_base 
{ 
public: 
    lua_function_base(lua_State *vm, const std::string& func) 
     : m_vm(vm) 
    { 
     // get the function 
     lua_getfield(m_vm, LUA_GLOBALSINDEX, func.c_str()); 
     // ensure it's a function 
     if (!lua_isfunction(m_vm, -1)) { 
      // throw an exception; you'd use your own exception class here 
      // of course, but for sake of simplicity i use runtime_error 
      lua_pop(m_vm, 1); 
      throw std::runtime_error("not a valid function"); 
     } 
     // store it in registry for later use 
     m_func = luaL_ref(m_vm, LUA_REGISTRYINDEX); 
    } 

    lua_function_base(const lua_function_base& func) 
     : m_vm(func.m_vm) 
    { 
     // copy the registry reference 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, func.m_func); 
     m_func = luaL_ref(m_vm, LUA_REGISTRYINDEX); 
    } 

    ~lua_function_base() 
    { 
     // delete the reference from registry 
     luaL_unref(m_vm, LUA_REGISTRYINDEX, m_func); 
    } 

    lua_function_base& operator=(const lua_function_base& func) 
    { 
     if (this != &func) { 
      m_vm = func.m_vm; 
      lua_rawgeti(m_vm, LUA_REGISTRYINDEX, func.m_func); 
      m_func = luaL_ref(m_vm, LUA_REGISTRYINDEX); 
     } 
     return *this; 
    } 
private: 
    // the virtual machine and the registry reference to the function 
    lua_State *m_vm; 
    int m_func; 

    // call the function, throws an exception on error 
    void call(int args, int results) 
    { 
     // call it with no return values 
     int status = lua_pcall(m_vm, args, results, 0); 
     if (status != 0) { 
      // call failed; throw an exception 
      std::string error = lua_tostring(m_vm, -1); 
      lua_pop(m_vm, 1); 
      // in reality you'd want to use your own exception class here 
      throw std::runtime_error(error.c_str()); 
     } 
    } 
}; 

// the function wrapper class 
template <typename Ret> 
class lua_function : public lua_function_base 
{ 
public: 
    lua_function(lua_State *vm, const std::string& func) 
     : lua_function_base(vm, func) 
    { 
    } 

    Ret operator()() 
    { 
     // push the function from the registry 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     // call the function on top of the stack (throws exception on error) 
     call(0); 
     // return the value 
     return detail::value_extractor<Ret>::get(m_vm); 
    } 

    template <typename T1> 
    Ret operator()(const T1& p1) 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     // push the argument and call with 1 arg 
     detail::push_value(m_vm, p1); 
     call(1); 
     return detail::value_extractor<Ret>::get(m_vm); 
    } 

    template <typename T1, typename T2> 
    Ret operator()(const T1& p1, const T2& p2) 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     // push the arguments and call with 2 args 
     detail::push_value(m_vm, p1); 
     detail::push_value(m_vm, p2); 
     call(2); 
     return detail::value_extractor<Ret>::get(m_vm); 
    } 

    template <typename T1, typename T2, typename T3> 
    Ret operator()(const T1& p1, const T2& p2, const T3& p3) 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     detail::push_value(m_vm, p1); 
     detail::push_value(m_vm, p2); 
     detail::push_value(m_vm, p3); 
     call(3); 
     return detail::value_extractor<Ret>::get(m_vm); 
    } 

    // et cetera, provide as many overloads as you need 
}; 

// we need to specialize the function for void return type 
// as the other class would fail to compile with void as return type 
template <> 
class lua_function<void> : public lua_function_base 
{ 
public: 
    lua_function(lua_State *vm, const std::string& func) 
     : lua_function_base(vm, func) 
    { 
    } 

    void operator()() 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     call(0); 
    } 

    template <typename T1> 
    void operator()(const T1& p1) 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     detail::push_value(m_vm, p1); 
     call(1); 
    } 

    template <typename T1, typename T2> 
    void operator()(const T1& p1, const T2& p2) 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     detail::push_value(m_vm, p1); 
     detail::push_value(m_vm, p2); 
     call(2); 
    } 

    template <typename T1, typename T2, typename T3> 
    void operator()(const T1& p1, const T2& p2, const T3& p3) 
    { 
     lua_rawgeti(m_vm, LUA_REGISTRYINDEX, m_func); 
     detail::push_value(m_vm, p1); 
     detail::push_value(m_vm, p2); 
     detail::push_value(m_vm, p3); 
     call(3); 
    } 

    // et cetera, provide as many overloads as you need 
}; 

La idea aquí es que en el momento de la construcción, la clase de función se encuentra la función con el nombre y almacenarlo en el registro. La razón por la que lo hago de esta manera, en lugar de simplemente almacenar el nombre de la función y obtenerlo del índice global en cada invocación, es porque de esta manera si algún otro script en un momento posterior reemplazaría el nombre global con otro valor (que podría ser algo más que una función), el objeto de función todavía se referiría a la función correcta.

De todos modos, quizás se pregunte por qué pasar por la molestia de todo esto. Este método tiene varios beneficios:

Ahora tiene un tipo autocontenido para manejar los objetos de la función lua. Puede pasarlos fácilmente en su código, sin tener que preocuparse por el lua stack o lua internals. También es más limpio y menos propenso a errores para escribir código de esta manera.

Como lua_function overloads op(), básicamente tiene un objeto de función. Esto tiene el beneficio de poder utilizarlo como una devolución de llamada para cualquier algoritmo o función que los acepte. Por ejemplo, digamos que tiene un lua_function<int> foo("foo");, y digamos que la función foo en lua toma dos argumentos, uno doble y uno de cadena. Ahora puede hacer esto:

// or std::function if C++11 
boost::function<int (double, std::string)> callback = foo; 
// when you call the callback, it calls the lua function foo() 
int result = callback(1.0, "hello world"); 

Esto es muy poderoso mecanismo, ya que ahora puede unirse a su código Lua código existente C++ sin tener que escribir ningún tipo de código de contenedor adicional.

Y como puede ver, esto también le permite obtener fácilmente el valor de retorno de la función lua. Con su idea anterior, tendría que extraer los valores manualmente de la pila después de llamar al CallFunction. Sin embargo, la desventaja obvia aquí es que esta clase solo admite un valor de retorno, pero si necesita más que eso, puede ampliar fácilmente la idea de esta clase (es decir,puede hacer que la clase tome parámetros de plantilla adicionales para múltiples tipos de devolución, o puede usar boost::any y devolver un contenedor de ellos).

+0

¡Guau! respuesta increíble, muchas gracias por ir tan en profundidad. ¡Tendremos tiempo de implementarlo pronto! ¡Aclamaciones! – GracelessROB

Cuestiones relacionadas