如何在C ++中将用户数据从一个Lua块传递到另一个

时间:2018-09-09 07:41:18

标签: c++ lua swig lua-userdata

我试图从C ++中的Lua脚本(userdata)中获取chunk A(通过示例中函数返回的变量),然后再将其返回userdata从C ++转换为Lua脚本(chunk B)(通过我的示例中的函数参数),因此userdata可以像在chunk B中那样在chunk A中使用。

MyBindings.h

class Vec2
{
public:
    Vec2():x(0), y(0){};
    Vec2(float x, float y):x(x), y(y){};
    float x, y;
};

MyBindings.i

%module my
%{
    #include "MyBindings.h"
%}

%include "MyBindings.h"

main.cpp

#include <iostream>
#include <lua.hpp>

extern "C"
{
    int luaopen_my(lua_State *L);
}

int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    luaopen_my(L);
    lua_settop(L, 0);
    /* chunk A */
    luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
                     "function setup()\n"
                       "return vec2\n"
                     "end\n");
    /* chunk B */
    luaL_dostring(L, "function test(p)\n"
                       "print(p.x)\n"
                     "end\n");
    void *userDataPtr = nullptr;

    /* call setup function */
    int top = lua_gettop(L);
    lua_getglobal(L, "setup");
    if (lua_pcall(L, 0, LUA_MULTRET, 0))
    {
        std::cout << lua_tostring(L, -1) << '\n';
        lua_pop(L, 1);
    }
    /* check the return value */
    if (lua_gettop(L) - top)
    {
        /* store userdata to a pointer */
        if (lua_isuserdata(L, -1))
            userDataPtr = lua_touserdata(L, -1);
    }
    /* check if userDataPtr is valid */
    if (userDataPtr != nullptr)
    {
        /* call test function */
        lua_getglobal(L, "test");
        lua_pushlightuserdata(L, userDataPtr); /* pass userdata as an argument */
        if (lua_pcall(L, 1, 0, 0))
        {
            std::cout << lua_tostring(L, -1) << '\n';
            lua_pop(L, 1);
        }
    }
    lua_close(L);
}

我得到的结果:

  

[字符串“本地vec2 = my.Vec2(3,4)...”]:6:尝试索引a   用户数据值(本地“ p”)

我期望的结果:

  

3

是否可以从userdata获取chunk A,然后将其传递给chunk B,以便像在chunk A中一样使用它?

2 个答案:

答案 0 :(得分:6)

当获取指向userdata数据的原始指针并将其作为lightuserdata推入参数时,您将丢失有关对象类型的所有信息。 lightuserdata甚至没有单独的元表。

正确的方法是按原样传递Lua值。将原始返回的值保留在Lua堆栈上,或将其复制到其他Lua容器(临时的Lua表或Lua注册表)中,然后在Lua堆栈上复制该值以将其作为参数传递。这样,您不必了解绑定实现的任何知识。您甚至不必担心这是用户数据还是任何其他Lua类型。

根据您的代码,可能看起来像这样:

#include <iostream>
#include <lua.hpp>

extern "C"
{
    int luaopen_my(lua_State *L);
}

int main()
{
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);

    /* chunk A */
    luaL_dostring(L, "local vec2 = {x=3, y=4}\n"
                     "function setup()\n"
                       "return vec2\n"
                     "end\n");
    /* chunk B */
    luaL_dostring(L, "function test(p)\n"
                       "print(p.x)\n"
                     "end\n");

    /* call setup function */
    int top = lua_gettop(L);
    lua_getglobal(L, "setup");

    if (lua_pcall(L, 0, LUA_MULTRET, 0))
    {
        std::cout << lua_tostring(L, -1) << '\n';
        lua_pop(L, 1);

        exit(EXIT_FAILURE); // simpy fail for demo
    }

    /* check the return value */
    if (lua_gettop(L) - top)
    {
        // the top now contains the value returned from setup()

        /* call test function */
        lua_getglobal(L, "test");

        // copy the original value as argument
        lua_pushvalue(L, -2);

        if (lua_pcall(L, 1, 0, 0))
        {
            std::cout << lua_tostring(L, -1) << '\n';
            lua_pop(L, 1);
            exit(EXIT_FAILURE);
        }

         // drop the original value
        lua_pop(L, 1);

    }else
    {
        // nothing is returned, nothing to do
    }
    lua_close(L);
}

