Lua – 为什么C函数作为userdata返回?

我正在为我的引擎开发游戏脚本,并使用metatable将函数从表(存储自定义函数和播放器数据)重定向到userdata对象(这是我的Player类的主要实现),以便用户可以用self来指代两者。

这就是我在Player类中使用C#进行绑定的方法:

  state.NewTable("Player"); // Create Player wrapper table state["Player.data"] = this; // Bind Player.data to the Player class state.NewTable("mt"); // Create temp table for metatable 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)"); // Change Player's metatable 

对于我的Player类,我实现了一个方法, bool IsCommandActive(string name) 。 当我需要使用self调用此方法时,它需要使用userdata对象而不是表,否则我会收到以下错误:

NLua.Exceptions.LuaScriptException:’实例方法’IsCommandActive’需要非空目标对象’

原因很明显。 这是因为self引用表而不是userdata。 所以我实现了一个metatable,以便它可以使用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 ,以向一个marshaller公开一些与之关联的状态/上下文。 但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。 使用Lua table而不是userdata可以有效地完成它。 那么你会说“每个本地函数都是一个表”吗? 🙂

经过大量关于元表的阅读,我设法解决了我的问题。

为了回答标题中的问题,它显然是NLua刚刚决定做的,并且是特定于实现的。 在任何其他绑定中,它可能很好地返回function ,但对于NLua来说显然不是这样。

至于我如何设法完成我想要的,我必须定义metatable __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.datauserdata检索它。 如果那里不存在,那么Lua只做它的事情并返回nil

就这样,我可以从userdata检索字段! 然而,我很快就注意到,如果我在Lua中设置self.Pos ,那么Player.Pos不会在支持C#代码中更新。 同样快,我意识到这是因为PosPlayer包装器表中产生了一个错过,这意味着它正在为表创建一个新的Pos字段,因为它实际上并不存在!

这不是预期的行为,所以我不得不重写__newindex 。 在此特定实现中,它检查Player.datauserdata )是否具有key ,如果是,则设置该特定key的数据。 如果它在userdata中不存在,那么它应该为Player包装器表创建它,因为它应该是用户的自定义Player实现的一部分。