Lua C API:如何从表内存储的C函数读取该表的其他字段值
问题背景
你正在实现Lua与C++的绑定逻辑,期望Lua侧可以通过如下方式访问游戏实体属性:
ball = entity_query.get_first_entity_by_name("Ball"); print(ball.id) ball.name.set("new name") print(ball.name.get())
当前C侧的实现逻辑是创建两层Lua表:外层表存储实体id字段和name子表,name子表内存储同名实体id、get/set两个C绑定函数,需要在两个C函数中读取所属name表内存储的实体id字段。
结论
该需求完全可以实现,以下是两种常用实现方案,可根据代码结构选择:
方案1:C闭包上值绑定(性能最优,推荐)
不需要在函数运行时动态查表,注册get/set函数时直接把实体id作为闭包上值绑定到C函数上,调用时直接从上值读取id即可,不依赖表结构,也不需要修改Lua侧调用语法:
int getNameFn(lua_State *state) { lua_getglobal(state, "__privateDatabase"); auto *pointer = lua_touserdata(state, -1); auto &entityDatabase = *static_cast<EntityDatabase *>(pointer); // 从闭包上值读取entity id,上值索引从1开始计数 uint32_t entity = static_cast<uint32_t>(lua_tointeger(state, lua_upvalueindex(1))); const char *name = entityDatabase.getComponent<NameComponent>(entity).name; lua_pushstring(state, name); return 1; } int setNameFn(lua_State *state) { // 栈索引1位置是Lua侧传入的第一个参数:新名称 const char * argument = lua_tostring(state, 1); lua_getglobal(state, "__privateDatabase"); auto *pointer = lua_touserdata(state, -1); auto &entityDatabase = *static_cast<EntityDatabase *>(pointer); // 同样从闭包上值读取entity id uint32_t entity = static_cast<uint32_t>(lua_tointeger(state, lua_upvalueindex(1))); entityDatabase.setComponent<NameComponent>(entity, {argument}); return 0; } // 表注册逻辑改造 lua_createtable(state, 0, 2); // 外层ball表存id lua_pushinteger(state, entityId); lua_setfield(state, -2, "id"); // 创建name子表 lua_createtable(state, 0, 2); // 如果不需要Lua侧访问ball.name.id,可删掉子表存id的逻辑 // lua_pushinteger(state, entityId); // lua_setfield(state, -2, "id"); // 绑定get函数,传入1个上值:entityId lua_pushinteger(state, entityId); lua_pushcclosure(state, getNameFn, 1); lua_setfield(state, -2, "get"); // 绑定set函数,传入1个上值:entityId lua_pushinteger(state, entityId); lua_pushcclosure(state, setNameFn, 1); lua_setfield(state, -2, "set"); lua_setfield(state, -2, "name");
方案2:运行时动态查表
如果确实需要动态从所属表读取id,需要修改Lua侧调用语法,将点号调用改为冒号调用(ball.name:get()、ball.name:set("new name")),冒号调用会自动把所属表作为第一个参数self传入C函数,此时可直接从self表读取id字段:
该方案会修改Lua侧调用形式,和你最初预期的接口写法不兼容,无特殊需求不推荐。
int getNameFn(lua_State *state) { lua_getglobal(state, "__privateDatabase"); auto *pointer = lua_touserdata(state, -1); auto &entityDatabase = *static_cast<EntityDatabase *>(pointer); // 栈索引1位置是self,也就是name表本身 lua_getfield(state, 1, "id"); uint32_t entity = static_cast<uint32_t>(lua_tointeger(state, -1)); lua_pop(state, 1); // 弹出读取到的id,维持栈平衡 const char *name = entityDatabase.getComponent<NameComponent>(entity).name; lua_pushstring(state, name); return 1; } int setNameFn(lua_State *state) { // 栈索引1位置是self(name表),索引2位置才是传入的新名称 const char * argument = lua_tostring(state, 2); lua_getglobal(state, "__privateDatabase"); auto *pointer = lua_touserdata(state, -1); auto &entityDatabase = *static_cast<EntityDatabase *>(pointer); // 从self表读取id lua_getfield(state, 1, "id"); uint32_t entity = static_cast<uint32_t>(lua_tointeger(state, -1)); lua_pop(state, 1); entityDatabase.setComponent<NameComponent>(entity, {argument}); return 0; }
如果要保留点号调用形式同时实现动态查表,需要通过元表__index做代理对象,实现复杂度远高于闭包上值方案,无特殊需求不建议使用。
注意事项
- 你当前给出的代码中
setNameFn的函数定义只写了参数类型lua_State *,没有形参名,会导致函数内部访问state时编译报错,需要修正为int setNameFn(lua_State *state)。 - 编写Lua C绑定逻辑时需要时刻注意栈平衡,读取完临时值后及时弹出不需要的栈元素,避免栈污染引发后续逻辑异常。
lua_upvalueindex是Lua C API提供的专门用于获取闭包上值的接口,传入上值的序号(从1开始)即可返回对应上值的栈伪索引,不需要手动执行压栈操作。
内容的提问来源于stack exchange,提问作者Gasim
相关产品推荐
相关产品推荐