答案 1 :(得分:2)

除了其他答案外,我还要显示一个变体,在其中存储对Lua注册表中值的引用。这种方法的优点是您不必将值保留在堆栈上,也不必考虑偏移量是多少。另请参见“在Lua中编程”中的27.3.2 – References

此方法使用三个功能:

  1. int luaL_ref (lua_State *L, int t);

    从堆栈中取出最高的值,将其存储到索引为t的表中,并返回该值在该表中具有的索引。因此,为了在注册表中保存一个值,我们使用

    userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
    
  2. int lua_rawgeti (lua_State *L, int index, lua_Integer n);

    将表的元素n的值推入堆栈,位于index(在Lua中为t[n])。因此,我们使用注册表从索引中检索索引userDataRef上的值

    lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);
    
  3. void luaL_unref (lua_State *L, int t, int ref);

    ref的表中删除存储在索引t上的引用,以便可以对该引用进行垃圾回收,并可以重用索引ref。因此,要从注册表中删除引用userDataRef,请使用

    luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);
    
#include <iostream>
#include <lua.hpp>

extern "C" {
int luaopen_my(lua_State *L);
}

int main() {
    lua_State *L = luaL_newstate();
    luaL_openlibs(L);
    luaopen_my(L);
    lua_settop(L, 0);
    /* chunk A */
    luaL_dostring(L, "local vec2 = my.Vec2(3, 4)\n"
                     "function setup()\n"
                       "return vec2\n"
                     "end\n");
    /* chunk B */
    luaL_dostring(L, "function test(p)\n"
                       "print(p.x)\n"
                     "end\n");
    int userDataRef = LUA_NOREF;

    /* call setup function */
    int top = lua_gettop(L);
    lua_getglobal(L, "setup");
    if (lua_pcall(L, 0, LUA_MULTRET, 0)) {
        std::cout << lua_tostring(L, -1) << '\n';
        lua_pop(L, 1);
    }
    /* check the return value */
    if (lua_gettop(L) - top) {
        /* store userdata to a pointer */
        userDataRef = luaL_ref(L, LUA_REGISTRYINDEX);
    }

    /* check if userDataRef is valid */
    if (userDataRef != LUA_NOREF && userDataRef != LUA_REFNIL) {
        /* call test function */
        lua_getglobal(L, "test");
        lua_rawgeti(L, LUA_REGISTRYINDEX, userDataRef);

        /* free the registry slot (if you are done) */
        luaL_unref(L, LUA_REGISTRYINDEX, userDataRef);

        if (lua_pcall(L, 1, 0, 0)) {
            std::cout << lua_tostring(L, -1) << '\n';
            lua_pop(L, 1);
        }
    }
    lua_close(L);
}

也许您想签出Lua-C-API的Sol2包装器。它可以用最少的样板完全满足您的要求。但是,它需要C ++ 14。

#include <iostream>

#define SOL_CHECK_ARGUMENTS 1
#include <sol.hpp>

extern "C" int luaopen_my(lua_State *L);

int main() {
    sol::state L;
    L.open_libraries();
    luaopen_my(L);

    /* chunk A */
    L.script("local vec2 = my.Vec2(3, 4)\n"
             "function setup()\n"
               "return vec2\n"
             "end\n");
    /* chunk B */
    L.script("function test(p)\n"
               "print(p.x)\n"
             "end\n");

    auto userDataRef = L["setup"]();
    L["test"](userDataRef);
}