Extremely Confused about raw lua binding

Started by
1 comment, last by WazzatMan 14 years, 10 months ago
I have a few questions regarding low level lua binding. Specifically when it comes to exposing the functionality of my own objects. 1. According to http://www.lua.org/pil/28.1.html, creating an object in Lua comes down to manually allocating the proper size according to the members of that object: a. What about constructor parameters? Sure, I can always forgo constructor parameters. In most cases it's pretty easy, but I wanted to make sure that there's no way to include them. b. The example uses structs, but I'm using classes. I'm not familiar with C, and my code is purely C++. So I apologize if this is a stupid question, but is the constructor actually called? and does lua actually call the destructor when it cleans the object? I make liberal use of both in my code. c. I make extensive use of the standard C++ library. Vectors, strings, etc. I love 'em and can't live without them. But I assume that the size of those objects can change (And by extension the objects that contain them can change) during runtime. This sounds extremely dangerous, what happens if an object goes above the size I instruct lua to allocate? I'm guessing memory corruption. 2. This sounds like a lot of trouble to go to just to make use of Lua's garbage collection system. Using lightuserdatums seems far simpler and more appealing to me. But I'm guessing it can create problems with non-programmers writing the lua scripts, because they'd need to manually call the cleanup routines. With that in mind. What do people actually use in the professional world? 3. The majority of the examples I've seen merely give pointers on how to expose functions. All of the object-oriented examples I've seen use SWIG or Luabind--or an unsupported helper class called Luna. But I was hoping to use raw lua binding for this project. Is there an object-oriented example or open source game (In C++, not C) that makes use of raw lua binding? Yes, I already asked google :). P.S. I've looked into Luabind, and it's great, but I was hoping to only use standard code in this project. The game industry still seems incredibly skeptical of boost, and anything other than standard lua. I'm sure this will change by the time I graduate (Hopefully c++0x will be standard by then), but since I have no guarantee of that, I'd like to have at least one project using raw lua binding. This is not a big project, so I have little else to worry about except my own education in C++ programming, and program design. P.S.S. A lot of questions I know. But answering just one would be extremely helpful :).
Advertisement
I have done this. Its a good bit of work, and buys you little.

1 a & b) Constructors aren't called. You can use placement new to do this (with parameters):
int create_foo(lua_State *L){    int x = // grab x from the lua stack    // grab more parameters    void *ptr = lua_newuserdata(L, sizeof(Foo));    Foo *foo = new(ptr) Foo(x, /* more arguments ... */);    // ...}

Note that in C++ there is little difference between structs and classes.

1 c) The size of these objects doesn't change. The dynamic data is stored on the heap, so this isn't an issue.

