7

C++ で実装された基本的なイベント ハンドラーがあります。また、イベント マネージャーとやり取りするために必要な Lua インタープリターがアプリケーションに組み込まれています。最終的な目標は、イベントが発生したときに C++ と Lua の両方の関数を実行する 1 つのイベント ハンドラーを持つことができるようにすることです。

私の問題は、C++ コードに lua 関数への参照を格納する簡単な方法を思いつかないことです。c から Lua 関数を実行する方法は知っていますが ( lua_getglobalandを使用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;
}
4

3 に答える 3

17

All Lua-owned objects are garbage-collected. This includes functions. Therefore, even if you could get a reference to a Lua function, Lua would still own it and thus it would be subject to the GC whenever Lua detects that it isn't being referenced anymore.

External code cannot own a Lua reference. But external code can store that reference in a place that Lua code cannot reach (and thus cannot break): the Lua registry.

The Lua registry is a Lua table (which is at stack pseudo-index LUA_REGISTRYINDEX, so it's accessible from the stack) which Lua code cannot (directly) access. As such, it is a safe place for you to store whatever you need. Since it is a Lua table, you can manipulate it like any other Lua table (adding values, keys, etc).

However, the registry is global, and if you use other C modules, it is entirely possible that they could start stomping on each other's stuff. So it's a good idea to pick a specific registry key for each of your modules and build a table within that registry key.

Step one: when initializing your C interface code, create a table and stick it in a known key in the registry table. Just an empty table.

When the Lua code passes you a Lua function to use as a callback, load that table from the special key and stick the Lua function there. Of course, to do that, you need to give each registered function a unique key (which you store as the Lua function's void* data), which you can later use to retrieve that function.

Lua has a simple mechanism for doing this: luaL_ref. This function will register the object on the top of the stack with the table it is given. This registration process is guaranteed to return unique integer keys for each registered object (as long as you don't manually modify the table behind the system's back). luaL_unref releases a reference, allo

Since the references are integer keys, you could just do a cast from int to void* and have that be the data. I would probably use an explicit object (mallocing an int), but you can store it however you like.

Step two: when a Lua function is registered, use luaL_ref to add it to the registry table created in step 1. Store the key returned by this function in the void* parameter for the registered function.

Step three: when that function needs to be called, use the integer key you stored in the void* parameter to access the registry table created in step 1. That will give you the function, which you can then call with the usual Lua methods.

Step four: when you are unregistering the Lua function, use luaL_unref to release the function (thus you avoid leaking Lua's memory). If you malloced memory to store the integer key, free it here.

于 2012-10-10T17:41:24.483 に答える
7

関数をレジストリに保存し、関数によって提供される参照メカニズムを使用することをお勧めluaL_refしますluaL_unref

これらの関数は、Cint値を使用して値にアクセスします。たとえば、このような整数値を C++ クラス メンバに格納するのは簡単です。

于 2012-10-10T17:31:14.013 に答える