在C中存储对lua函数的引用
Storing reference to lua function in C
我有一个用C++实现的基本事件处理程序。我的应用程序中还有一个嵌入的Lua解释器,我需要它与事件管理器交互。最终目标是能够有一个事件处理程序,当事件被触发时,它将同时执行c++和Lua函数。
我的问题是,我无法想出一种简单的方法来在C++代码中存储对lua函数的引用。我知道如何从c执行Lua函数(使用lua_getglobal
和lua_pcall
),但我更喜欢存储对函数本身的引用,这样我就可以将Lua函数直接传递给registerListener
注意假设所有Lua监听器的用户数据都是NULL
是可以接受的。
这是我的代码:
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;
}
所有Lua拥有的对象都是垃圾收集的。这包括功能。因此,即使您可以获得对Lua函数的引用,Lua仍然会拥有它,因此每当Lua检测到它不再被引用时,它就会受到GC的约束。
外部代码不能拥有Lua引用。但是外部代码可以将该引用存储在Lua代码无法访问(因此无法破坏)的地方:Lua注册表。
Lua注册表是Lua代码无法(直接)访问的Lua表(位于堆栈伪索引LUA_REGISTRYINDEX
,因此可以从堆栈访问)。因此,这是一个安全的地方,你可以储存任何你需要的东西。由于它是一个Lua表,您可以像操作任何其他Lua表一样操作它(添加值、键等)。
然而,注册表是全局的,如果你使用其他C模块,它们完全有可能开始践踏彼此的东西。因此,最好为每个模块选择一个特定的注册表项,并在该注册表项中构建一个表。
第一步:初始化C接口代码时,创建一个表并将其粘贴在注册表表中的已知键中。只是一张空桌子。
当Lua代码传递给您一个Lua函数用作回调时,从特殊键加载该表,并将Lua函数粘贴在那里。当然,要做到这一点,您需要为每个注册的函数提供一个唯一的密钥(您将其存储为Lua函数的void*
数据),稍后可以使用该密钥来检索该函数。
Lua有一个简单的机制:luaL_ref
。此函数将把堆栈顶部的对象注册到给定的表中。这个注册过程保证为每个注册的对象返回唯一的整数键(只要您不手动修改系统背后的表)。luaL_unref
发布一个参考,allo
由于引用是整数键,所以可以只执行从int
到void*
的强制转换,并将其作为数据。我可能会使用一个显式对象(malloc
或int
),但您可以随心所欲地存储它。
第二步:注册Lua函数时,使用luaL_ref
将其添加到步骤1中创建的注册表表中。将此函数返回的密钥存储在已注册函数的void*
参数中。
第三步:当需要调用该函数时,使用存储在void*
参数中的整数键访问步骤1中创建的注册表表。这将为您提供函数,然后您可以使用常用的Lua方法调用该函数。
第四步:在注销Lua函数时,使用luaL_unref
释放该函数(这样可以避免泄露Lua的内存)。如果您使用malloc
内存来存储整数密钥,请在此处使用free
。
我建议将函数存储到注册表中,并使用函数luaL_ref
和luaL_unref
提供的引用机制。
这些函数使用Cint
值来访问这些值。例如,将这样的整数值存储在C++类成员中是很容易的。
@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);
- 区分接受常量参数的函数引用/指针和与函数参数同名的非常量参数
- 类 Referention 中C++回调函数引用非静态函数
- 使用函数引用指向节点的指针删除链表中的节点?
- 解释通过从函数引用返回数组的语法
- "Class1"类"Class2"对象作为私有数据成员。如何通过"Class 2"函数引用"Class1"对象?
- 使用默认构造函数引用成员变量初始化错误
- 无法调用函数引用 c++
- 使用 decltype(this) 获取函数引用
- 我应该如何定义返回指针的函数?(引用指针与指针指针)
- 从内联函数引用文件静态变量
- Boost::将sigaction函数引用绑定到实例
- 一种比函数引用更有效的方法
- 奇怪的未定义函数引用,函数调用C++不存在
- 是否可以检测绑定成员函数引用的对象是否被删除或销毁
- 构造函数引用参数导致seg错误
- 是否可以使函数模板从函数引用中获取“decltype”
- 无法让 Lua 函数引用"self"
- 如何解析变量和函数引用(Linker & Compiler)?
- 从不同模块调用函数 - 引用错误
- 如何在主函数中连接到数据库,然后从其他函数引用它