Как передать userdata из одного фрагмента Lua в другой в C ++ - PullRequest
0 голосов
/ 09 сентября 2018

Я пытаюсь получить userdata из сценария Lua (chunk A) в C ++ (через возвращаемую переменную из функции в моем примере), а затем позже передать это userdata обратно в сценарий Lua (chunk B) из 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);
}

Результат Iget:

[строка "local vec2 = my.Vec2 (3, 4) ..."]: 6: попытка проиндексировать значение пользовательских данных (локальное 'p')

Результат, который я ожидаю:

3

Можно ли получить userdata из chunk A изатем передайте это chunk B, чтобы его можно было использовать как chunk A?

Ответы [ 2 ]

0 голосов
/ 10 сентября 2018

В дополнение к другому ответу я хотел бы показать вариант, в котором вы храните ссылку на значение в реестре Lua. Преимущество этого подхода заключается в том, что вам не нужно сохранять значение в стеке и думать о том, каким будет смещение. См. Также 27.3.2 - Ссылки в разделе «Программирование на Lua».

Этот подход использует три функции:

  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 (t[n] в Lua). Следовательно, чтобы извлечь значение по индексу 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);
}

Может быть, вы хотите проверить оболочку Sol2 для Lua-C-API. Он может делать именно то, что вы хотите с минимальным шаблоном. Однако для этого требуется 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);
}
0 голосов
/ 09 сентября 2018

Вы теряете всю информацию о типе объекта, когда получаете необработанный указатель на данные пользовательских данных и выдвигаете его в качестве аргументов в виде 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);
}
...