【问题标题】:How should I bind lua functions to C++ functions?我应该如何将 lua 函数绑定到 C++ 函数?
【发布时间】:2011-08-15 08:36:53
【问题描述】:

我有一个名为 Entity 的类,它有许多函数,如 onPickup、onDrop、onUse 等。我想做的是,编写一个定义所有这些函数的脚本,并使它们可以从 C++ 函数中调用。所以在 C++ 中定义的函数只会调用它们对应的具有某些功能的 Lua 函数。

但问题是,我希望我编写的每个脚本,让程序中的每个实体都在它自己的范围内工作。

我正在使用 LuaBind,并且之前没有使用 Lua 的经验,所以在这里我有点迷茫。

【问题讨论】:

  • 等等,你是在问如何定义继承 Lua 实体的类,以便在调用 onXXX 虚拟方法时调用你的 Lua 方法?如果是 - 这是您的链接:rasterbar.com/products/luabind/docs.html#deriving-in-lua
  • @sbk,我认为这是解决问题的好方法。如果我有行为不同的对象,我将它们存储为单独的类是正确的。谢谢!
  • 您希望每个脚本在其自己的范围内运行,还是在每个函数中运行?你使用的是哪个版本的 Lua?使用 Lua 5.2,设置脚本或函数的环境非常简单。这可能会让您轻松完成您想要完成的工作。如果我不在基地,您能否提供更多详细信息,说明您为什么希望每个脚本在其自己的范围内执行?
  • 好吧,如果我想为一个对象使用一些额外的变量,我可以在派生类中定义这些变量并使用它们。因此,我为定义类的实体编写了一个脚本文件,然后在运行主脚本的全局范围内创建它的对象。所以我在类中定义的任何变量,都留在类中,不会干扰主脚本文件中定义的任何变量。

标签: c++ lua luabind


【解决方案1】:

我不使用 lua 绑定,但这可能会有所帮助。想法是在 C++ 类中注册 lua 函数,并在 C++ 类中保留对 lua 函数的引用。

为了定义一个可从 C/C++ 调用的 lua 函数,我使用 luaL_ref 在我的 C++ 对象中存储对回调函数的引用。

// a little helper function
template <typename T>
T *Lua_getUserData(lua_State *L) {
    assert(lua_isuserdata(L, 1) == 1);
    T **v = (T **) lua_touserdata(L, 1);
    assert(v != NULL);
    return *v;
}

int lua_FormRegisterMethods(lua_State *L) {
    Entity *f = Lua_getUserData<Entity>(L);
    assert(lua_istable(L, 2) == 1); // check the next parameter is a table
    lua_pushvalue(L,2); // dup the table
    f->LuaTable = luaL_ref(L, LUA_REGISTRYINDEX); // keep a reference to the table
    lua_getmetatable(L, 2); // get the metatable
    lua_pushstring(L, "OnClick"); 
    lua_rawget(L, -2); // get the OnClick Lua Function
    f->LuaMethod = luaL_ref(L, LUA_REGISTRYINDEX); // save a reference to it
    return 0;
}

然后你就可以在你的 C++ 事件中获取 lua 方法了

lua_rawgeti( LuaInstance->L, LUA_REGISTRYINDEX, LuaMethod );
assert(lua_isfunction(LuaInstance->L, -1) == 1);

现在您可以调用此函数并将 self 设置为您之前保存的表。 hth

