How to pass userdata from one Lua chunk to another in C++

∥☆過路亽.° 提交于 2019-12-11 08:02:58

问题


I'm trying to get userdata from a Lua script(chunk A) in C++(through a returned variable from function in my example) and then, later pass this userdata back to Lua script(chunk B) from C++(through a function argument in my example) so the userdata can be used in chunk B as it was in the 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);
}

The Result I get :

[string "local vec2 = my.Vec2(3, 4)..."]:6: attempt to index a userdata value (local 'p')

The Result I expect :

3

Is it possible to get userdata from chunk A and then pass this to chunk B so it can be used like it was in chunk A?


回答1:


In addition to the other answer I would like to show a variant where you store the a reference to the value in the Lua registry. The advantage of this approach is that you don't have to keep the value on the stack and think about what the offset will be. See also 27.3.2 – References in “Programming in Lua”.

This approach uses three functions:

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

    Pops from the stack the uppermost value, stores it into the table at index t and returns the index the value has in that table. Hence to save a value in the registry we use

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

    Pushes onto the stack the value of the element n of the table at index (t[n] in Lua). Hence to retrieve a value at index userDataRef from the registry we use

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

    Removes the reference stored at index ref in the table at t such that the reference can be garbage collected and the index ref can be reused. Hence to remove the reference userDataRef from the registry we use

    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);
}

Maybe you want to check out the Sol2 wrapper for the Lua-C-API. It can do exactly what you want with minimal boilerplate. However, it requires 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);
}



回答2:


You're losing all information about the object's type when you get raw pointer to userdata's data and pushing it to arguments as lightuserdata. The lightuserdata even has no individual metatables.

The correct way would be to pass the Lua value as it is. Leave the original returned value on Lua stack, or copy it into other Lua container (your Lua table for temporaries, or Lua registry), then copy that value on Lua stack to pass it as an argument. That way you don't have to know anything about binding implementation. You don't even have to care if that's a userdata or any other Lua type.

Based on your code, this might look like this:

#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);
}


来源:https://stackoverflow.com/questions/52242393/how-to-pass-userdata-from-one-lua-chunk-to-another-in-c

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!