Created
January 11, 2012 14:29
-
-
Save kizzx2/1594905 to your computer and use it in GitHub Desktop.
Illustrative C++ Lua binding example/tutorial
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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; | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
-- 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) |
Why not use placement-new to directly create the object in the userdata memory?
@farleylai .. userdata is just pointer to pointer, so the memory is not allocated yet, lua just returns a location of memory where you will store the pointer to user data.
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
With lua 5.x the code does not work properly, the call at line 136 should be modified from:
luaL_register(l, NULL, sFooRegs);
toluaL_setfuncs(l, sFooRegs, 0);
as stated in this page.