C++ to Lua: calling a method with arbitrary args
Let's say you need to call different functions in your Lua script from your C code that have different signatures, and they take user-defined datatypes. Using tolua++ and some varargs magic, this can be done.
On the C side, we need to create a function that takes a Lua function name, an arbitrary number of arguments and the argument count.
bool pass_to_lua(const char* in_func, int argc, ...) {
va_list argp;
va_start(argp, argc);
lua_getfield(lua_, LUA_GLOBALSINDEX, "arbitrary");
if(!lua_isfunction(lua_, 1))
{
// unable to find our proxy function, this happens only when the Lua state is corrupt
throw std::runtime_error(lua_tostring(lua_, lua_gettop(lua_)));
}
lua_pushfstring(lua_, in_func);
for (int i=0; i < argc; ++i) {
const char* argtype = (const char*)va_arg(argp, const char*);
void* argv = (void*)va_arg(argp, void*);
push_userdata(argv,argtype);
}
int ec = lua_pcall(lua_, argc+1, 1, 0);
if (ec != 0)
{
// there was a lua error, propagate or handle as you wish
throw std::runtime_error(lua_tostring(lua_, lua_gettop(lua_)));
}
bool result = lua_toboolean(lua_, lua_gettop(lua_));
lua_remove(lua_, lua_gettop(lua_));
va_end(argp);
return result;
}
The function must be called as follows (in this example we're passing two arguments, the first of type ABC::Foo, and the other is ABC::Bar to a function called SayFoobar inside a table called Pixy)
passToLua("Pixy.SayFoobar", 2, "ABC::Foo", (void*)&mFoo, "ABC::Bar", (void*)&mBar);
On Lua's side, we need to have a function called arbitrary (or whatever you choose) that basically calls the function you requested with the arguments:
-- helper method: iterates over a table
function list_iter(t)
local i = 0
local n = table.getn(t)
return function ()
i = i + 1
if i <= n then return t[i] else return nil end
end
end
-- helper method: explodes a string into a set of words delimited by ' '
function all_words(str)
local t = {}
local b = 0
local e = 0
b,e = str:find("%w+", e+1)
while b do
table.insert(t, str:sub(b,e))
b,e = str:find("%w+", e+1)
end
return t
end
-- locates the method identified by name and calls it with the arguments passed
function arbitrary(name, ...)
local _p = _G
for word in list_iter(all_words(name)) do
_p = _p[word]
if not _p then
return error("attempting to call an invalid arbitrary method: " .. name)
end
end
return _p(unpack(arg))
end
And in our SayFoobar Lua function:
Pixy.SayFoobar = function(foo, bar) foo:sayHi() bar:sayBye() end
Of course this code expects that Foo and Bar types are defined manually in a metatable somewhere or by your favorite binder (such as tolua++, Luabind, SWIG, Lunar etc)