Lua 用户数据:无法同时访问数组和方法 [英] Lua userdata: Unable to have simultaneous array access and methods

查看:32
本文介绍了Lua 用户数据:无法同时访问数组和方法的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我遇到了这个人的问题:Lua 用户数据数组访问和方法

I had this guy's problem: Lua userdata array access and methods

其中,当我设置用户数据元表的 __index 时,它总是调用 getter,而不是我的其他未为元事件声明的方法.上面链接的解决方案是在 Lua 中,我尝试了一个看起来不雅的 C 实现,但无论如何,它产生了一个新问题,因为我的新方法不能再接受参数,并且出现此错误:

wherein when I set the __index of my userdata's metatable, it always called the getter, instead of my other methods that weren't declared for meta-events. The solution to the above link is in Lua, and I attempted a C implementation which seems inelegant, but regardless, it creates a new problem in that my new methods can no longer take arguments, and I get this error:

尝试调用方法asTable"(表值)

关于这个 Lua 声明:

on this Lua statement:

print_r(c:asTable())

这就是我设置一切的方式:

This is how I set everything up:

//Methods, many of which are overridden Lua meta-events (with the underscores)
static const struct luaL_reg vallib_m [] = {
    {"asTable", PushLuaTable}, //these functions are not called
    {"asCopy", CopyLuaVal}, 

    {"__newindex", SetLuaVal},
    {"__index", GetLuaVal},
    {"__tostring", ValToString},
    {"__gc", GarbageCollectVal},
    {"__metatable", HideMetaTable},

    {NULL, NULL}
};

//Static library functions
static const struct luaL_reg vallib_f [] = {
    {"specialprint", PrintVals}, 
    {NULL, NULL}
};


int luaopen_custom(lua_State *L)
{
    luaL_newmetatable(L, "custom.Value");
    lua_pushstring(L, "__index");
    lua_pushvalue(L, -2);  /* pushes the metatable */
    lua_settable(L, -3);  /* metatable.__index = metatable */

    luaL_register(L, NULL, vallib_m);
    luaL_register(L, "special", vallib_f);

    return 0;
}

然后在我默认调用的 getter 中(通过 __index),我首先检查我打算调用的其他事件并将控制转移给它们,如下所示.请注意,我从堆栈中删除了包含函数名称的参数.

Then in my getter, which is called by default (via __index), I first check for other events that I intended to be called and transfer control to them as follows. Note that I remove the argument containing the name of the function from the stack.

//TODO: this is a tentative fix, I would rather do this with metatables
//checking for methods
if (lua_isstring(L, 2))
{
    field = luaL_checkstring(L, 2);
    if (unlikely(!field))
    {
        reporter->Warning("Fail in getter -- bad string as method attempt");
        return LUA_FAILURE;
    }

    if (strcmp(field, "asTable") == 0)
    {
        lua_remove(L, 2); //delete string "asTable"
        return PushLuaTable(L);
    }
    else if (strcmp(field, "asCopy") == 0)
    {
        lua_remove(L, 2); //delete string "asCopy"
        return CopyLuaVal(L);
    }
    //... other methods. 
    else
    {
        //Insert string back into stack??
    }

}

