【问题标题】:How to pass userdata from one Lua chunk to another in C++如何在 C++ 中将用户数据从一个 Lua 块传递到另一个块
【发布时间】:2019-02-13 23:30:12
【问题描述】:

我正在尝试从 C++ 中的 Lua 脚本(chunk A)中获取 userdata(通过我的示例中的函数返回的变量),然后,稍后将这个 userdata 传递回 Lua 脚本(@987654326 @) 来自 C++(在我的示例中通过函数参数),因此 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);
}

我得到的结果:

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

我期望的结果:

3

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

【问题讨论】:

  • 哦,这是一个难题,因为 SWIG 引入了自己的抽象层来处理用户数据。如果这是在 SWIG 生成的库中,您可以使用 SWIG's Lua-C API,但在外部则非常棘手。
  • 查看更新后的答案。
  • Lua DLL 可以动态加载。也许我不明白 swig,但 DLL 应该是一个单独的构建目标。我假设luaopen_my(L); 仅用于测试目的。 lua_getglobal(L, "require"); lua_pushlstring(L, file, strlen(file)); 后跟 lua_pcall(L, 1, 0, 0); 就是您需要做的所有事情。见这里github.com/tilkinsc/LuaConsole/blob/…

标签: c++ lua swig lua-userdata


【解决方案1】:

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

正确的方法是按原样传递 Lua 值。将原始返回值保留在 Lua 堆栈上,或将其复制到其他 Lua 容器(您的 Lua 临时表或 Lua 注册表),然后将该值复制到 Lua 堆栈上以将其作为参数传递。这样您就不必了解绑定实现的任何内容。您甚至不必关心这是 userdata 还是任何其他 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);
}

【讨论】:

    【解决方案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);

      index(Lua 中的t[n])表的元素n 的值压入堆栈。因此,要从我们使用的注册表中检索索引 userDataRef 处的值

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

      删除存储在t 表中索引ref 处的引用,以便可以对引用进行垃圾收集并且可以重用索引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);
    }
    

    【讨论】:

    • 非常感谢!这帮助我解决了我的问题。
    猜你喜欢
    • 2019-06-14
    • 1970-01-01
    • 2012-07-04
    • 2021-01-18
    • 1970-01-01
    • 1970-01-01
    • 2021-11-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多