Continuing the post about
lua integration with C++. Now to more serious stuff. Let’s try to write a wrapper for a std::list<int>
. Imagine that you have a std::list<int>
in your C++ that you want to share with the Lua enviroment. So both C++ and Lua can access the list.
Keep in mind, that I’m going to use a std::list<int>
as an example but you could apply the same idea to any other type: user-defined or builtin.
Basics
First, let’s get the Makefile
in place
LUAHOME=$(HOME)/tmp/lua-5.2.1/src
all: sampleluahost
sampleluahost: sampleluahost.cpp
g++ -g sampleluahost.cpp -llua -L$(LUAHOME) -I$(LUAHOME) -o sampleluahost
Lua script
function entries(arg) -- iterator
return function()
return arg:pop();
end
end
for i in entries(the_list) do
io.write("From LUA: ", i, "\n")
end
for i=1,10 do
the_list:push(50+i*100);
end
This script when executed will empty the_list
printing its contents and will
fill it again with new content. That will illuestrate that Lua can access the
underlyint std::list<int>
backing up the_list
.
Note that we use the colon notation to call methods on the_list
. So when I
write arg:pop()
it’s tranlated to arg.pop(arg)
. The first argument to the
function will be the object itself (Think on that argument like the implicit
*this
in C++ methods or self
argument in python).
Note: that I wrote an iterator for the list in lua. That is a function that
returns a closure. the for
will call this returned function over and over
until it returns nil
.
The arg:pop()
will pop a element from the front of the std::list<int>
and
will return nil
when the list is empty.
The C++ host application
#include <lua.hpp>
#include <iostream>
#include <list>
#include <assert.h>
extern "C" {
static int l_list_push(lua_State *L) { // Push elements from LUA
assert(lua_gettop(L) == 2); // check that the number of args is exactly 2
std::list<int> **ud = static_cast<std::list<int> **>(luaL_checkudata(L,1, "ListMT")); // first arg is the list
int v =luaL_checkint(L,2); // seconds argument is the integer to be pushed to the std::list<int>
(*ud)->push_back(v); // perform the push on C++ object through the pointer stored in user data
return 0; // we return 0 values in the lua stack
}
static int l_list_pop(lua_State *L) {
assert(lua_gettop(L) == 1); // check that the number of args is exactly 1
std::list<int> **ud = static_cast<std::list<int> **>(luaL_checkudata(L, 1, "ListMT")); // first arg is the userdata
if ((*ud)->empty()) {
lua_pushnil(L);
return 1; // if list is empty the function will return nil
}
lua_pushnumber(L,(*ud)->front()); // push the value to pop in the lua stack
// it will be the return value of the function in lua
(*ud)->pop_front(); // remove the value from the list
return 1; //we return 1 value in the stack
}
}
class Main
{
public:
Main();
~Main();
void run();
/* data */
private:
lua_State *L;
std::list<int> theList;
void registerListType();
void runScript();
};
Main::Main() {
L = luaL_newstate();
luaL_openlibs(L);
}
Main::~Main() {
lua_close(L);
}
void Main::runScript() {
lua_settop(L,0); //empty the lua stack
if(luaL_dofile(L, "./samplescript.lua")) {
fprintf(stderr, "error: %s\n", lua_tostring(L,-1));
lua_pop(L,1);
exit(1);
}
assert(lua_gettop(L) = 0); //empty the lua stack
}
void Main::registerListType() {
std::cout << "Set the list object in lua" << std::endl;
luaL_newmetatable(L, "ListMT");
lua_pushvalue(L,-1);
lua_setfield(L,-2, "__index"); // ListMT .__index = ListMT
lua_pushcfunction(L, l_list_push);
lua_setfield(L,-2, "push"); // push in lua will call l_list_push in C++
lua_pushcfunction(L, l_list_pop);
lua_setfield(L,-2, "pop"); // pop in lua will call l_list_pop in C++
}
void Main::run() {
for(unsigned int i = 0; i<10; i++) // add some input data to the list
theList.push_back(i*100);
registerListType();
std::cout << "creating an instance of std::list in lua" << std::endl;
std::list<int> **ud = static_cast<std::list<int> **>(lua_newuserdata(L, sizeof(std::list<int> *)));
*(ud) = &theList;
luaL_setmetatable(L, "ListMT"); // set userdata metatable
lua_setglobal(L, "the_list"); // the_list in lua points to the new userdata
runScript();
while(!theList.empty()) { // read the data that lua left in the list
std::cout << "from C++: pop value " << theList.front() << std::endl;
theList.pop_front();
}
}
int main(int argc, char const *argv[])
{
Main m;
m.run();
return 0;
}
The idea is to set up the basic lua enviroment thought the luaL_newstate and luaL_openlibs.
Then we create a metatable with
luaL_newmetatable.
A metatable is just a regular table that can be associated with lua values such
as userdata. The metatable is where Lua goes to search for metamethods. You can
see the list of available metamethods in
Lua
Reference. In this case we
define the metamethod __index
, which is the metamethod used by Lua when in
cannot find a given index in a table or userdata. So imagine that a
is a
userdata and we type a.elem
. a
has no elem
in it so it invokes __index
on a’s metatable to see what to do. It’s kind of method_missing
in Ruby if
you are familiar with Ruby. Now the __index
metamethod it’s a little bit
special in the sense that I doesn’t need to be a method/function at all. If Lua
finds out that the __index
field of the metatable is actually a table and not
a function it will just use that table to find the key. So going back to
a.elem
example, that will be translated to getmetatable(a)["__index"].elem
.
We will use the "ListMT"
metatable to hold the methods for lists. We
associate the metatable entries for push
and pop
with two static C
functions l_list_push
and l_list_pop
. This functions must be of type
lua_CFunction
. that is they should take a lua_State *
as paramter and
return an integer. That’s how the Lua communicates with C++, via the
lua_State
and its stack.
The functions themselves are quite straighforward. They must be defined as
extern "C"
because Lua is compiled as a C library and it will call all the
lua_CFunction
with a C linkage (that determines the order in which the
function parameters will be pushed into the machine’s stack, etc.) so we need
to make sure that the function that we are generating here can be called from
C.
The function are designed so that the first parameters is always the “object” in this case a userdata of type “ListMT”. The lua function luaL_checkudata will check that the metatable of the userdata matches and it will provide the pointer to the userdata. When Lua call the funciton the arguments to the functions are always pushed into the stack so that the first parameter lands on the stack position 1. So arguments are easy to address.
Finally the Lua resources are freed with lua_close.
Output
Set the list object in lua
creating an instance of std::list in lua
From LUA: 0
From LUA: 100
From LUA: 200
From LUA: 300
From LUA: 400
From LUA: 500
From LUA: 600
From LUA: 700
From LUA: 800
From LUA: 900
from C++: pop value 150
from C++: pop value 250
from C++: pop value 350
from C++: pop value 450
from C++: pop value 550
from C++: pop value 650
from C++: pop value 750
from C++: pop value 850
from C++: pop value 950
from C++: pop value 1050
Things to remember
- Understand userdata, metatables and metamethods
- Lua and C++ communicate though the Lua stack
- check the number of arguments with `assert(lua_gettop(L) == x);
- empty the lua stack or assert that it’s empty where do you know that the stack should be empty