Code for Concinnity

beautiful and elegant solutions


Binding C++ classes to Lua: a step by step example for beginners

Well, I lied. This isn’t step by step but I believe this commented source code dump should be more illustrative than any tutorial I’ve read, here we go :)

// fun.cpp

extern "C"
{
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
}

#include <iostream>
#include <sstream>

class Foo
{
public:
    Foo(const std::string & name) : name(name)
    {
        std::cout << "Foo is born" << std::endl;
    }

    std::string Add(int a, int b)
    {
        std::stringstream ss;
        ss << name << ": " << a << " + " << b << " = " << (a+b);
        return ss.str();
    }

    ~Foo()
    {
        std::cout << "Foo is gone" << std::endl;
    }

private:
    std::string name;
};

// The general pattern to binding C++ class to Lua is to write a Lua
// thunk for every method for the class, so here we go:

int l_Foo_constructor(lua_State * l)
{
    const char * name = luaL_checkstring(l, 1);

    // We could actually allocate Foo itself as a user data but
    // since user data can be GC'ed and we gain unity by using CRT's heap
    // all along.
    Foo ** udata = (Foo **)lua_newuserdata(l, sizeof(Foo *));
    *udata = new Foo(name);

    // Usually, we'll just use "Foo" as the second parameter, but I
    // say luaL_Foo here to distinguish the difference:
    //
    // This 2nd parameter here is an _internal label_ for luaL, it is
    // _not_ exposed to Lua by default.
    //
    // Effectively, this metatable is not accessible by Lua by default.
    luaL_getmetatable(l, "luaL_Foo");

    // The Lua stack at this point looks like this:
    //
    // 3| metatable "luaL_foo" |-1
    // 2| userdata |-2
    // 1| string parameter |-3
    //
    // So the following line sets the metatable for the user data to the luaL_Foo
    // metatable
    //
    // We must set the metatable here because Lua prohibits setting
    // the metatable of a userdata in Lua. The only way to set a metatable
    // of a userdata is to do it in C.
    lua_setmetatable(l, -2);

    // The Lua stack at this point looks like this:
    //
    // 2| userdata |-1
    // 1| string parameter |-2
    //
    // We return 1 so Lua callsite will get the user data and
    // Lua will clean the stack after that.

    return 1;
}

Foo * l_CheckFoo(lua_State * l, int n)
{
    // This checks that the argument is a userdata
    // with the metatable "luaL_Foo"
    return *(Foo **)luaL_checkudata(l, n, "luaL_Foo");
}

int l_Foo_add(lua_State * l)
{
    Foo * foo = l_CheckFoo(l, 1);
    int a = luaL_checknumber(l, 2);
    int b = luaL_checknumber(l, 3);

    std::string s = foo->Add(a, b);
    lua_pushstring(l, s.c_str());

    // The Lua stack at this point looks like this:
    //
    // 4| result string |-1
    // 3| metatable "luaL_foo" |-2
    // 2| userdata |-3
    // 1| string parameter |-4
    //
    // Return 1 to return the result string to Lua callsite.

    return 1;
}

int l_Foo_destructor(lua_State * l)
{
    Foo * foo = l_CheckFoo(l, 1);
    delete foo;

    return 0;
}

void RegisterFoo(lua_State * l)
{
    luaL_Reg sFooRegs[] =
    {
        { "new", l_Foo_constructor },
        { "add", l_Foo_add },
        { "__gc", l_Foo_destructor },
        { NULL, NULL }
    };

    // Create a luaL metatable. This metatable is not
    // exposed to Lua. The "luaL_Foo" label is used by luaL
    // internally to identity things.
    luaL_newmetatable(l, "luaL_Foo");

    // Register the C functions _into_ the metatable we just created.
    luaL_register(l, NULL, sFooRegs);

    // The Lua stack at this point looks like this:
    //
    // 1| metatable "luaL_Foo" |-1
    lua_pushvalue(l, -1);

    // The Lua stack at this point looks like this:
    //
    // 2| metatable "luaL_Foo" |-1
    // 1| metatable "luaL_Foo" |-2

    // Set the "__index" field of the metatable to point to itself
    // This pops the stack
    lua_setfield(l, -1, "__index");

    // The Lua stack at this point looks like this:
    //
    // 1| metatable "luaL_Foo" |-1

    // The luaL_Foo metatable now has the following fields
    // - __gc
    // - __index
    // - add
    // - new

    // Now we use setglobal to officially expose the luaL_Foo metatable
    // to Lua. And we use the name "Foo".
    //
    // This allows Lua scripts to _override_ the metatable of Foo.
    // For high security code this may not be called for but
    // we'll do this to get greater flexibility.
    lua_setglobal(l, "Foo");
}

int main()
{
    lua_State * l = luaL_newstate();
    luaL_openlibs(l);
    RegisterFoo(l);

    int erred = luaL_dofile(l, "fun.lua");
    if(erred)
        std::cout << "Lua error: " << luaL_checkstring(l, -1) << std::endl;

    lua_close(l);

    return 0;
}

view raw fun.cpp This Gist brought to you by GitHub.
-- fun.lua

-- Because the metatable has been exposed
-- to us, we can actually add new functions
-- to Foo
function Foo:speak()
    print("Hello, I am a Foo")
end

local foo = Foo.new("fred")
local m = foo:add(3, 4)

-- "fred: 3 + 4 = 7"
print(m)

-- "Hello, I am a Foo"
foo:speak()

-- Let's rig the original metatable
Foo.add_ = Foo.add
function Foo:add(a, b)
    return "here comes the magic: " .. self:add_(a, b)
end

m = foo:add(9, 8)

-- "here comes the magic: fred: 9 + 8 = 17"
print(m)

view raw fun.lua This Gist brought to you by GitHub.

Published by kizzx2, on January 11th, 2012 at 10:32 pm. Filled under: UncategorizedNo Comments

No comments yet.

Leave a Reply

 

Spam protection by WP Captcha-Free