2007-01-31 Simple C++ Lua Binding

I encourage you to participate in Lua communities:

There is and example how to simple bind Lua with C++ : http://lua-users.org/wiki/SimpleCppBinding . ( Here is a "mirror" of that webpage )
Unforunatelly it does not work for Lua 5.1.1.
There are some improvements needed.

(But there is said how to do this with Lua 5.0 , so my article may be just an alternative )

So here we go…

First of all, to compile this stuff we use command

g++ account.cpp /usr/local/lib/liblua.a

Now compiler raises errors:

account.cpp: In static member function ‘static int LuaAccount::create_account(lua_State*)’:
account.cpp:31: error: ‘lua_boxpointer’ undeclared (first use this function)
account.cpp:31: error: (Each undeclared identifier is reported only once for each function it appears in.)
account.cpp: In static member function ‘static int LuaAccount::gc_account(lua_State*)’:
account.cpp:55: error: ‘lua_unboxpointer’ undeclared (first use this function)
account.cpp: In function ‘int main(int, char**)’:
account.cpp:110: error: ‘lua_dofile’ undeclared (first use this function)

Complains about lua_boxpointer and lua_unboxpointer ?
No problem:

#define lua_boxpointer(L,u) (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))
#define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i)))

‘lua_dofile’ undeclared ?
Of course … coause it's auxiliary library and have new prefix "luaL_"
Now it looks like that:

There is and example how to simple bind Lua with C++ : http://lua-users.org/wiki/SimpleCppBinding .
Unforunatelly it does not work for Lua 5.1.1.
There are some improvements needed.

So here we go…

First of all, to compile this stuff we use command

g++ account.cpp /usr/local/lib/liblua.a

Now compiler raises errors:

account.cpp: In static member function ‘static int LuaAccount::create_account(lua_State*)’:
account.cpp:31: error: ‘lua_boxpointer’ undeclared (first use this function)
account.cpp:31: error: (Each undeclared identifier is reported only once for each function it appears in.)
account.cpp: In static member function ‘static int LuaAccount::gc_account(lua_State*)’:
account.cpp:55: error: ‘lua_unboxpointer’ undeclared (first use this function)
account.cpp: In function ‘int main(int, char**)’:
account.cpp:110: error: ‘lua_dofile’ undeclared (first use this function)

Complains about lua_boxpointer and lua_unboxpointer ?
No problem:

#define lua_boxpointer(L,u) (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))
#define lua_unboxpointer(L,i) (*(void **)(lua_touserdata(L, i)))

‘lua_dofile’ undeclared ?
Of course … coause it's auxiliary library and have new prefix "luaL_"
Now it looks like that:

if(argc>1) luaL_dofile(L, argv[1]);

Hurray !
Now file compled without any problem (if you have any you can try -llua -llualib -ldl )

So let's try to run lua helloworld script:

print "Hello world\n"

by following command:

./a.out lua_helloworld.lua

O ! No !
Lua's Virtual Machine PANIC :O

PANIC: unprotected error in call to Lua API (no calling environment)

Let's look into Reference Manual…
http://www.lua.org/manual/5.1/manual.html#5
And now it's clear.

Beginning with version 5.1 we have to call luaopen_ functions throught lua_call.
Auxiliary library provides {{luaL_openlibs}} function.
We will use it for initialisation.
It implies new linkage problem:

/usr/local/lib/liblua.a(loadlib.o)(.text+0xf5): In function ‘ll_loadfunc’:
loadlib.c: undefined reference to ‘dlopen’
/usr/local/lib/liblua.a(loadlib.o)(.text+0x11a):loadlib.c: undefined reference to ‘dlsym’
/usr/local/lib/liblua.a(loadlib.o)(.text+0x1d1):loadlib.c: undefined reference to ‘dlerror’
/usr/local/lib/liblua.a(loadlib.o)(.text+0x1ff):loadlib.c: undefined reference to ‘dlerror’
/usr/local/lib/liblua.a(loadlib.o)(.text+0xe47): In function ‘gctm’:
loadlib.c: undefined reference to ‘dlclose’
collect2: ld returned 1 exit status

Witch are easy to solve… just add -ldl compiler option.

g++ account.cpp /usr/local/lib/liblua.a -ldl

Now file compled without any problem (if you have any you can try -llua -llualib -ldl ).

