Сохранение ссылки на функцию lua в C

126
18

У меня есть базовый обработчик событий, реализованный на С++. У меня также есть встроенный интерпретатор Lua в моем приложении, что мне нужно взаимодействовать с Менеджером событий. Конечная цель - иметь один обработчик событий, который будет выполнять как функции С++, так и Lua при запуске события.


Моя проблема в том, что я не могу придумать простой способ хранения ссылок на функции lua в моем коде на С++. Я знаю, как выполнять функции Lua из c (используя lua_getglobal и lua_pcall), но я бы предпочел сохранить ссылку на саму функцию, так что я могу передать функцию Lua непосредственно на registerListener


Примечание Допустимо предположить, что userdata будет NULL для всех слушателей Lua.


Здесь мой код:


EventManager.h


#include <string>
#include <map>
#include <vector>

using namespace std;

typedef void (*fptr)(const void* userdata, va_list args);
typedef pair<fptr, void*> Listener;
typedef map<string, vector<Listener> > CallbackMap;

class EventManager {
private:
friend ostream& operator<<(ostream& out, const EventManager& r);

CallbackMap callbacks;
static EventManager* emInstance;
EventManager() {
callbacks = CallbackMap();
}
~EventManager() {
}
public:
static EventManager* Instance();
bool RegisterEvent(string const& name);
void RegisterListener(string const &event_name, fptr callback,
void* userdata);
bool FireEvent(string name, ...);
};

inline ostream& operator<<(ostream& out, const EventManager& em) {
return out << "EventManager: " << em.callbacks.size() << " registered event"
<< (em.callbacks.size() == 1 ? "" : "s");
}


EventManager.cpp


#include <cstdarg>
#include <iostream>
#include <string>

#include "EventManager.h"

using namespace std;

EventManager* EventManager::emInstance = NULL;

EventManager* EventManager::Instance() {
if (!emInstance) {
emInstance = new EventManager;
}

return emInstance;
}

bool EventManager::RegisterEvent(string const& name) {
if (!callbacks.count(name)) {
callbacks[name] = vector<Listener>();
return true;
}

return false;
}

void EventManager::RegisterListener(string const &event_name, fptr callback,
void* userdata) {
RegisterEvent(event_name);
callbacks[event_name].push_back(Listener(callback, userdata));
}

bool EventManager::FireEvent(string name, ...) {
map<string, vector<Listener> >::iterator event_callbacks =
callbacks.find(name);
if (event_callbacks == callbacks.end()) {
return false;
}

for (vector<Listener>::iterator cb =
event_callbacks->second.begin();
cb != event_callbacks->second.end(); ++cb) {
va_list args;
va_start(args, NULL);
(*cb->first)(cb->second, args);
va_end(args);
}

return true;
}


luaw_eventmanager.h


#pragma once
#ifndef LUAW_EVENT_H
#define LUAW_EVENT_H

#include "EventManager.h"

extern "C" {

#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>

void luaw_eventmanager_push(lua_State* L, EventManager* em);
int luaopen_weventmanager(lua_State* L);

}
#endif


luaw_eventmanager.cpp


#include <assert.h>
#include <stdio.h>

#include <sstream>
#include <iostream>

#include "luaw_eventmanager.h"

using namespace std;

static int
luaw_eventmanager_registerevent(lua_State* L)
{
int nargs = lua_gettop(L);
if (nargs != 2) {
return 0;
}

stringstream ss;
ss << luaL_checkstring(L, 2);

EventManager::Instance()->RegisterEvent(ss.str());
return 1;
}

static int
luaw_eventmanager_registerlistener(lua_State* L)
{
return 1;
}

static int
luaw_eventmanager_fireevent(lua_State* L)
{
return 1;
}

static int
luaw_eventmanager_tostring(lua_State* L)
{
stringstream ss;
ss << *EventManager::Instance();
lua_pushstring(L, &ss.str()[0]);
return 1;
}

static const struct luaL_Reg luaw_eventmanager_m [] = {
{"registerEvent", luaw_eventmanager_registerevent},
{"registerListener", luaw_eventmanager_registerlistener},
{"fireEvent", luaw_eventmanager_fireevent},
{"__tostring", luaw_eventmanager_tostring},
{NULL, NULL}
};

void
luaw_eventmanager_push(lua_State* L, EventManager* em)
{
EventManager** emUserdata = (EventManager**)lua_newuserdata(L, sizeof(EventManager*));
*emUserdata = em;
luaL_getmetatable(L, "WEAVE.mEventManager");
lua_setmetatable(L, -2);
}

