Получение правильного мета-метода Lua для вызова (C-api) - PullRequest
3 голосов
/ 22 марта 2012

Я использую [на самом деле учусь использовать] Lua C api.Я новичок в Lua, поэтому извиняюсь за неправильную терминологию и буду признателен за любые исправления.

У меня есть пустая глобальная таблица G, которую я создаю с использованием lua_setglobal в какой-то момент в init.__Index из G указывает на функцию C, которая, я считаю, называется метаметодом.При вызове эта функция создает новый элемент lightuserdata, который она вставляет в глобальную таблицу G.

Итак, если я правильно понимаю, G.foo приведет к вызову метаметода __index G и будет создан fooи добавил к G. Будущим вызовам G.foo больше не нужно будет вызывать метаметод, поскольку он обнаружит, что foo существует в G.

Теперь, когда я создаю foo, я связываю метатабельный с новымсоздал lightuserdata (foo), установив его __index в массив функций C, который содержит, в частности, «set» и «get».Идея заключается в том, что всякий раз, когда вызывается foo: get (), следует искать метатаблицу foo для вызова функции C, чтобы получить ее значение и т. Д.

Вот (нормальное) поведение, которое я вижу:

  • Вызов G.foo из файла lua.

    Это создает foo, используя метаметод G., как и ожидалось.

  • Затем вызовите G.foo: get ()

    Поскольку foo уже является частью G (предыдущий шаг), метаметод G не вызывается, как ожидалось.Вместо этого проверяется метатабельность foo и вызывается функция C, соответствующая get.Это также, как и ожидалось, и именно так я хочу, чтобы это работало.

Однако, если я сделаю это:

  • Invoke G.foo: get () напрямую, без предварительного вызова G.foo

    Затем он вызывает метаметод G дважды, один раз для foo (ожидается) и один раз для 'get' (не ожидается).Я не хочу, чтобы 'get' обрабатывался метаметодом G __index.Он в основном пытается создать новые lightuserdata под названием «get» (как это было для «foo») и так далее, что я не хочу делать.Мне бы хотелось, чтобы метатабельный объект вновь созданного foo был найден таким образом, чтобы для foo вызывалась правильная функция get '.

Я упростил свой вариант использования, чтобыпроблема наиболее очевидна, и поэтому я надеюсь, что она достаточно понятна.Кроме того, был бы признателен, если бы вы могли указать мне любую документацию lua или ссылку на функцию, которая поможет мне понять, почему это происходит.

Редактировать: Добавление некоторого кода с соответствующими частями, чтобы продемонстрировать, что я делаю:

static void init()
{
    lua_newtable( luaVM );
    lua_createtable( luaVM, 0, 0 );             
    lua_pushcfunction( luaVM, lua_metaMethod );
    lua_setfield( luaVM, -2, "__index" );
    lua_setmetatable( luaVM, -2 );          
    lua_setglobal( luaVM, "G" );    
}

static const luaL_reg lua_methods[] =
{
    {   "set",      lua_set },
    {   "get",      lua_get },      
    {0, 0}
};

static int lua_metaMethod( lua_State *luaVM )
{
    // I get "foo" by using lua_tostring( luaVM, 2 ), and store that in 'name'.
    // I then lookup 'fooData', which is a pointer to data associated with foo that I want to add to G.

    lua_getglobal( "G" );
    lua_pushlightuserdata( luaVM, ( void* ) fooData );
    lua_createtable( luaVM, 0, 0 );
    lua_createtable( luaVM, 0, 0 );
    luaL_register( luaVM, NULL, lua_methods );              // Trying to make sure foo:get() calls one of these, etc.
    lua_setfield( luaVM, -2, "__index" );
    lua_setmetatable( luaVM, -2 );
    lua_setfield( luaVM, -2, name );

    return 1;
}

1 Ответ

3 голосов
/ 22 марта 2012

Учитывая код, который вы описали, проблема в lua_metaMethod (кстати: плохая идея назвать ваши функции lua_. Это префикс, зарезервированный для функций Lua API) .

Возвращаемое значение из метаметода __index будет возвращено пользователю. Поэтому, если пользователь говорит G.foo, то будет возвращено значение метаметода G __index.

Что возвращает lua_metaMethod? Он возвращает ровно 1 возвращаемое значение. А поскольку параметры, переданные функции, являются первыми в стеке, она вернет первый параметр. Первым параметром метаметода __index всегда является таблица, для которой вызывается метаметод. В вашем случае эта таблица является таблицей, хранящейся в G.

Следовательно, G.foo вернет G. Следовательно, G.foo:get() эквивалентно G:get() (хотя в первой версии есть дополнительный вызов метаметода). Я предполагаю, что это не то, что вы хотите;)

То, что он должен вернуть, это легкие пользовательские данные, которые были только что сохранены в G["foo"].

Вместо этого рассмотрите этот код.

static int lua_metaMethod( lua_State *luaVM )
{
    //We won't use this, so only do this if you need the string.
    const char *name = lua_tostring(luaVM, 2); 

    // I then lookup 'fooData', which is a pointer to data associated with foo that I want to add to G.

    //No need to get G. We already have it: the first parameter.
    lua_pushlightuserdata( luaVM, ( void* ) fooData );    //Stack: table, key, userdata
    lua_createtable( luaVM, 0, 0 );                       //Stack: table, key, userdata, {}
    lua_createtable( luaVM, 0, 0 );                       //Stack: table, key, userdata, {}, {}
    luaL_register( luaVM, NULL, lua_methods );              // Trying to make sure foo:get() calls one of these, etc.
    lua_setfield( luaVM, -2, "__index" );                 //Stack: table, key, userdata, {}
    lua_setmetatable( luaVM, -2 );                        //Stack: table, key, userdata

      //Stack now contains: table, key, userdata.
    lua_insert(luaVM, 2);                //Stack: table, userdata, key
    lua_pushvalue(luaVM, -2);            //Stack: table, userdata, key, userdata
    lua_rawset(luaVM, 1);            //Use rawset because table has a metatable. It's probably best not to invoke it.

      //Stack now contains: table, userdata.
    lua_insert(luaVM, 1);                 //Stack: userdata, table.

    return 1;  //Return just the userdata. `table` will be discarded.
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...