Изолированная среда Lua 5.2 в разных объектах с C API

#c #lua #embedded-language

#c #lua #встроенный язык

Вопрос:

Рассмотрим следующий код C с использованием Lua C API:

 #include <string>
#include <cassert>

#include <lua/lua.hpp>

class AwesomeThing
{
    lua_State* _lua;
    std::string _name;

public:
    AwesomeThing(lua_State* L, const std::stringamp; name, const std::stringamp; luafile)
        : _lua{ L },
          _name{ name }
    {
        assert(luaL_loadfile(_lua, luafile.c_str()) == 0); // 1:chunk

        lua_newtable(_lua); // 1:chunk, 2:tbl
        lua_newtable(_lua); // 1:chunk, 2:tbl, 3:tbl(mt)
        lua_getglobal(_lua, "_G"); // 1:chunk, 2: tbl, 3:tbl(mt), 4:_G
        lua_setfield(_lua, 3, "__index"); // 1:chunk, 2: tbl, 3:tbl(mt)
        lua_setmetatable(_lua, 2); // 1:chunk, 2: tbl

        lua_setupvalue(_lua, -2, 1); // 1:chunk
        if (lua_pcall(_lua, 0, 0, 0) != 0) // compiled chunk
        {
            auto error = lua_tostring(_lua, -1);
            throw std::runtime_error(error);
        }

        lua_setglobal(_lua, _name.c_str()); // empty stack
    }

    void init()
    {
        lua_getglobal(_lua, _name.c_str()); // 1:env
        assert(lua_isnil(_lua, 1) == 0);

        lua_getfield(_lua, 1, "onInit"); // 1:env, 2:func
        assert(lua_isnil(_lua, 2) == 0);
        assert(lua_isfunction(_lua, 2) == 1);

        assert(lua_pcall(_lua, 0, LUA_MULTRET, 0) == 0); // 1:env, 2:retval

        lua_pop(_lua, 1); // -1:env
        lua_pop(_lua, 1); // empty stack
        assert(lua_gettop(_lua) == 0);
    }
};

int main()
{
    lua_State* L = luaL_newstate();
    luaL_openlibs(L);

    AwesomeThing at1(L, "thing1", "file1.lua");
    AwesomeThing at2(L, "thing2", "file2.lua");

    at1.init();
    at2.init();

    return 0;
}
  

С двумя очень простыми файлами Lua:

file1.lua

 function onInit()
    print("init file1")
end
  

file2.lua

 function onInit()
    print("init file2")
end
  

Как есть, я получаю ошибку при at2 вызове конструктора при lua_pcall : попытке вызвать значение таблицы

Когда я комментирую все ссылки / вызовы на at2 , вместо этого я получаю ошибку в at1 init() at lua_getfield(_lua, 1, "onInit") : PANIC: незащищенная ошибка при вызове Lua API (попытка индексировать нулевое значение)

Я чувствую, что есть что-то фундаментальное, чего мне не хватает в том, как я справляюсь с песочницей. Я изо всех сил старался следовать нескольким другим примерам изолированной обработки Lua 5.2, которые я нашел в Интернете, но пока ничего не помогло.

Ответ №1:

После того, как я сам поработал с кодом, я смог это исправить, и ошибки, похоже, возникают всего из-за нескольких ошибок.

  • lua_pcall вызываемая функция извлекается из стека, но в обоих случаях в вашем коде предполагается, что функция все еще находится в стеке после lua_pcall . Это приводит к неправильному управлению стеком.
  • В конструкторе вы, по-видимому, пытаетесь сохранить ссылку на фрагмент (функцию) вместо таблицы среды. Это даже не работает, потому что функция уже была извлечена. Если бы это сработало, lua_getfield вызов init() не работал бы так, как предполагалось, поскольку в блоке нет поля с именем onInit — в таблице среды есть.

Исправление конструктора включает в себя создание таблицы окружения и загрузку фрагмента в обратном порядке, так что таблица окружения остается в стеке после вызова функции:

         lua_newtable(_lua); // 1:tbl

        assert(luaL_loadfile(_lua, luafile.c_str()) == 0); // 1:tbl, 2:chunk

        lua_newtable(_lua); // 1:tbl, 2:chunk, 3:tbl(mt)
        lua_getglobal(_lua, "_G"); // 1:tbl, 2:chunk, 3:tbl(mt), 4:_G
        lua_setfield(_lua, 3, "__index"); // 1:tbl, 2:chunk, 3:tbl(mt)
        lua_setmetatable(_lua, 1); // 1:tbl, 2:chunk
        lua_pushvalue(_lua, 1); // 1:tbl, 2:chunk, 3:tbl

        lua_setupvalue(_lua, -2, 1); // 1:tbl, 2:chunk
        if (lua_pcall(_lua, 0, 0, 0) != 0) // compiled chunk
        {
            auto error = lua_tostring(_lua, -1);
            throw std::runtime_error(error);
        }

        // 1:tbl

        lua_setglobal(_lua, _name.c_str()); // empty stack
  

Затем в init() , поскольку вы используете LUA_MULTRET , просто очистите стек, заменив оба всплывающих вызова на lua_settop(_lua, 0) .

Комментарии:

1. Я только что понял, как это сделать, используя некоторые Lua с luaL_dostring , и все еще не был уверен, что я делаю не так с моим кодом C, но это объясняет это. Большое спасибо!

2. @Addy Рад помочь! lua_type() в частности, это очень полезно для проверки ваших предположений о том, что находится в стеке. Я разбросал это повсюду и сразу обнаружил, что раньше стек был пуст lua_setglobal в конструкторе. С этого момента все начало собираться воедино.