Reputation: 185
I've been working with LuaPlus to expose the functionality of a module with a scripting language. For that, LuaPlus has been really awesome but i'm stuck with the cleanup of my exposed objects because I don't know how to handle the deletion of the lua object representing my c++ object so i can correctly free the c++ resource.
I'm using lua tables and metatables to represent cpp objects, passing the pointer to the cpp object as the lightuserdata parameter "__object" of the table, so i can do things like
function foo()
local p = MyCppObject:new() //Create a cpp object and bind it to a table+metatable
p:do_something() //Correctly calls the cpp member function do_something
....
(exits scope) //No more references to p, so p is deleted.
end
After the exit of the function (or some time later), i expected to get the call to the metatable method "__gc", where i call the delete for the internal cpp object, but i don't see my cpp callback being called at all. I tried forcing the the garbagecollection using lua's function collectgarbage, called my function a ton of times to force lua to collect my objects and i can't see my callback executing. On top of it, i see that the result of calling collectgarbage("count") decreases sometimes so something is getting deleted somewhere, but i don't know what. I've checked the lua documentation and i don't see what i'm doing wrong :(
Any comment is appreciated! Thanks!
Update: Added c++ code side + added local as Mud pointed out + sample of my test
I created this small sample of my program. The LuaShell object is simply a wrapper for the state + the loop of reading a command line and executing the string read from std::cin
#include <iostream>
#include "LuaPlus.h"
class Point
{
private:
int x_,y_;
public:
Point(): x_(0), y_(0){}
Point(int a, int b): x_(a), y_(b){}
~Point() {std::cout << "Point "<< x_ << ","
<< y_ << "being deleted" << std::endl;}
int x() const { return x_;}
int y() const { return y_;}
};
LuaPlus::LuaObject metatable;
int new_point( LuaPlus::LuaState* state)
{
LuaPlus::LuaStack args(state);
//std::cout << "Creating point!!" << std::endl;
float x = 0, y = 0;
if ( args.Count() == 3)
{
if (args[2].IsNumber() && args[3].IsNumber())
{
x = args[2].GetFloat();
y = args[3].GetFloat();
}
}
Point* p = new Point(x,y);
LuaPlus::LuaObject lua_obj = state->CreateTable();
lua_obj.SetLightUserData("__object", p);
lua_obj.SetMetaTable( metatable );
return 1;
}
int my_gc_event( LuaPlus::LuaState* state)
{
std::cout << "Calling gc_event from lua" << std::endl;
return 0;
}
int main()
{
/* Creating the object that holds the Lua interpreter as well as
* the command line
*/
LuaShell::Shell shell(true);
LuaPlus::LuaObject globals = shell.get_state()->GetGlobals();
metatable = globals.CreateTable("PointMetaTable");
metatable.SetObject("__index", metatable);
metatable.Register("new", new_point);
metatable.Register("__gc",my_gc_event);
metatable.RegisterObjectDirect("x", (Point*)0 ,&Point::x);
metatable.RegisterObjectDirect("y", (Point*)0 ,&Point::y);
globals.SetObject("Point", metatable);
//Get into the read-command-line-until-quit loop.
shell.run();
return 0;
}
In the lua side, i run this to test.
? k,b = collectgarbage("count") print (k*1024)
> 33761
? for it=1,1000000 do foo() end
? k,b = collectgarbage("count") print (k*1024)
> 75315
? collectgarbage()
? k,b = collectgarbage("count") print (k*1024)
> 32363
As you see, there is "some" garbage collecting according to the lua runtime, but when i see the top report of my process the memory only goes up, and never down. Also i never see the message from the point destructor (expected as i'm not really calling it) or the one from inside "my_gc_event" (unexpected, because i'd think it got called at some point during the collectgarbage work).
Thanks again!
Upvotes: 4
Views: 2072
Reputation: 185
Considering the correct answer (props to Mud) was buried in the comments, i thought it would be better to add the final code and answer here.
Just as Mud said, my problem was trying to use tables to expose the objects. Although this works, the lua 5.1 runtime (which is used by luaplus) does not call the garbage collector event (__gc) for tables, so I had to use the BoxPointer helper function from luaplus. Also added a map to track the types of the exposed objects as they are passed as void* and later you need to correctly cast them for a proper cleanup. Using some template trickery, I ended with a very simple system to cleanup. Here's the code (the main function remains unchanged, as well as the Point class definition).
#include <iostream>
#include <map>
#include <boost/bind.hpp>
#include <boost/function.hpp>
#include "LuaPlus.h"
#include "Shell.h"
//Function used to delete an object created and exposes to lua
template<typename type_to_delete>
void delete_wrapper(void* ptr)
{
type_to_delete* typed_ptr = static_cast<type_to_delete*>(ptr);
delete typed_ptr;
}
//My object metatable
LuaPlus::LuaObject metatable;
//Table to save function pointers for the correct deletion of exposed objects
std::map<void*,boost::function<void(void*)> > g_object_type_deleter;
int new_point( LuaPlus::LuaState* state)
{
LuaPlus::LuaStack args(state);
float x = 0, y = 0;
if ( args.Count() == 3)
{
if (args[2].IsNumber() && args[3].IsNumber())
{
x = args[2].GetFloat();
y = args[3].GetFloat();
}
}
Point* p = new Point(x,y);
//This used to be a table, but given the shortcoming with the gc, now using this version
LuaPlus::LuaObject lua_obj = state->BoxPointer(p);
lua_obj.SetMetaTable( metatable );
//Save the callback to delete the object we just created.
g_object_type_deleter[static_cast<void*> (p)] = boost::bind( delete_wrapper<Point> , _1);
return 1;
}
int my_gc_event( LuaPlus::LuaState* state)
{
//get the pointer lua is trying to delete.
void *p = state->UnBoxPointer(1);
//look for the delete callback with the type information
std::map<void*,boost::function<void(void*)> >::iterator it = g_object_type_deleter.find(p);
if (it == g_object_type_deleter.end() )
{
std::cout << "receiving callback to destroy object that was not indexed !" << std::endl;
return 0;
}
//Actually call the delete function passing the pointer as parameter
it->second(it->first);
return 0;
}
Thanks guys!
Upvotes: 2
Reputation: 28991
function foo()
p = MyCppObject:new() //Create a cpp object and bind it to a table+metatable
p:do_something() //Correctly calls the cpp member function do_something
....
(exits scope) //No more references to p, so p is deleted.
end
p
is global, so it doesn't leave scope when foo
returns. You need to use the keyword local
if you want it lexically scoped to that function.
lightuserdata parameter [...] i expected to get the call to the metatable method "__gc"
Lightuserdata are not garbage collected. That may not be related to your problem (I don't know how LuaBind works), but I thought it worth mentioning.
Response to comments:
I wrote the following test, which creates a single byte, do-nothing userdata type which has only a __gc
in it's metatable:
#include "lauxlib.h"
static int foo_gc (lua_State* L) {
puts("__gc called");
return 0;
}
static int foo_new (lua_State* L) {
lua_newuserdata(L, 1);
luaL_getmetatable(L, "foo");
lua_setmetatable(L, -2);
return 1;
}
int __declspec(dllexport) __cdecl luaopen_luagc (lua_State* L) {
// create foo type
static const struct luaL_reg foo[] = {
{ "__gc", foo_gc },
NULL, NULL
};
luaL_newmetatable(L, "foo");
luaL_openlib(L, NULL, foo, 0);
// create constructor
lua_register(L, "foo", foo_new);
return 0;
}
If I then run the following test:
require 'luagc'
for i=1,5 do
foo()
end
collectgarbage("collect")
The output is:
__gc called
__gc called
__gc called
__gc called
__gc called
Upvotes: 1