Вызов анонимной Lua функции из С++
Мне понадобилось решить следующую задачу:
для окна в моем GUI нужно задать функцию, которая будет вызвана в результате нажетия некоей комбинации кнопок(«горячие кнопки», hot keys). Окна GUI создаются в Lua, соответственно и функция, которая должна вызываться — это Lua функция.
Проблема в том, что функция может быть локальной.
Установка калбека у окна может выглядеть так:
window:setCallback(function () print('Hi from callback!') end, 'left alt', 'a')
Соответственно, функция регистрации калбека должна выглядеть как-то так:
int windowSetCallback(lua_State* L) { // stack: window callback key1 key2 key3 key4 ... return 0; }
Первый параметр на вершине стека — это переданный из Lua указатель на окно.
Второй параметр — некая, возможно безымянная, Lua функция. Ее нужно как-то сохранить, для дальнейшего использования.
Далее идут строковые параметры — список кнопок для комбинации.
Для хранения в С временных Lua объектов используется реестр.
Для того, чтобы сохранить ссылку на Lua объект, находящийся на вершине стека,
нужно вызвать функцию
int luaL_ref (lua_State *L, int t);
с параметром
t = LUA_REGISTRYINDEX
которая сохранит ссылку на объект в специальной внутренней таблице Lua(реестре) и вернет индекс,
по которому в дальнейшем можно будет обратиться, чтобы получить доступ к объекту.
Функция:
luaL_ref()
выталкивает объект из стека и возвращает индекс ссылки на него.
Функция:
void lua_rawgeti (lua_State *L, int index, int n);
вызванная с параметром
index = LUA_REGISTRYINDEX
поместит на стек объект, находящийся в этой таблице по индексу n.
Если временный Lua объект больше не нужен, то ссылку на него можно удалить при помощи функции
void luaL_unref (lua_State *L, int t, int ref);
Итак, в моем случае функция регистрации Lua калбека выглядит так:
int windowSetCallback(lua_State* L) { // stack: window callback key1 key2 key3 key4 WIDGET_CAST(WindowBase, window, L, 1); // помещаем калбек на вершину стека lua_pushvalue(L, 2); // stack: window callback key1 key2 key3 key4 callback int callbackRef = luaL_ref(L, LUA_REGISTRYINDEX); // stack: window callback key1 key2 key3 key4 // вызов luaL_ref() вытолкнет калбек с вершины стека // список кнопок WindowBase::HotKeys hotKeys; std::string key; for(int i = 3; 7 > i; ++i) { if(getValue(L, i, key)) { hotKeys.insert(getKeyboardButtonByName(key.c_str())); } }
// установка калбека для окна // первый параметр - указатель на функцию - нам он не нужен // второй параметр - список кнопок // третий параметр - указатель void*, который будет передан в функцию, // указатель на которую задан первым параметром. // мы в этом параметре сохраним ссылку передадим ссылку на безымянную Lua функцию. window->setHotKey(0, hotKeys, (void*)callbackRef); return 0; }
Далее в обработчике горячих кнопок окна:
bool LuaWindow::onHotKey_(const HotKeyCombo& combo) // virtual { // приводим void* к int int callbackRef = (int)combo.param_; lua_rawgeti(L_, LUA_REGISTRYINDEX, callbackRef); // stack: callback // убедимся, что это функция if(lua_isfunction(L_, -1)) { // вызов lua_pcall с обработкой ошибок callLuaFunction(L_, 0, 0); } return true; }
Хорошая и простая статья. У меня была проблема,мой биндер экспортировал классы, в нем были стандартные геттеры и сетеры для __index и __newindex соответственно, но если я добавлял в луа скрипте какую-то функцию к моему экспортируемому классу, то происходили вылеты с ошибками … кто делал тот знает какие ошибки, но прочитав статью, слава Богу понял и сделал хранение луа функции, теперь все работает так как и хотел!
Ура! 🙂