在C中存储对lua函数的引用

时间:2012-10-10 16:16:57

标签: c++ event-handling lua

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

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

注意可以假设所有Lua听众的userdata都是registerListener

这是我的代码:

EventManager.h

NULL

EventManager.cpp

#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");
}

luaw_eventmanager.h

#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.cpp

#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

3 个答案:

答案 0 :(得分:16)

所有Lua拥有的对象都是垃圾收集的。这包括功能。因此,即使您可以获得对Lua函数的引用,Lua仍然拥有它,因此每当Lua检测到它不再被引用时它将受GC控制。

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

The Lua registry是一个Lua表(位于堆栈伪索引LUA_REGISTRYINDEX,因此可以从堆栈访问)Lua代码无法(直接)访问。因此,它是一个安全的地方,您可以存储您需要的任何东西。由于它是一个Lua表,你可以像任何其他Lua表一样操作它(添加值,键等)。

但是,注册表是全局的,如果你使用其他C模块,它们完全有可能开始踩到彼此的东西。因此,为每个模块选择一个特定的注册表项并在该注册表项中构建一个表是个好主意。

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

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

Lua有一个简单的机制:luaL_ref。此函数将使用给定的表在堆栈顶部注册对象。此注册过程保证为每个注册对象返回唯一的整数键(只要您不手动修改系统后面的表)。 luaL_unref发布了一个引用,allo

由于引用是整数键,因此可以只执行从intvoid*的转换,并将其作为数据。我可能会使用一个显式对象(mallocint),但你可以随意存储它。

第二步:注册Lua函数时,使用luaL_ref将其添加到步骤1中创建的注册表中。将此函数返回的密钥存储在{{1已注册函数的参数。

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

第四步:当您取消注册Lua函数时,使用void*释放该函数(这样可以避免泄漏Lua的内存)。如果luaL_unref内存存储整数键,malloc就在这里。

答案 1 :(得分:6)

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

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

答案 2 :(得分: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);