如何在c++中不绑定lua的情况下将成员函数注册到lua

How to register member function to lua without lua bind in c++

本文关键字:lua 情况下 成员 函数 注册 c++ 绑定      更新时间:2023-10-16

我在我的c++游戏项目中使用lua 5.1,但当我尝试注册一个c++成员函数时,我遇到了使用lua的麻烦。我想在lua中使用我的c++类成员函数,但是lua_register()函数的第三个形参只能接受c类型的普通函数指针或静态成员函数的指针。

我听说lua绑定库可以解决这个问题,但我不想使用lua绑定。很好,但是对我的项目来说太重了。有没有任何方法注册c++成员函数没有任何库?我该怎么做呢?

我自己也有过同样的经历。

我知道基本上有两种好的解决方案。如果成员函数是针对一个类的,那么对于每个lua状态只有一个成员函数是好的。另一种方法更灵活,但更复杂、更慢。(我渴望学习其他方法/改进这些方法!)

我认为lua_bind使用了一些与方法1非常相似的模板,但使用了一些技巧使实现像方法2一样灵活。我认为这两种方法都比lua_bind更透明。

方法1

(1)对于要传递给lua的my_class的每个成员函数,它应该取lua_State * L并返回int

(2)在初始化lua的同时,在lua_extraspace中存储一个指向my_class的指针。

*static_cast<my_class**>(lua_getextraspace(L_)) = &instance;

(3)当你想传递成员函数给lua时,使用如下模板:

typedef int (my_class::*mem_func)(lua_State * L);
// This template wraps a member function into a C-style "free" function compatible with lua.
template <mem_func func>
int dispatch(lua_State * L) {
    my_class * ptr = *static_cast<my_class**>(lua_getextraspace(L));
    return ((*ptr).*func)(L);
}

然后像这样注册:

const luaL_Reg regs[] = {
    { "callback_1", &dispatch<&my_class::callback_1> },
    { "callback_2", &dispatch<&my_class::callback_2> },
    { "callback_3", &dispatch<&my_class::callback_3> },
    { NULL, NULL }
};
luaL_register(L, regs); 

方法1的好处是它非常简单和非常快,我认为它将比lua绑定更快。因为get_extraspace除了做一点指针运算外什么也没做。最有可能的是,一个好的编译器可以优化dispatch模板,使它生成的函数是内联的,这样就不会有任何开销。

你可能想改变dispatch模板,以便在额外空间有一个空指针检查,这取决于你的lua状态和my_class的生存期是如何管理的。

潜在地,你也可以在额外空间中存储更复杂的东西,比如指向几个不同对象的指针,或者甚至像一个向量之类的东西(你可以在他们的文档中阅读如何配置lua额外空间)。或者您可以将内容存储在lua注册表中,然后分派函数可以从那里检索它们,但是额外空间更快——这取决于您。

方法2

在方法2中,你基本上使用通常的lua技术来将一个c++对象推入lua,但你这样做的对象是一个c++ std::function,你重载_call元函数,这样它就调用了这个函数。(如果你不是c++ 11,你可以使用boost::function)

当你想把一个c++成员函数推入lua时,你可以使用std::bind使它成为一个函数对象。

这个方法的缺点是,在lua中,"函数"的类型实际上是userdata,但是因为你可以很好地调用它并将其作为函数使用,所以这并不重要。如果这对您不利,您可以做的一件事是使用相同的技巧,但也是在之后,创建一个将函数对象userdata作为upvalue的闭包,当调用闭包时,它只是将参数转发给函数对象并返回结果。那么闭包的类型在lua中将是function,但它将做基本相同的事情。

typedef std::function<int(lua_State *)> lua_function;
char const * cpp_function = "CPP_Function";
static int intf_dispatcher ( lua_State* L )
{
    //make a temporary copy, in case lua_remove(L,1) might cause lua to garbage collect and destroy it
    lua_function f = * static_cast<lua_function *> (luaL_checkudata(L, 1, cpp_function));
    // remove from the stack before executing, so that like all other callbacks, f finds only its intended arguments on the stack.
    lua_remove(L,1);
    int result = (f)(L);
    return result;
}
static int intf_cleanup ( lua_State* L )
{
    lua_function * d = static_cast< lua_function *> (luaL_testudata(L, 1, cpp_function));
    if (d == NULL) {
        std::cerr << "ERROR: intf_cleanup called on data of type: " << lua_typename( L, lua_type( L, 1 ) ) << std::endl;
        lua_pushstring(L, "C++ function object garbage collection failure");
        lua_error(L);
    } else {
        d->~lua_function();
    }
    return 0;
}
static int intf_tostring( lua_State* L )
{
    lua_function * d = static_cast< lua_function *> (luaL_checkudata(L, 1, cpp_function));
    // d is not null, if it was null then checkudata raised a lua error and a longjump was executed.
    std::stringstream result;
    result << "c++ function: " << std::hex << d;
    lua_pushstring(L, result.str().c_str());
    return 1;
}
void register_metatable ( lua_State* L )
{
    luaL_newmetatable(L, cpp_function);
    lua_pushcfunction(L, intf_dispatcher);
    lua_setfield(L, -2, "__call");
    lua_pushcfunction(L, intf_cleanup);
    lua_setfield(L, -2, "__gc");
    lua_pushcfunction(L, intf_tostring);
    lua_setfield(L, -2, "__tostring");
    lua_pushvalue(L, -1); //make a copy of this table, set it to be its own __index table
    lua_setfield(L, -2, "__index");
    lua_pop(L, 1);
}
void push_function( lua_State* L, const lua_function & f )
{
    void * p = lua_newuserdata(L, sizeof(lua_function));
    luaL_setmetatable(L, cpp_function);
    new (p) lua_function(f);
}

前面的答案是正确的。

就这个问题而言,这取决于当您从lua调用类成员时,是希望lua决定使用的对象,还是希望将其绑定到固定对象实例的成员。如果它是固定的,那么用std::bind创建一个函数对象来保存要调用的成员函数的指针,以及调用它的this指针,并将其注册到lua中是相当简单的。

http://en.cppreference.com/w/cpp/utility/functional/bind

代码看起来像这样:

class C {
   public:
      void blah(lua_State* L);
};
C inst;
lua_pushcclosure(L, std::bind(&C::blah, &inst, std::placeholder::_1), 0);
lua_setglobal(L, "blah");

还有一个更自然的方法来绑定C/c++函数与lua,避免编写包装器,看看这里:

http://inspired-code.blogspot.com.au/p/automagical-language-bindings-using-c.html