Lua and C++
March 2nd, 2006
Last night I decided to finally try out Lua and it was fun.
Specifically, I wanted to experiment with how I might expose a C++ class to a Lua script – it really wasn’t that tricky.
Here are the details…
First, let’s start with an academic nothing class that we want to make available to our script:
class Person
{
public:
Person()
:
m_age(0)
{
}
int getAge() const
{
return m_age;
}
void setAge(int age)
{
m_age = age;
}
private:
int m_age;
};
Next, the mainline:
extern "C"
{
#include "lua.h"
#include "lualib.h"
#include "lauxlib.h"
}
#include <cassert>
#include <iostream>
using namespace std;
// lua callbacks
int newPerson(lua_State* state);
int deletePerson(lua_State* state);
int getAge(lua_State* state);
int setAge(lua_State* state);
int main(int argc, char** argv)
{
const char* script = argv[1];
lua_State* state = lua_open();
// load all the standard libraries
luaL_openlibs(state);
// register functions
lua_register(state, "new_person", newPerson);
lua_register(state, "delete_person", deletePerson);
lua_register(state, "get_age", getAge);
lua_register(state, "set_age", setAge);
if (luaL_loadfile(state, script) == 0) // all is well
{
int errors = lua_pcall(state, 0, LUA_MULTRET, 0);
if (errors)
{
cerr << lua_tostring(state, -1) << endl;
}
}
lua_close(state);
return 0;
}
As is fairly obvious, the name of the Lua script is meant to be passed in on the command-line. Notice the stubs? The intention is to provide interface to Lua to allow for the dynamic allocation and deallocation of a Person as well as to get/set the Person's age.
Now to fill out those stubs...
int newPerson(lua_State* state)
{
assert(lua_gettop(state) == 0);
lua_pushlightuserdata(state, new Person);
return 1;
}
int deletePerson(lua_State* state)
{
assert(lua_gettop(state) == 1);
Person* person =
static_cast<Person*>(lua_touserdata(state, 1));
delete person;
return 0;
}
int getAge(lua_State* state)
{
assert(lua_gettop(state) == 1);
Person* person =
static_cast<Person*>(lua_touserdata(state, 1));
lua_pushnumber(state, person->getAge());
return 1;
}
int setAge(lua_State* state)
{
assert(lua_gettop(state) == 2);
Person* person =
static_cast<Person*>(lua_touserdata(state, 1));
int age = lua_tonumber(state, 2);
person->setAge(age);
return 0;
}
The assertion on the first line of each function is on the number of arguments passed in. Not enormously useful, but I'm guessing it'll end up advantageous in the long-run when I'm tearing out my hair screaming at my computer.
Something a bit unfortunate with these callbacks is that if the parameter passed in (from the script) is not a Person, you're only indication will be a seg-fault. To achieve this in a relatively type-safe way, you would need a base-class common to all user data that is exposed to Lua. It would then be possible to assert lua_isuserdata, static_cast to the base-class and then dynamic_cast to the expected type. I haven't actually tried this, though.
So all that's left at this point is the script!
michael = new_person()
marcus = new_person()
io.write("michael is ", get_age(michael), " years old\n")
io.write("marcus is ", get_age(marcus), " years old\n")
set_age(michael, 37)
set_age(marcus, 36)
io.write("michael is ", get_age(michael), " years old\n")
io.write("marcus is ", get_age(marcus), " years old\n")
delete_person(michael)
delete_person(marcus)
And there you have it!
I'm confident that some of my visitors are quite Lua-savy and as such would have some much better suggestions on how this can be done. I'm reasonably comfortable with the mechanics of it all, but I haven't a clue as to best-practices or anything like that - particularly when it comes to slotting it in to an existing C++ code base, like say, an adventure game engine
Any advice would be appreciated.