Lua - 为什么返回 C 函数作为 userdata?
我正在为我的引擎开发游戏脚本,并使用元表将来自表(存储玩家自定义函数和数据)的函数重定向到 userdata 对象上(这是我的 Player 类的主要实现),以便用户可以使用“self”同时引用两者。
这是我在 Player 类中在 C# 中绑定的方法:
state.NewTable("Player"); // 创建 Player 包装表
state["Player.data"] = this; // 将 Player.data 绑定到 Player 类
state.NewTable("mt"); // 创建元表的临时表
state.DoString(@"mt.__index = function(self,key)
local k = self.data[key]
if key == 'data' or not k then
return rawget(self, key)
elseif type(k) ~= 'function' then
print(type(k))
print(k)
return k
else
return function(...)
if self == ... then
return k(self.data, select(2,...))
else
return k(...)
end
end
end
end");
state.DoString("setmetatable(Player, mt)"); // 修改 Player 的元表
对于我的 Player 类,我实现了一个方法 bool IsCommandActive(string name)。当我需要使用 self 来调用此方法时,它需要使用 userdata 对象而不是表,否则我会收到以下错误消息:
NLua.Exceptions.LuaScriptException: 'instance method 'IsCommandActive' 需要一个非空目标对象'
这是显而易见的。这是因为 self 指向表而不是 userdata。因此,我实现了一个元表,这样它就可以使用 self 来引用任一者。这一实现取自这里,但以下是我的特定变体(我的 userdata 存储在一个名为 data 的索引中:
mt.__index = function(self,key)
local k = self.data[key]
if key == 'data' or not k then
return rawget(self, key)
elseif type(k) ~= 'function' then
print(type(k))
print(k)
return k
else
return function(...)
if self == ... then
return k(self.data, select(2,...))
else
return k(...)
end
end
end
end
end
之后我使用 setmetatable,显然。
现在进入我的问题的实质。请注意下面的代码,即在 elseif 下打印 type(k) 和 print(k)。这是因为我注意到我仍然得到相同的错误消息,所以我想进行一些调试。在这样做时,我得到了以下输出(我相信这是针对 IsCommandActive 的):
userdata:0BD47190
难道不应该打印'function'吗?为什么打印'userdata:0BD47190'?如果是这样,如何检测值是否为C函数,以便我可以做出正确的重定向?
任何属于 C 类的函数或对象都是用户数据 userdata。
这并不正确。无论是本地函数还是 Lua 编写的函数,函数都是函数。检查本地函数的类型会打印出 "function"。
只是你的绑定解决方案可能使用了一个带有 __call 元方法的 userdata,以公开与其相关联的某些状态/上下文的编码器。但这并不意味着每个本地函数都是一个 userdata,或者每个绑定库都会以相同的方式实现。可以使用 Lua table 相同的方式有效地实现。那么你会说 "每个本地函数都是一个表格" 吗? :)
- Lua 虚拟机加密load(string.dump(function)) 后执行失败问题如何解决
- 我想创建一个 Nginx 规则,禁止访问
- 如何将两个不同的lua文件合成一个 东西有点长 大佬请耐心看完 我是小白研究几天了都没搞定
- 如何在roblox studio中1:1导入真实世界的地形?
- 求解,lua_resume的第二次调用继续执行协程问题。
- 【上海普陀区】内向猫网络招募【Skynet游戏框架Lua后端程序员】
- SF爱好求教:如何用lua实现游戏内调用数据库函数实现账号密码注册?
- Lua实现网站后台开发
- LUA错误显式返回,社区常见的规约是怎么样的
- lua5.3下载库失败
- 请问如何实现文本框内容和某个网页搜索框内容连接,并把网页输出来的结果反馈到另外一个文本框上
- lua lanes多线程使用
- 一个kv数据库
- openresty 有没有比较轻量的 docker 镜像
- 想问一下,有大佬用过luacurl吗
- 在Lua执行过程中使用Load函数出现问题
- 为什么 neovim 里没有显示一些特殊字符?
- Lua比较两个表的值(不考虑键的顺序)
- 有个lua简单的项目,外包,有意者加微信 liuheng600456详谈,最好在成都
- 如何在 Visual Studio 2022 中运行 Lua 代码?

经过大量阅读有关元表的文章,我终于解决了我的问题。
回答标题中的问题,显然 NLua 就是决定这样做并且实现特定的。在任何其他绑定中,它也可能会返回
function,但这显然不适用于 NLua。至于我如何实现我想要的功能,我不得不定义元表
__index和__newindex函数:state.NewTable("Player"); state["Player.data"] = this; state.NewTable("mt"); state.DoString(@"mt.__index = function(self,key) local k = self.data[key] local metatable = getmetatable(k) if key == 'data' or not k then return rawget(self, key) elseif type(k) ~= 'function' and (metatable == nil or metatable.__call == nil) then return k else return function(...) if self == ... then return k(self.data, select(2,...)) else return k(...) end end end end"); state.DoString(@"mt.__newindex = function(self, key, value) local c = rawget(self, key, value) if not c then local dataHasKey = self.data[key] ~= key if not dataHasKey then rawset(self, key, value) else self.data[key] = value end else rawset(self, key, value) end end"); state.DoString("setmetatable(Player, mt)");__index的作用是覆盖表的索引方式。在这个实现中,如果在Player包装表中没有找到key,那么它会去尝试从Player.data中的userdata中检索它。如果它在那里不存在,那么 Lua 就会做它的事情并返回nil。就这样,我就可以从
userdata中检索字段了!然而,我很快开始注意到,如果我在 Lua 中设置了self.Pos,那么Player.Pos在支持的 C# 代码中就不会更新。我很快意识到,这是因为Pos在Player包装表中生成了一个错误的索引,这意味着它实际上并不存在!这不是预期的行为,因此我还必须覆盖
__newindex。在这个特定的实现中,它检查Player.data(userdata) 是否有key,如果有,则为该特定key设置数据。如果它在userdata中不存在,则应为Player包装表创建它,因为它应该是用户自定义的Player实现的一部分。