C代码:
g_Runtime->set_on_connection([](TCPConnection& conn) {
//调用lua函数 on_connection(conn)
});
lua函数:
function on_connection(conn)
print(conn:is_connect())
end
问题:
怎么在C/C++里调用一个参数是C++对象的lua函数,并且lua函数里,可以调用这个对象的C方法。
Solution:
通过userdata把对象的指针传递到lua的函数参数里。
lua_getglobal(L, "on_connection");
TCPConnection** ptr =(TCPConnection**) lua_newuserdata(L,sizeof(TCPConnection*));
*ptr=&conn;
lua_pcall(L, 1, 0, 0)
接下来lua代码里的conn:is_connect()其实是一个语法糖
conn:is_connect() = conn.is_connect(conn)
这样就很清晰了,conn是lua中的一个值,conn:is_connect()就是用 is_connect 作为key来索引conn。显然,这个值需要是一个函数。然后这个函数把conn当作参数做了一次调用。
那么我们需要做两件事情,
- C里存在一个符合is_connect需求的luac函数,参数为conn
- 让conn的is_connect能索引到这个函数
这样print(conn:is_connect())就能够正常工作了。
第一件事情简单,
int conn_is_connected(lua_State* L)
{
TCPConnection* conn = *(TCPConnection**)lua_touserdata(L,1);
bool state = conn->connected();
lua_pushboolean(L,state);
return 1;
}
第二件事情就有点复杂了,需要用到lua里metatable(元表)的机制。
元表官网是这么描述的:
Lua 中的每个值都可以有一个
元表。 这个
元表就是一个普通的 Lua 表, 它用于定义原始值在特定操作下的行为。 如果你想改变一个值在特定操作下的行为,你可以在它的元表中设置对应域。 例如,当你对非数字值做加操作时, Lua 会检查该值的元表中的 "
__add
" 域下的函数。 如果能找到,Lua 则调用这个函数来完成加这个操作。
以上可以看出,元表就是用来改变一个值在特定操作下的行为的,跟我们的需求对应。具体来说,我们需要把userdata这个值被“is_connect”当作key索引时,返回一个函数。这对应了lua元表里的“__index”元方法。
"index":索引table[key]。 当table不是表或是表table中不存在key这个键时,这个事件被触发。 此时,会读出table相应的元方法。
尽管名字取成这样, 这个事件的元方法其实可以是一个函数也可以是一张表。 如果它是一个函数,则以 table 和 key 作为参数调用它。 如果它是一张表,最终的结果就是以 key 取索引这张表的结果。
那么,我们要做的就是,创建一张元表,设置到conn上。这张元表的“_index”元方法在key为“is_connect”的时候,返回上面的conn_is_connected函数。
最终C代码:
static const struct luaL_Reg tcp_connection_meta[] =
{
{ "is_connect",conn_is_connected },
{ nullptr,nullptr }
};
void attach_tcpconnection(lua_State* L)
{
luaL_newlib(L, tcp_connection_meta);
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
lua_setmetatable(L, -2);
}
g_Runtime->set_on_connection([](TCPConnection& conn) {
lua_getglobal(L, "on_connection");
lua_pushuserdata(L, (void*)&conn);
attach_tcpconnection(L);
lua_pcall(L, 1, 0, 0);
});
这里我们把__index元方法赋值为了一张表tcp_connection_meta而不是一个函数,因为这样可以随意给lua里的conn对象添加新的函数。问题解决。
以上,打完,收工,撒花~
-----更新-----
文章原来用的是lightuserdata,后来被坑惨,lua里lightuserdata作用很有限,而且所有lightuserdata共享同一份元表,这一点官网文档也没写。在luawiki里有提到:
lua-users wiki: Light User Datalua-users.org