Lua - 为什么 C 函数作为用户数据返回?

Lua - Why are C functions returned as userdata?

我正在为我的引擎编写游戏脚本,我正在使用元table将函数从table(为玩家存储自定义函数和数据)重定向到用户数据对象(这是我的播放器 class) 的主要实现,因此用户可以使用 self 来指代两者。

这就是我在 Player class:

中使用 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 class,我实现了一个方法 bool IsCommandActive(string name)。当我需要使用 self 调用此方法时,它需要使用 userdata 对象,而不是 table,否则我会得到以下错误:

NLua.Exceptions.LuaScriptException: 'instance method 'IsCommandActive' requires a non null target object'

原因显而易见。这是因为 self 指的是 table,而不是用户数据。所以我实现了一个 metatable 以便它可以使用 self 来引用任何一个。该实现取自 here,但这是我的特定变体(我的用户数据存储在名为 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 函数以便我可以进行正确的重定向?

阅读了大量有关元tables 的资料后,我设法解决了我的问题。

要回答标题中的问题,显然是NLua刚刚决定做的事情,并且是implementation-specific。在任何其他绑定中,它很可能 return 与 function 一样,但 NLua.

显然不是这种情况

至于我如何设法完成我想要的,我必须定义元table __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 所做的是覆盖 table 的索引方式。在此实现中,如果在 Player 包装器 table 中找到 key 而不是 ,则它会尝试从 [=17] 中检索它=] 在 Player.data 中。如果它在那里不存在,那么 Lua 只是做它的事情并且 returns nil.

就这样,我可以从 userdata 中检索字段!但是,我很快开始注意到,如果我在 Lua 中设置 self.Pos,那么 Player.Pos 而不是 支持 C# 代码。同样迅速,我意识到这是因为 PosPlayer 包装器 table 中生成了一个未命中,这意味着它正在为 Pos 创建一个新字段=54=] 因为它实际上并不存在!

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

any functions or objects that are part of a C class are userdata`

这不是真的。函数就是函数,不管是原生的还是用Lua写的。检查本机函数的类型将打印 "function".

这可能是因为您的绑定解决方案正在使用 userdata 并在其上设置了 __call 元方法,以公开一个编组器与一些 state/context 相关联。但这并不意味着每个本机函数都是用户数据,或者每个绑定库都将以相同的方式实现。使用 Lua table 而不是 userdata 可以有效地完成相同的操作。那你会说 "every native function is a table" 吗? :)