问题描述
我在我的 c++ 游戏项目中使用了 lua 5.1,但是当我尝试注册一个 c++ 成员函数时,我在使用 lua 时遇到了麻烦.我想在 lua 中使用我的 c++ 类成员函数,但是 lua_register() 函数的第三个参数只能接受c类型的普通函数指针或静态成员函数的指针.
I use lua 5.1 in my c++ game project, but I had a trouble to use lua when I try to register a c++ member function. I want to use my c++ class member function in lua , but lua_register() function's 3rd parameter can only accept c type normal function pointer or static member function's pointer.
听说lua bind库可以解决这个问题,但是我不想用lua bind.这很好,但对我的项目来说太重了.有没有什么方法可以在没有任何库的情况下注册 C++ 成员函数?我该怎么办?
I heard that lua bind library can solve this problem, but I do not want to use lua bind. It's good, but too heavy for my project. Is there have any method to register c++ member function without any libraries? How should I go about this?
推荐答案
我自己也有过同样的经历.
I have gone through this same experience myself.
我知道基本上有两个很好的解决方案.如果成员函数用于一个类,对于每个 lua 状态,该类只有一个.另一种更灵活但更复杂和更慢.(我很想学习其他方法/对这些方法的改进!)
There are basically two good solutions that I know. One is good if the member function is for a class which there will only be one of, for each lua state. Another is more flexible but more complicated and slower. (I would be keen to learn other methods / improvements on these methods!)
我认为 lua_bind 使用了一些与方法 1 非常相似的模板,但使用了一些技巧使实现与方法 2 一样灵活.我认为这两者中的任何一个都比 lua_bind 所做的更透明.
I think lua_bind uses some templates quite similar to method 1, but uses tricks to make the implementation as flexible as method 2. I think either of these is more transparent than what lua_bind does.
(1) my_class
的每个成员函数要传递给lua,都需要取lua_State * L
并返回int
.
(1) For each member function of my_class
which you want to pass to lua, it should take lua_State * L
and return int
.
(2) 在lua初始化的时候,在lua_extraspace
中存储一个指向关联my_class
的指针.
(2) At the time that lua is initialized, store a pointer to the associated my_class
in the lua_extraspace
.
*static_cast<my_class**>(lua_getextraspace(L_)) = &instance;
(3) 当你想将成员函数传递给 lua 时,使用这样的模板:
(3) When you want to pass member functions to lua, use a template like this:
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);
}
然后您可以像这样注册:
You then register things like this:
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 bind快.因为,get_extraspace
除了一点点指针运算之外什么都不做.最有可能的是,一个好的编译器可以优化 dispatch
模板,以便它生成的函数是内联的,并且不会有任何开销.
Method 1 has the benefit that it is pretty simple and extremely fast, I think it will be faster than lua bind. Because, get_extraspace
doesn't do anything except a little pointer arithmetic. Most likely, a good compiler can optimize the dispatch
template so that the function it makes is inlined, and there would be no overhead.
您可能想要更改 dispatch
模板,以便在额外空间进行空指针检查,这取决于您的 lua 状态和您的 my_class
的生命周期被管理.
You may want to change the dispatch
template so that there is a null pointer check at the extraspace, it depends on how the lifetimes of your lua state and your my_class
are managed.
潜在地,你还可以在额外空间中存储更复杂的东西,比如指向几个不同对象的指针,甚至像向量或其他东西(你可以在他们的文档中阅读如何配置 lua 额外空间).或者,您可以将内容存储在 lua 注册表中,调度函数可以从那里检索它们,但额外空间更快——这取决于您.
Potentially, you can also store more complicated stuff in the extraspace, like pointers to several different objects, or even like a vector or something (You can read about how to configure the lua extraspace in their docs). Or you could store things in the lua registry and the dispatch function can retrieve them from there, but the extraspace is faster -- it's up to you.
在方法 2 中,您基本上使用通常的 lua 技术将 C++ 对象推送到 lua 拥有的 lua,但是您在对象是 C++ std::function
的情况下执行此操作,并且您重载 _call
元函数,以便它调用该函数.(如果你不是在 C++11 中,你可以使用 boost::function
.)
In method 2, you basically use the usual lua techniques for pushing a C++ object to lua that is owned by lua, but you do it where the object is a C++ std::function
and you overload the _call
metafunction so that it calls the function. (If you are not in C++11 you can use boost::function
.)
然后当你想将一个c++成员函数推送到lua时,你使用std::bind
使它成为一个函数对象.
Then when you want to push a c++ member function to lua, you use std::bind
to make it a function object.
这个方法的缺点是在 lua 中,函数"的类型实际上是 userdata
,但是由于您可以很好地调用它并将其用作函数,因此实际上并不是事情.如果这对您不利,您可以做的一件事是使用相同的技巧,但之后也可以创建一个将函数对象 userdata 作为上值的闭包,并且在调用闭包时,它只是将参数转发给函数对象并返回结果.那么闭包的类型在lua中会是function
,但是基本上都是一样的.
This method has the drawback that within lua, the type of the "function" will actually be userdata
, but since you can call it just fine and use it as a function it doesn't really matter. If this is bad for you, one thing you can do is to use the same trick, but also afterwards, make a closure which has the function object userdata as an upvalue, and when the closure is called, it just forwards the arguments to the function object and returns the results. Then the type of the closure will be function
in lua, but it will do basically the same thing.
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);
}
这篇关于c++ - 如何在没有lua绑定的情况下将成员函数注册到lua的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持跟版网!