我应该如何将lua函数绑定到C ++函数?

时间:2011-04-29 08:51:52

标签: c++ lua luabind

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

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

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

3 个答案:

答案 0 :(得分:3)

我不使用lua bind但这可能有所帮助。我们的想法是在你的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);

现在你可以将自己设置为之前保存的表格来调用此函数。 HTH

答案 1 :(得分:1)

您可以使用例如

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

如果Lua函数返回一个int并取一个参数。

我很确定你可以根据需要制作尽可能多的lua_State。只需将实体的正确版本传递给call_function

答案 2 :(得分:0)

要完全实现这一点,你可能会想要在Lua的一些更深奥的位中挖掘一下。虽然值得花时间。我将展示一个非常精简的版本,我处理了这个问题。请注意,这里有很多小部分都在一起工作 - 主要是保存和调用已保存的函数以及使用c ++对象作为Lua用户数据。

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

现在您想要将您的Entity类公开给Lua。如果你不熟悉如何做到这一点,那就有一篇好文章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状态并创建Entity表并创建测试。测试将创建一个Entity并将其事件处理程序设置为传递给它的Lua函数。最后测试是否由c ++对象调用Lua函数。

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中创建非常强大且可用的Apis。祝你好运!