2011-10-25 1 views
5

Ok, quindi questa potrebbe non essere la migliore decisione di progettazione, e non voglio veramente usare qualcosa come LuaBind ... Ero curioso di sapere se il seguente è possibile in C++ 03 (C++ 11 rende possibile con i modelli variadic). Inoltre, sono sicuro che questo è stato chiesto prima, ma non ho potuto trovare una risposta diretta!Funzione call lua C++ con parametri variabili

dire che ho un metodo di supporto per richiamare le funzioni Lua dal codice:

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

che potenzialmente possono accettare N numero di argomenti (utilizzando va_arg o qualsiasi altro metodo di molteplici args)

Come posso , se è anche possibile, calcola il tipo di ogni parametro e passa al lua_push {type}() appropriato; funzione prima di chiamare la funzione lua desiderata?

Non sono sicuro se questo può essere fatto con var_arg, perché devi conoscere il tipo quando prendi il parametro, ho provato ad afferrarlo con void * e passarlo a un modello specializzato, ma cerca di passalo al modello.

Speriamo che qualcuno di gran lunga migliore in C++ avrà un trucco o due! Grazie mille

+0

Non è possibile. 'va_arg' è fondamentalmente una lista concatenata glorificata di' void * 's. Semplicemente non vi sono ulteriori informazioni sul tipo allegate a "void *". – GManNickG

+0

Stai effettivamente utilizzando C o C++. Stai parlando di C99, ma hai codificato questo come C++ (forse stai usando C++ 03)? Anche senza modelli variadici, è possibile fornire una serie di overload di modelli di funzioni per far fronte a questo. Normalmente questo sarebbe molto noioso dato che per inoltrare i parametri devi sovraccaricare ogni parametro per riferimento const e non-const (in C++ 03), ma nel tuo caso, le funzioni lua_push * non dovrebbero mai avere bisogno di una versione ref non-const, quindi hai solo bisogno di N overload (dove N è il numero massimo di parametri che vuoi supportare). –

+0

Ah, scusa È C++ 03, mio ​​errore. Grazie, il tuo suggerimento sembra essere il modo migliore (un mio collega l'ha fatto in quel modo ma ero curioso di sapere se c'era un modo più generico). – GracelessROB

risposta

9

Prendere in considerazione la possibilità di includere le funzionalità per chiamare le funzioni lua in una classe. Ha diversi vantaggi che ti mostrerò tra un secondo, ma prima ecco una possibile idea di implementazione per questo. Tieni presente che non ho testato questo codice (o anche provato a compilarlo), è stato solo qualcosa che ho rapidamente scritto dalla mia testa in base ai miei precedenti tentativi di fare la stessa cosa.

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

L'idea è che, al momento della costruzione, la classe funzioni troverà la funzione con il nome e memorizzarlo nel Registro di sistema. Il motivo per cui lo faccio in questo modo, invece di memorizzare semplicemente il nome della funzione e ottenerlo dall'indice globale su ogni invocazione, è perché in questo modo se qualche altro script in un secondo momento sostituirà il nome globale con un altro valore (che potrebbe essere qualcosa di diverso da una funzione), l'oggetto funzione si riferirebbe comunque alla funzione corretta.

In ogni modo si potrebbe chiedere perché passare attraverso la briga di tutto questo. Questo metodo presenta vari vantaggi:

Ora si dispone di un tipo autonomo per la gestione degli oggetti funzione lua. Puoi passarli facilmente nel tuo codice, senza doversi preoccupare del lua stack o dei lua internals. È anche più pulito e meno soggetto a errori di scrittura del codice in questo modo.

Poiché overload di lua_function op(), in pratica si dispone di un oggetto funzione. Questo ha il vantaggio di poterlo usare come callback per qualsiasi algoritmo o funzione che li accetti. Ad esempio, supponiamo di avere un lua_function<int> foo("foo"); e diciamo che la funzione foo in lua richiede due argomenti, una doppia e una stringa. È ora possibile fare questo:

// 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"); 

Questo è meccanismo molto potente, in quanto è ora possibile vincolare il proprio codice di lua al codice esistente C++ senza dover scrivere qualsiasi tipo di codice wrapper supplementare.

E come potete vedere, questo consente anche di ottenere facilmente il valore di ritorno dalla funzione lua. Con la tua idea precedente dovresti estrarre i valori manualmente dallo stack dopo aver chiamato CallFunction. L'ovvio inconveniente qui è però che solo un valore di ritorno è supportato da questa classe, ma se hai bisogno di più, puoi facilmente espandere l'idea di questa classe (ad es.potresti fare in modo che la classe assuma parametri di modello aggiuntivi per più tipi di reso, oppure potresti usare e restituirne un contenitore).

+0

Wow! risposta sorprendente, grazie mille per l'approfondimento. Proverò a implementarlo presto! Saluti! – GracelessROB