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).
Non è possibile. 'va_arg' è fondamentalmente una lista concatenata glorificata di' void * 's. Semplicemente non vi sono ulteriori informazioni sul tipo allegate a "void *". – GManNickG
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). –
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