如何在C ++中将用户数据从一个Lua块传递到另一个 [英] How to pass userdata from one Lua chunk to another in C++

查看:153
本文介绍了如何在C ++中将用户数据从一个Lua块传递到另一个的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

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

解决方案

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

此方法使用三个功能:

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

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

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?

解决方案

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

这篇关于如何在C ++中将用户数据从一个Lua块传递到另一个的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