无论传递了多少参数,它都不会将我的方法视为函数,并且即使有任何括号或冒号也会引发错误.(它可以通过 c.asTable 访问,它适用于不带参数的方法,但我计划添加一些带参数的方法,无论如何,语法与方法不一致.

It doesn't treat my method as a function regardless of how many arguments are passed, and throws an error if there are even any parentheses or a colon. (It can be accessed by c.asTable, which works fine for methods that takes no arguments but I plan to add some that do, and regardless, the syntax is inconsistent with methods.

无论如何,最好不要通过我的 C getter 调用这些函数,而是使用元表来解决这个问题.如果可能,请提供一个使用 C API 的示例——Lua 中已经有 StackOverflow 解决方案,但我无法将它们转换为 C.

In any case, it would be preferable to NOT call these functions through my C getter, and instead solve this with metatables. If this is possible, please provide an example using the C API -- there are already StackOverflow solutions in Lua, but I haven't been able to translate them to C.

推荐答案

要从 Lua 访问 C 结构的字段,您需要一个函数作为 __index 元方法,因为您需要访问userdata 对象,如果 __index 是一个表,你将不会得到它:

For accessing fields of a C struct from Lua you'll need a function as the __index metamethod, because you need access to the userdata object, and you won't get it if __index is a table:

-- Lua example code; obj* should be userdatas ...
-- an example __index function
local function anIndex( o, k )
  print( "accessing", k, "in", o )
  return 1
end

local obj = {}
local meta = { __index = anIndex }
setmetatable( obj, meta )
print( obj )
--> table: 0xfcb060
print( obj.x )
--> accessing x in  table: 0xfcb060
--> 1

这对于属性来说很好用,但是对于访问由相同类型的所有用户数据共享的方法来说很不舒服(并且效率低下).__index 表会更好:

This works fine for properties, but it's uncomfortable (and inefficient) for accessing methods shared by all userdatas of the same type. An __index table would be better for that:

-- an example method
local function aMethod( o )
  print( "calling aMethod on", o )
  return 2
end

local obj2 = {}
local methods = { aMethod = aMethod }
local meta2 = { __index = methods }
setmetatable( obj2, meta2 )
print( obj2 )
--> table: 0xfcada0
print( obj2:aMethod() )
--> calling aMethod on  table: 0xfcada0
--> 2

但现在我们两者都想要!

But now we want both!

Metamethods 可以在 Lua 中链接,所以我们可以尝试设置一个 __index 函数作为 __index 表的回退(这里是 methods案例):

Metamethods can be chained in Lua, so we could try to set an __index function as fallback for the __index table (methods in this case):

setmetatable( methods, meta )
print( obj2 )
--> table: 0xfcada0
print( obj2.x )
--> accessing x in  table: 0xfcade0
--> 1
print( obj2:aMethod() )
--> calling aMethod on  table: 0xfcada0
--> 2

但是如果你仔细观察,你会发现 __index 函数得到的对象与 obj2 不同......

But if you look closer, you'll see that the __index function gets a different object than obj2 ...

print( methods )
--> table: 0xfcade0

它获取 methods 表作为第一个参数.所以我们无法访问原始用户数据(本例中的表),我们实际上无法查找任何字段.所以这行不通.

It gets the methods table as the first argument instead. So we lose access to the original userdata (table in this example), and we can't actually look up any fields. So that won't work.

setmetatable( methods, nil ) -- let's undo this ...

幸运的是,__index 函数可以做任意的事情,包括访问另一个表(例如存储在 upvalue 中的表):

Fortunately, an __index function can do arbitrary things, including accessing another table (e.g. one that's stored in an upvalue):

local obj3 = {}
local meta3 = {
  __index = function( o, k )
    local v = methods[ k ]  -- methods is an upvalue here
    if v == nil then
      print( "accessing", k, "in", o )
      v = 1
    end
    return v
  end
}
setmetatable( obj3, meta3 )
print( obj3 )
--> table: 0xfc23a0
print( obj3.x )
--> accessing x in  table: 0xfc23a0
--> 1
print( obj3:aMethod() )
--> calling aMethod on  table: 0xfc23a0
--> 2

现在效果很好!如果这种情况经常发生,我们可以编写一个辅助函数,为我们创建一个合适的 __index 函数.作为参数传递的 indexfunc 只关心字段查找,根本不需要处理方法.生成的函数会这样做:

Now this worked great! In case this happens more often, we could write a helper function that creates an appropriate __index function for us. The indexfunc that's passed as argument is only concerned with field lookup, and doesn't have to handle methods at all. The generated function will do that:

local function makeindex( methodstable, indexfunc )
  return function( o, k )
    local v = methodstable[ k ]
    if v == nil then
      v = indexfunc( o, k )
    end
    return v
  end
end

local obj4 = {}
local meta4 = { __index = makeindex( methods, anIndex ) }
setmetatable( obj4, meta4 )
print( obj4 )
--> table: 0xfc92b0
print( obj4.x )
--> accessing x in  table: 0xfc92b0
--> 1
print( obj4:aMethod() )
--> calling aMethod on  table: 0xfc92b0
--> 2

如果您尝试将其转换为 Lua C API,您会发现使用 luaL_Reg 数组而不是方法表和 lua_CFunction 数组更方便> 指向 Lua 函数的指针而不是堆栈索引.这就是 this 答案确实如此(此外,它让您可以为 luaL_setfuncs() 等所有方法设置 upvalues).

If you try to translate that to the Lua C API, you'll see that it's more convenient to take a luaL_Reg array instead of a methods table, and a lua_CFunction pointer instead of a stack index to a Lua function. And this is what the moon_propindex() function linked in this answer does (additionally it let's you set upvalues for all methods like luaL_setfuncs()).

这篇关于Lua 用户数据:无法同时访问数组和方法的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