2) Not really. It looks like a lot of work, but is very simple. You create a metatable, point the __gc field at an appropriate function, and then set the meta table of the userdata values at it. Such a function:
int gc_foo(lua_State *L){    Foo *foo = check_foo(L, 1); // magic function    foo->~Foo();    return 0;}


3)
I'll try piece together a simple example from some pieces of code I have. I can't get this to run at the minute (some DLL error I don't have time to investigate), but it compiles:

lua_helper.h
#ifndef LUA_HELPER_H#define LUA_HELPER_H#include "lua.hpp"#include <cassert>namespace lua{	class Object	{	public:		virtual ~Object() {}	};	namespace detail	{		template<class T>		void validate_type()		{			T *t = 0;			Object *o = t;			(void)o;		}		template<class T>		int garbageCollect(lua_State *L)		{			lua::Object *ptr = check_object(L,1,typeid(T).name());			ptr->~Object();			return 0;		}		Object *check_object(lua_State *L, int index, const char *key);		void *create_object(lua_State *L, const char *key, int bytes);		void registerType(lua_State *L, const char *key, const luaL_Reg *functions, lua_CFunction garbageCollect);		template<int (*function)(lua_State *L)>		int callProxy(lua_State *L)		{			try			{				return function(L);			}			catch(const std::exception &e)			{				luaL_error(L, "std::exception %s", e.what());			}			catch(...)			{				luaL_error(L, "unknown exception");			}			assert(false);			return 0;		}	}	template<class T>	void registerType(lua_State *L, const luaL_Reg *functions)	{		detail::validate_type<T>();		detail::registerType(L, typeid(T).name(), functions, &detail::garbageCollect<T>);	}	template<class T>	void *create_object(lua_State *L)	{		detail::validate_type<T>();		return detail::create_object(L, typeid(T).name(), sizeof(T));	}	template<class T>	T *check_type(lua_State *L, int index)	{		detail::validate_type<T>();		return dynamic_cast<T *>(detail::check_object(L,index,typeid(T).name()));	}	namespace detail	{			}}#define ENTRY(x) {#x, &lua::detail::callProxy<&x>}#endif

lua_helper.cpp
#include "lua_helper.h"namespace lua{	namespace detail	{		using lua::Object;		Object *check_object(lua_State *L, int index, const char *key)		{			void *ptr = luaL_checkudata(L, index, key);						if(ptr == NULL)			{				luaL_error(L,"\'%s\' expected",key);			}			return static_cast<Object *>(ptr);		}		void *create_object(lua_State *L, const char *key, int bytes)		{			void *ptr = lua_newuserdata(L,bytes);			luaL_getmetatable(L, key);			lua_setmetatable(L, -2);#if 1			check_object(L,-1,key);#endif			return ptr;		}		void registerType(lua_State *L, const char *key, const luaL_Reg *functions, lua_CFunction garbageCollect)		{			luaL_newmetatable(L,key);						lua_pushstring(L, "__index");			lua_pushvalue(L, -2);  // pushes the metatable			lua_settable(L, -3);  // metatable.__index = metatable			lua_pushstring(L, "__gc");			lua_pushcfunction(L, garbageCollect);			lua_settable(L, -3);	    			luaL_openlib(L, NULL, functions, 0);			//lua_getglobal(L, "_G");			//luaL_register(L, NULL, functions);		}	}}

example.h
#ifndef EXAMPLE_H#define EXAMPLE_H#include <string>#include <iostream>#include "lua_helper.h"class Example : public lua::Object{public:	Example(int x, const std::string &s) { std::cout << "Example(" << x << ", " << s << ")\n"; }	~Example() { std::cout << "~Example()\n"; }	void frobnicate(double d) { std::cout << "Example.frobnicate(" << d << ")\n"; }};#endif

example_lua.h
#ifndef EXAMPLE_LUA_H#define EXAMPLE_LUA_Hstruct lua_State *L;void registerExample(lua_State *L);#endif

example_lua.cpp
#include "example_lua.h"#include "example.h"#include "lua_helper.h"int create(lua_State *L){     // read arguments from stack	int x = lua_tointeger(L,1);	const char *string = lua_tostring(L, 1);	void *ptr = lua::create_object<Example>(L);	new (ptr) Example(x, string);	return 1;}int frobnicate(lua_State *L){	Example *example = lua::check_type<Example>(L,1);	double d = lua_tonumber(L, 2);	// read function arguments	example->frobnicate(d); 	// for non void functions, push the results on the stack	return 0;}luaL_Reg exampleInstanceReg[] = {	ENTRY(frobnicate),	{0,0}};luaL_Reg exampleCreateReg[] = {	ENTRY(create),	{0,0}};void registerExample(lua_State *L){	lua::registerType<Example>(L, exampleInstanceReg);	luaL_register(L, "example", exampleCreateReg);}


main.cpp
#include "lua_helper.h"const char *script = "local x = example.create(42, \'hello\')\n x:frobnicate(0.5)\n";int main(){	lua_State *L = luaL_newstate();	luaL_dostring(L, script);	lua_close(L);}

I don't have time to explain any of it, I might post a follow up later on.

This was the result of refactoring what had been even more manual work. The Object superclass exists solely for the dynamic_cast and the virtual destruction.

Doing it taught me a lot about how lua works, and also about binding in a low level manner (I have my own scripting language in progress, and parts of how it works or how it will work are based off Lua). If I were to do it again I would use something like LuaBind or Lua++. It would have saved me lots of effort, and would have been even easier to use.
Thanks rip-off! :).

Frankly, I wasn't expecting this much help. The sources actually give me a good framework to create my own prototype.

Thanks again!

This topic is closed to new replies.

Advertisement