Связывание структур и ctor/dtor с tolua++
Допустим, я хочу связать фрагмент кода с Lua, который выглядит следующим образом:
typedef struct bar {
void * some_data;
} bar;
bar * bar_create(void);
void bar_do_something(bar * baz);
void bar_free(bar * baz);
Я хочу создать эти объекты из сценария Lua, а не явно управлять их временем жизни. Желательно, чтобы мой сценарий был написан.
require "foo"
local baz = foo:bar()
baz:do_something()
baz = nil
Проблема: для того, чтобы это работало как положено, мне нужно как-то сказать tolua++ о bar_create и bar_free, являющемся конструктором / деструктором для bar. Как я могу это сделать? Для классов tolua++ утверждает, что автоматически использует их ctor / dtor, но для структур?
Лучшее, что я могу придумать, это определение foo.pkg:
module foo {
struct bar {
static tolua_outside bar_create @ create();
tolua_outside bar_do_something @ do_something();
tolua_outside bar_free @ free();
};
}
что означало бы, что я должен вызывать create() и free() явно.
1 ответ
bar
функции могут быть импортированы в Lua с помощью tolua++ и упакованы для создания интерфейса в стиле объекта, включая сборку мусора.
Чтобы продемонстрировать передачу аргументов, я изменил bar
интерфейс к
bar * bar_create(int x);
int bar_do_something(bar * baz, int y);
void bar_free(bar * baz);
и написал тестовую реализацию, которая распечатывает x
, y
и т. д. при вызове функций.
bar_create()
Функция Lua возвращает значение пользовательских данных. Lua освобождает такие пользовательские данные, вызывая __gc
метод хранится в метатаблице данных. Учитывая userdata
ценность и деструктор gc
, __gc
метод перезаписывается так, что он сначала вызывает gc
а затем называет оригинал gc
метод:
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
Пользовательские данные одного типа имеют одинаковую метатабельность; Следовательно wrap_garbage_collector()
Функция должна вызываться только один раз для каждого класса (при условии, что метатаблицы tolua++ создаются один раз и освобождаются только при выходе).
В нижней части этого ответа является полным bar.pkg
файл, который импортирует bar
функции и добавляет bar
класс для модуля Lua с именем foo
, foo
модуль загружается в интерпретатор ( см., например, мой пример SO tolua++) и используется следующим образом:
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
Реализация теста выводит, что происходит:
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)
Строительство bar
класс ниже немного сложнее: build_class()
Утилита возвращает класс (таблицу Lua) с указанием конструктора, деструктора и методов класса. Корректировки, несомненно, понадобятся, но в качестве демонстрации прототипа пример должен быть в порядке.
$#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
$]