This two steps solved PANIC problem.

So code from http://lua-users.org/wiki/SimpleCppBinding now looks like that after modifications:
file account.cpp :

extern "C" {
#include "lua.h"
#include "lauxlib.h"
#include "lualib.h"
}
 
#define lua_boxpointer(L,u)  (*(void **)(lua_newuserdata(L, sizeof(void *))) = (u))
#define lua_unboxpointer(L,i)   (*(void **)(lua_touserdata(L, i)))
 
class Account {
    public:
        Account(double balance)      { m_balance = balance; }
        void deposit(double amount)  { m_balance += amount; }
        void withdraw(double amount) { m_balance -= amount; }
        double balance(void)         { return m_balance; }
    private:
        double m_balance;
};
 
class LuaAccount {
    static const char className[];
    static const luaL_reg methods[];
 
    static Account *checkaccount(lua_State *L, int narg) {
        luaL_checktype(L, narg, LUA_TUSERDATA);
        void *ud = luaL_checkudata(L, narg, className);
        if(!ud) luaL_typerror(L, narg, className);
        return *(Account**)ud;  // unbox pointer
    }
 
    static int create_account(lua_State *L) {
        double balance = luaL_checknumber(L, 1);
        Account *a = new Account(balance);
        lua_boxpointer(L, a);
        luaL_getmetatable(L, className);
        lua_setmetatable(L, -2);
        return 1;
    }
    static int deposit(lua_State *L) {
        Account *a = checkaccount(L, 1);
        double amount = luaL_checknumber(L, 2);
        a->deposit(amount);
        return 0;
    }
    static int withdraw(lua_State *L) {
        Account *a = checkaccount(L, 1);
        double amount = luaL_checknumber(L, 2);
        a->withdraw(amount);
        return 0;
    }
    static int balance(lua_State *L) {
        Account *a = checkaccount(L, 1);
        double balance = a->balance();
        lua_pushnumber(L, balance);
        return 1;
    }
    static int gc_account(lua_State *L) {
        Account *a = (Account*)lua_unboxpointer(L, 1);
        delete a;
        return 0;
    }
 
    public:
    static void Register(lua_State* L) {
        lua_newtable(L);                 int methodtable = lua_gettop(L);
        luaL_newmetatable(L, className); int metatable   = lua_gettop(L);
 
        lua_pushliteral(L, "__metatable");
        lua_pushvalue(L, methodtable);
        lua_settable(L, metatable);  // hide metatable from Lua getmetatable()
 
        lua_pushliteral(L, "__index");
        lua_pushvalue(L, methodtable);
        lua_settable(L, metatable);
 
        lua_pushliteral(L, "__gc");
        lua_pushcfunction(L, gc_account);
        lua_settable(L, metatable);
 
        lua_pop(L, 1);  // drop metatable
 
        luaL_openlib(L, 0, methods, 0);  // fill methodtable
        lua_pop(L, 1);  // drop methodtable
 
        lua_register(L, className, create_account);
    }
};
 
const char LuaAccount::className[] = "Account";
 
#define method(class, name) {#name, class::name}
 
const luaL_reg LuaAccount::methods[] = {
    method(LuaAccount, deposit),
    method(LuaAccount, withdraw),
    method(LuaAccount, balance),
    {0,0}
};
 
int main(int argc, char* argv[])
{
    lua_State *L = lua_open();
 
    luaL_openlibs(L);
 
    LuaAccount::Register(L);
 
    if(argc>1) luaL_dofile(L, argv[1]);
 
    lua_close(L);
    return 0;
}

Test file lua_helloworld.lua :

print "Hello world.\n"

Compilation:
g++ account.cpp /usr/local/lib/liblua.a -ldl -o account.bin
And running:
./account.bin lua_helloworld.lua

Now another example - lua_account.lua

local a = Account(100)
printf("Account balance = $%0.02f\n", a:balance() )
a:deposit(50.30)
printf("Account balance = $%0.02f\n", a:balance() )
a:withdraw(25.10)
printf("Account balance = $%0.02f\n", a:balance() )

Command : ./account.bin lua_account.lua provides such output:

Account balance = $100.00
Account balance = $150.30
Account balance = $125.20

O ile nie zaznaczono inaczej, treść tej strony objęta jest licencją Creative Commons Attribution-ShareAlike 3.0 License