int
luaopen_weventmanager(lua_State* L)
{
luaL_newmetatable(L, "WEAVE.mEventManager");
lua_pushvalue(L, -1);
lua_setfield(L, -2, "__index");
luaL_register(L, NULL, luaw_eventmanager_m);
assert(!lua_isnil(L, -1));
return 1;
}

спросил(а) 2021-01-25T14:15:22+03:00 4 месяца, 4 недели назад
1
Решение
173

Все объекты, принадлежащие Lua, собираются с мусором. Сюда входят функции. Поэтому, даже если вы можете получить ссылку на функцию Lua, Lua все равно будет владеть ею и, следовательно, она будет подчиняться GC всякий раз, когда Lua обнаруживает, что она больше не ссылается.


Внешний код не может иметь ссылку Lua. Но внешний код может хранить эту ссылку в месте, которое код Lua не может достичь (и, следовательно, не может сломаться): реестр Lua.


Lua registry - это таблица Lua (которая находится в стеке псевдоиндекса стека LUA_REGISTRYINDEX, поэтому она доступна из стека), который Lua-код не может (напрямую) доступ. Таким образом, это безопасное место для вас, чтобы хранить все, что вам нужно. Поскольку это таблица Lua, вы можете манипулировать ею, как и любая другая таблица Lua (добавление значений, ключи и т.д.).


Однако реестр является глобальным, и если вы используете другие C-модули, вполне возможно, что они могут начать топать друг на друга. Поэтому рекомендуется выбрать конкретный раздел реестра для каждого из ваших модулей и создать таблицу в этом разделе реестра.


Шаг первый: при инициализации кода интерфейса C, создайте таблицу и вставьте ее в известный ключ в таблице реестра. Просто пустой стол.


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

У Lua есть простой механизм для этого: luaL_ref. Эта функция зарегистрирует объект в верхней части стека с указанием таблицы. Этот процесс регистрации гарантированно возвращает уникальные целые ключи для каждого зарегистрированного объекта (если вы вручную не измените таблицу за спиной системы). luaL_unref освобождает ссылку, allo


Так как ссылки являются целыми ключами, вы можете просто сделать отличное от int до void* и иметь это данные. Я бы, вероятно, использовал явный объект (malloc ing a int), но вы можете сохранить его, как вам нравится.


Шаг второй:, когда функция Lua зарегистрирована, используйте luaL_ref, чтобы добавить ее в таблицу реестра, созданную на шаге 1. Храните ключ, возвращенный этой функцией в параметре void* для зарегистрированная функция.


Шаг третий:, когда эта функция должна быть вызвана, используйте ключ целого, который вы сохранили в параметре void*, для доступа к таблице реестра, созданной на шаге 1. Это даст вам функцию, которые вы можете вызвать с помощью обычных методов Lua.


Шаг четвертый:, когда вы отменяете регистрацию функции Lua, используйте luaL_unref для освобождения функции (таким образом, вы избегаете утечки памяти Lua). Если вы malloc сохранили память для хранения целочисленного ключа, free здесь.

ответил(а) 2021-01-25T14:15:22+03:00 4 месяца, 4 недели назад
117

Я бы предложил хранить ваши функции в реестре и использовать ссылочный механизм, предоставляемый функциями luaL_ref и luaL_unref.

Для доступа к значениям эти функции используют значение C int. Нетрудно сохранить такое целочисленное значение в элементе класса С++, например.

ответил(а) 2021-01-25T14:15:22+03:00 4 месяца, 4 недели назад
77

@Николас Болас предоставил хорошие инструкции, но слишком расплывчаты для новичков (включая меня).
Через пробную версию и ошибку я придумал рабочий пример:


Хранение


lua_newtable(L);  // create table for functions
int tab_idx = luaL_ref(L,LUA_REGISTRYINDEX); // store said table in pseudo-registry
lua_rawgeti(L,LUA_REGISTRYINDEX,tab_idx); // retrieve table for functions
lua_getglobal(L, "f"); // retrieve function named "f" to store
int t = luaL_ref(L,-2); // store a function in the function table
// table is two places up the current stack counter
lua_pop(L,1); // we are done with the function table, so pop it

индексирование


lua_rawgeti(L,LUA_REGISTRYINDEX,tab_idx); // retrieve function table
lua_rawgeti(L,-1,t); // retreive function

//use function
//don't forget to pop returned value and function table from the stack
lua_pop(L,2);

ответил(а) 2021-01-25T14:15:22+03:00 4 месяца, 4 недели назад
Ваш ответ
Введите минимум 50 символов
Чтобы , пожалуйста,
Выберите тему жалобы:

Другая проблема