binding structs and ctor/dtor with tolua++
Let's say I want to bind a piece of code to Lua that looks like this:
typedef struct bar {
void * some_data;
} bar;
bar * bar_create(void);
void bar_do_something(bar * baz);
void bar_free(bar * baz);
I want to create these objects from a Lua script, and not explicitly manage their lifetime. Prefereably, I would like my script to write
require "foo"
local baz = foo:bar()
baz:do_something()
baz = nil
Problem: For that to work as expected, I need to somehow tell tolua++ about bar_create and bar_free being the constructor/destructor for bar. How do I do that? For classes, tolua++ claims to automatically use their ctor/dtor, but for structs?
The best thing I can come up with is this definition of foo.pkg:
module foo {
struct bar {
static tolua_outside bar_create @ create();
t开发者_StackOverflow中文版olua_outside bar_do_something @ do_something();
tolua_outside bar_free @ free();
};
}
which would mean I have to call create() and free() explicitly.
The bar
functions can be imported into Lua using tolua++ and wrapped to yield an object-style interface, garbage collection included.
To demonstrate the passing of arguments, I have changed the bar
interface to
bar * bar_create(int x);
int bar_do_something(bar * baz, int y);
void bar_free(bar * baz);
and written a test implementation that prints out x
, y
, etc. when the functions are called.
The bar_create()
Lua function returns a userdata value. Lua deallocates such user data by calling the __gc
method stored in the metatable of the data. Given a userdata
value and a destructor gc
, the __gc
method is overwritten such that it first calls gc
and then calls the original gc
method:
function wrap_garbage_collector(userdata, gc)
local mt = getmetatable(userdata)
local old_gc = mt.__gc
function mt.__gc (data)
gc(data)
old_gc(data)
end
end
Userdata of the same type share the same metatable; therefore the wrap_garbage_collector()
function should be called only once for each class (assuming that tolua++'s metatables are constructed once and deallocated only at exit).
At the bottom of this answer is a complete bar.pkg
file that imports the bar
functions and adds a bar
class to a Lua module named foo
. The foo
module is loaded into the interpreter (see for example my SO tolua++ example) and used like this:
bars = {}
for i = 1, 3 do
bars[i] = foo.bar(i)
end
for i = 1, 3 do
local result = bars[i]:do_something(i * i)
print("result:", result)
end
The test implementation prints out what happens:
bar(1)
bar(2)
bar(3)
bar(1)::do_something(1)
result: 1
bar(2)::do_something(4)
result: 8
bar(3)::do_something(9)
result: 27
~bar(3)
~bar(2)
~bar(1)
The construction of the bar
class below is a little elaborate: the build_class()
utility returns a class (a Lua table) given the constructor, destructor, and the class methods. Adjustments will no doubt be needed, but as a prototype demonstration the example should be OK.
$#include "bar.hpp"
// The bar class functions.
bar * bar_create(int x);
int bar_do_something(bar * baz, int y);
void bar_free(bar * baz);
$[
-- Wrapping of the garbage collector of a user data value.
function wrap_garbage_collector(userdata, gc)
local mt = getmetatable(userdata)
local old_gc = mt.__gc
function mt.__gc (data)
gc(data)
old_gc(data)
end
end
-- Construction of a class.
--
-- Arguments:
--
-- cons : constructor of the user data
-- gc : destructor of the user data
-- methods : a table of pairs { method = method_fun }
--
-- Every 'method_fun' of 'methods' is passed the user data
-- as the first argument.
--
function build_class(cons, gc, methods)
local is_wrapped = false
function class (args)
-- Call the constructor.
local value = cons(args)
-- Adjust the garbage collector of the class (once only).
if not is_wrapped then
wrap_garbage_collector(value, gc)
is_wrapped = true
end
-- Return a table with the methods added.
local t = {}
for name, method in pairs(methods) do
t[name] =
function (self, ...)
-- Pass data and arguments to the method.
return (method(value, ...))
end
end
return t
end
return class
end
-- The Lua module that contains our classes.
foo = foo or {}
-- Build and assign the classes.
foo.bar =
build_class(bar_create, bar_free,
{ do_something = bar_do_something })
-- Clear global functions that shouldn't be visible.
bar_create = nil
bar_free = nil
bar_do_something = nil
$]
精彩评论