【问题标题】:binding structs and ctor/dtor with tolua++使用 tolua++ 绑定结构和 ctor/dtor
【发布时间】:2012-07-12 22:43:26
【问题描述】:

假设我想将一段代码绑定到 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()。

【问题讨论】:

  • 我目前正在考虑自己添加缺少的功能,但这意味着我将手动编写大量的 copypasta 绑定代码,并且我希望 tolua++ 为我完成这项工作。我的替代方法是在 tolua++ 的内部四处寻找,看看我是否可以利用现有的类代码。我觉得这两种选择都不愉快。

标签: c binding lua tolua++


【解决方案1】:

bar 函数可以使用 tolua++ 导入 Lua 并包装以产生对象样式的接口,包括垃圾收集。

为了演示参数的传递,我将bar接口改为

bar * bar_create(int x);
int bar_do_something(bar * baz, int y);
void bar_free(bar * baz);

并编写了一个测试实现,在调用函数时打印出xy等。

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 类添加到名为foo 的Lua 模块中。 foo 模块被加载到解释器 (see for example my SO tolua++ example) 并像这样使用:

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
$]

【讨论】:

  • Lua 比我想象的要写的要多得多,但看起来它可以作为一种解决方案。非常感谢您的冗长而精心编写的回答,我明天一下班就试试这个。
  • 非常感谢,这是一个很好的答案。我最初的希望是我不必编写 build_class 函数,因为 tolua++ 已经为 C++ 类执行此操作,并且不知何故,我可以纯粹使用 pkg 文件中的 tolua++ 语法来执行此操作,而无需嵌入 lua 代码。此外,您的解决方案导出了 bar_create 和 bar_free 符号,模块的用户不应明确使用它们,所以这不是那么漂亮。但这是一个解决方案,我会为我的图书馆使用这个,所以这是赏金!
  • @Enno 该示例存储了对函数的本地引用,因此可以在最后覆盖全局变量。我已将此添加到示例中。
猜你喜欢
  • 2016-10-10
  • 1970-01-01
  • 2014-05-21
  • 1970-01-01
  • 2017-05-17
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-04-21
相关资源
最近更新 更多