7

我有一个用 C++ 实现的基本事件处理程序。我的应用程序中还有一个嵌入式 Lua 解释器,我需要与事件管理器进行交互。最终目标是能够拥有一个事件处理程序,在事件触发时执行 c++ 和 Lua 函数。

我的问题是我无法想出一种简单的方法来在我的 C++ 代码中存储对 lua 函数的引用。我知道如何从 c 执行 Lua 函数(使用lua_getglobaland lua_pcall),但我更愿意存储对函数本身的引用,以便我可以将 Lua 函数直接传递给registerListener

注意假设 userdata 将NULL用于所有 Lua 侦听器是可以接受的。

这是我的代码:

事件管理器.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");
}

事件管理器.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;
}
4

3 回答 3

17

所有 Lua 拥有的对象都被垃圾回收。这包括功能。因此,即使您可以获得对 Lua 函数的引用,Lua 仍然拥有它,因此只要 Lua 检测到它不再被引用,它就会受到 GC 的约束。

外部代码不能拥有 Lua 引用。但是外部代码可以将该引用存储在 Lua 代码无法到达(因此无法破坏)的地方:Lua 注册表。

Lua 注册表LUA_REGISTRYINDEX是Lua 代码无法(直接)访问的 Lua 表(位于堆栈伪索引处,因此可以从堆栈访问)。因此,它是您存放所需物品的安全场所。由于它是一个 Lua 表,因此您可以像任何其他 Lua 表一样操作它(添加值、键等)。

但是,注册表是全局性的,如果您使用其他 C 模块,它们完全有可能开始互相跺脚。因此,为每个模块选择一个特定的注册表项并在该注册表项中构建一个表是一个好主意。

第一步:在初始化你的 C 接口代码时,创建一个表并将其粘贴到注册表中的一个已知键中。只是一张空桌子。

当 Lua 代码将 Lua 函数传递给您以用作回调时,从特殊键加载该表并将 Lua 函数粘贴在那里。当然,要做到这一点,您需要为每个注册的函数提供一个唯一的键(您将其存储为 Lua 函数的void*数据),您以后可以使用它来检索该函数。

Lua 有一个简单的机制来做到这一点:luaL_ref. 这个函数将把栈顶的对象注册到给定的表中。此注册过程保证为每个注册对象返回唯一的整数键(只要您不手动修改系统背后的表)。luaL_unref发布参考,allo

由于引用是整数键,因此您可以intto 进行转换void*并将其作为数据。我可能会使用显式对象(mallocing an int),但您可以随心所欲地存储它。

第二步:注册Lua函数时,使用luaL_ref将其添加到第一步创建的注册表中。将这个函数返回的key存放void*在注册函数的参数中。

第三步:当需要调用该函数时,使用您存储在void*参数中的整数键来访问在步骤 1 中创建的注册表。这将为您提供该函数,然后您可以使用通常的 Lua 方法调用该函数。

第四步:在注销 Lua 函数时,使用luaL_unref释放函数(这样就避免了 Lua 的内存泄漏)。如果你malloc编辑内存来存储整数键,free它在这里。

于 2012-10-10T17:41:24.483 回答
7

我建议将您的函数存储到注册表中,并使用函数提供的引用机制luaL_refluaL_unref.

这些函数使用 Cint值来访问这些值。例如,将这样的整数值存储在 C++ 类成员中很容易。

于 2012-10-10T17:31:14.013 回答
3

@Nicolas Bolas 提供了很好的说明,但对于新手(包括我自己)来说太模糊了。通过反复试验,我提出了工作示例:

贮存

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);
于 2015-08-11T21:12:56.573 回答