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

Premake whopping rocks! (or how Boost Build whopping sucks, or how to build Luabind for iOS proper)

Last time I wrote about how to build Luabind on OS X. Well, it turned out that was only half the battle won. Building Luabind for iOS (ARMV7) architecture just plain doesn’t work. It seems like some people have managed to make it work but none of what I have found actually worked. If you are reading this, I suppose you are also tired of jumping through hoops just to compile the damn thing, well, Premake to the rescue.

Let me tell you, this is how building stuff in C++ is meant to work:

Just create a file premake4.lua

1
2
3
4
5
6
7
8
9
10
-- premake4.lua

solution "luabind"
    configurations { "Release" }
    project "luabind"
        language "C++"
        kind "StaticLib"
        flags { "Optimize" }
        includedirs { ".", "/usr/local/include", "/where/is/your/lua" }
        files { "src/**" }

That is ALL THERE IS TO IT!

1
2
3
$ premake4 xcode3
$ xcodebuild -sdk iphonesimulator -configuration Release -arch i386
$ xcodebuild -sdk iphoneos -configuration Release -arch armv7

Boost Build whopping sucks. Premake is written by one guy and this turns out to be so trivial (as it should be).

Hope it helps.

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

Building luabind on Mac OS X

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
$ # If you have used the Homebrew recipes -- they don't work
$ brew uninstall lua
$ brew uninstall bjam

$ # The current version of LuaBind doesn't compile with Clang.
$ Fortunately the GitHub HEAD does.
$ git clone https://github.com/luabind/luabind.git

$ # LuaBind currently does not compile with Lua 5.2
$ wget $BOOST_URL
$ wget $LUA51_URL
$ wget $LUA_URL

$ cd /where/is/boost
$ chmod a+x tools/build/v2/bootstrap.sh
$ chmod a+x tools/build/v2/engine/build.sh
$ ./bootstrap.sh toolset=darwin
$ cp bjam /usr/local/bin

$ export BOOST_ROOT=/where/is/your/boost
$ export BOOST_BUILD_PATH=/where/is/your/boost/tools/build/v2

$ cd /where/is/lua
$ make macosx

$ # We need these because LuaBind hard-codes the non-standard directory names
$ ln -s src include


$ export LUA_PATH=/where/is/lua
$ cd /where/is/luabind
$ bjam toolset=darwin link=static
Published by kizzx2, on January 9th, 2012 at 11:51 pm. Filled under: UncategorizedNo Comments