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

时间:2018-03-11 12:59:09

标签: c lua lua-api metatable

我正在为我的引擎制作游戏脚本,并使用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来引用它们。实现取自here,但这是我的特定变体(我的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进行操作。

现在回答我的问题。请注意我如何在type(k)下打印print(k)elseif。这是因为我注意到我仍然遇到同样的错误,所以我想做一些调试。这样做时,我得到了以下输出(我认为是IsCommandActive):

userdata: 0BD47190

不应该打印'function'吗?为什么要打印'userdata: 0BD47190'?最后,如果确实如此,我如何检测该值是否为C函数,以便我可以进行正确的重定向?

2 个答案:

答案 0 :(得分:0)

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

为了回答标题中的问题,它显然是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的作用是覆盖表的索引方式。在此实现中,如果在key包装器表中找到Player ,则它会尝试从userdata Player.data中检索它}。如果它不存在,那么Lua只是做它的事情并返回nil

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

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

答案 1 :(得分:0)

  

属于C类的任何函数或对象都是userdata`

事实并非如此。函数是一个函数,无论它是原生的还是用Lua编写的。检查本机函数的类型将打印“function”。

只是可能是你的绑定解决方案正在使用userdata并在其上设置__call元方法,以向marshaller公开与之关联的某些状态/上下文。但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。使用Lua table而不是userdata可以有效地完成它。那么你会说“每个本地函数都是一个表”吗? :)