【讨论】:

    【解决方案2】:

    你可以调用 Lua 函数,例如

    int callLuaFunction(lua_State* lua_state) {
        return luabind::call_function<int>(lua_state, "myluafunction", param1);
    }
    

    如果 Lua 函数返回一个 int 并接受 1 个参数。

    我很确定您可以制作任意数量的lua_State。只需将正确的实体传递给call_function

    【讨论】:

    • 是的,当然,或者您可以这样做:-) - 您是否建议为每个实体实例设置单独的 lua 状态?
    【解决方案3】:

    要以您可能想要的方式完全实现这一点,需要深入研究 Lua 的一些更深奥的部分。不过,这很值得。我将展示一个非常精简的版本,说明我是如何处理这个问题的。请注意,这里有很多小部件一起工作 - 主要是保存和调用保存的函数以及使用 c++ 对象作为 Lua 用户数据。

    首先我们需要一个 c++ 类,它将事件处理程序(lua 函数)存储为简单 int 的 lua 引用。我在这里使用了一个数组,但你可以使用任何有意义的东西。这里发生的主要事情是您希望能够调用 int 引用所引用的 lua 函数。

    #include "lua.h"
    #include "lauxlib.h"
    #include "lualib.h"
    #include <string>
    #include <iostream>
    #include <assert.h>
    using namespace std;
    
    enum enum_event_types {
        ON_USE, ON_DROP, ON_WHATEVER, EVENT_COUNT
    };
    
    class Entity {
      private:  
        int events[EVENT_COUNT];
      public: 
        lua_State* lua;
        void setEventHandler(int event, int ref) {
            assert(event < EVENT_COUNT);
            events[event] = ref;
        }
        void callEventHandler(int event) {
            int error;
            assert(event < EVENT_COUNT);
                // to call the function we need to get it from the registry index
            lua_rawgeti(lua, LUA_REGISTRYINDEX, events[event]);
            error = lua_pcall(lua, 0, 0, 0); // use protected call for errors
            if (error) {
                printf("error: %s", lua_tostring(lua, -1));
                lua_pop(lua, 1); 
            }
        }
    };
    

    现在您想向 Lua 公开您的 Entity 类。如果您不熟悉这是如何完成的,有一篇很好的文章 here。本质上,我们正在将 Entity.new() 返回的用户数据设置为指向指针的指针。这样 Lua 就不会垃圾收集你的对象。然后为“实体”创建一个元表,它将 保存所有暴露给 Lua 的方法。

    int L_newEntity(lua_State* L) {
        Entity **e = (Entity **)lua_newuserdata(L, sizeof(Entity *));
        *e = new Entity(); 
        (*e)->lua = L;
        lua_getglobal(L, "Entity");
        lua_setmetatable(L, -2); 
        return 1;
    }
    
    int L_setOnUse(lua_State* L) {
        Entity** e = (Entity**) lua_touserdata(L, 1);
        lua_pushvalue(L, 2);
        int ref = luaL_ref(L, LUA_REGISTRYINDEX);
        (*e)->setEventHandler(ON_USE, ref);
        return 0;
    }
    
    // this will be exposed to Lua as a table called Entity
    static const luaL_Reg L_entityMethods[] = {
        {"new", L_newEntity},{"setOnUse", L_setOnUse},{NULL, NULL}
    };
    

    现在设置 Lua 状态并创建实体表并创建测试。该测试将创建一个实体并将其事件处理程序设置为传递给它的 Lua 函数。最后测试一下 Lua 函数是否被 c++ 对象调用。

    int main() {
        Entity** e;
        int error;
        lua_State* L=lua_open();
        luaL_openlibs(L);
        luaL_register(L, "Entity", L_entityMethods); 
        lua_pushvalue(L,-1);
        lua_setfield(L, -2, "__index"); 
        lua_pop(L, 1);
    
        luaL_loadstring(L, 
        "e = Entity.new(); "
        "e:setOnUse(function()"
        "   print('Some of them want to use you')"
        "end);");
        error = lua_pcall(L, 0, 0, 0);
        if (error) {
            printf("error: %s", lua_tostring(L, -1));
            lua_pop(L, 1); /* errors must be popped from stack */
        }   
        lua_getglobal(L, "e");
        if (lua_isuserdata(L, 1)) {
            e = (Entity**) lua_touserdata(L, 1);
            (*e)->callEventHandler(ON_USE);
        }
        return 0;
    }
    

    这远未完成。首先,如果您需要设置一个事件处理程序两次,您需要先使用 luaL_unref 清除旧的引用。其次,您可能希望将有关发生的事件的一些数据传递给事件处理程序。当前的事件处理程序不获取任何数据,因此 api 的用户几乎不需要继续操作。它可能至少应该传递对调用事件的对象的引用。这可用于在 Lua 中创建非常强大且可用的 API。祝你好运!

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-01-11
      • 1970-01-01
      • 2011-07-17
      • 2016-02-06
      • 2013-08-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多