ahmad amireh

C++ to Lua: calling a method with arbitrary args

June 25, 2011 ยท C++

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)

dark light