Big Bucket Software you like to use
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.