Как проиндексировать преобразованное значение пользовательских данных? - PullRequest
0 голосов
/ 08 сентября 2018

Я попытался преобразовать класс C ++ в пустой указатель, используя lua_touserdata(), а затем преобразовать его обратно в класс C ++, используя lua_pushlightuserdata().

Однако я не могу индексировать переменные в классе, как только я это сделаюпреобразование.

Вот мой тестовый код:

MyBindings.h

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

void *getPtr(void *p)
{
    return p;
}

MyBindings.i

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

%typemap(typecheck) void* 
{
    $1 = lua_isuserdata(L, $input);
}
%typemap(in) void* 
{
    $1 = lua_touserdata(L, $input);
}

%typemap(out) void* 
{
    lua_pushlightuserdata(L, $1);
    ++SWIG_arg;
}

%include "MyBindings.h"

main.cpp

#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);
    const int ret = luaL_dostring(L, "local vec = my.Vec2(3, 4)\n"
                                     "local p = my.getPtr(vec)\n"
                                     "print(p.x)");
    if (ret)
    {
        std::cout << lua_tostring(L, -1) << '\n';
    }
    lua_close(L);
}

Результат, который я получаю:

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

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

3

Что я должен сделать, чтобы получить ожидаемый результат?

1 Ответ

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

Если вы хотите сделать это так, вы должны адаптировать свой дизайн. Прежде всего, функция getPtr не может работать, потому что она слишком общая. Нет никакого способа, которым SWIG будет волшебным образом угадывать тип и делать правильные вещи Вы должны будете по крайней мере исправить тип ввода.

MyBindings.h

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

void *getPtr(Vec2 &p) { return &p; }

Опять же, вы действительно уверены, что хотите это сделать? Потому что это будет ужасно!

Вам нужно как минимум два метаметода, __index и __newindex, чтобы получить и установить элементы вектора через указатель. Я реализовал их в литеральном блоке (%{ ... %}) интерфейсного файла, но вы также можете переместить их в заголовок и включить этот заголовок в литеральный блок.

Теперь вы должны сообщить Lua о метаметодах, которые вы определили, и вставить их в именованный метатабль, чтобы вы могли отличать указатели типа Vec2 от других указателей. Поэтому вам нужно добавить немного в раздел %init файла интерфейса, чтобы зарегистрировать метатаблицу при запуске интерпретатора.

Поскольку вам пришлось избавиться от входного аргумента void* для getPtr, карты типов typecheck и in можно удалить. Карта out должна быть адаптирована. Мы должны выделить память для пользовательских данных, которые соответствуют указателю на Vec2. Мы устанавливаем userdata на этот указатель и добавляем метатаблицу Vec2. Теперь это было очень легко, не так ли? (сарказм)

MyBindings.i

%module my
%{
    #define SWIG_FILE_WITH_INIT
    #include <string>
    #include "MyBindings.h"

    static int setVec2(lua_State *L) {
        Vec2 *v = *static_cast<Vec2 **>(luaL_checkudata(L, 1, "Vec2"));
        luaL_argcheck(L, v != nullptr, 1, "invalid pointer");
        std::string index = luaL_checkstring(L, 2);
        luaL_argcheck(L, index == "x" || index == "y", 2, "index out of range");
        luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
        float record = lua_tonumber(L, 3);

        if (index == "x") {
            v->x = record;
        } else if (index == "y") {
            v->y = record;
        } else {
            assert(false); // Can't happen!
        }

        return 0;
    }

    static int getVec2(lua_State *L) {
        Vec2 *v = *static_cast<Vec2 **>(luaL_checkudata(L, 1, "Vec2"));
        luaL_argcheck(L, v != nullptr, 1, "invalid pointer");
        std::string index = luaL_checkstring(L, 2);
        luaL_argcheck(L, index == "x" || index == "y", 2, "index out of range");

        if (index == "x") {
            lua_pushnumber(L, v->x);
        } else if (index == "y") {
            lua_pushnumber(L, v->y);
        } else {
            assert(false); // Can't happen!
        }

        return 1;
    }

    static const struct luaL_Reg Vec2_meta[] = {
        {"__newindex", setVec2},
        {"__index", getVec2},
        {nullptr, nullptr} // sentinel
    };
%}

%init %{
    luaL_newmetatable(L, "Vec2");
    luaL_setfuncs(L, Vec2_meta, 0);
    lua_pop(L, 1);
%}

%typemap(out) void* 
{
    void * udata = lua_newuserdata(L, sizeof(Vec2 *));
    *static_cast<void **>(udata) = $1;
    luaL_getmetatable(L, "Vec2");
    lua_setmetatable(L, -2);
    ++SWIG_arg;
}

%include "MyBindings.h"

Посмотрим, работает ли оно.

test.lua

local my = require("my")
local vec = my.Vec2(3, 4)
local p = my.getPtr(vec)
print(p.x, p.y)
p.x = 1.0
p.y = 2.0
print(p.x, p.y)
print(vec.x, vec.y)
$ swig -lua -c++ MyBindings.i
$ clang++ -Wall -Wextra -Wpedantic -I/usr/include/lua5.3 -fPIC -shared MyBindings_wrap.cxx -o my.so -llua5.3
$ lua5.3 test.lua
3.0 4.0
1.0 2.0
1.0 2.0

Может быть немного проще, если вы использовали легкие пользовательские данные, но у этого есть недостаток, что все легкие пользовательские данные будут совместно использовать одну и ту же метаданную, поэтому вы можете сделать это только для одного типа объекта.


Ответ на комментарий

Преобразование указателя на определенный тип в void* называется стиранием типа, поскольку вы теряете всю информацию о содержащихся данных. Поэтому вы должны быть осторожны при восстановлении типа, чтобы вы действительно восстановили правильный тип. Приведение к несвязанному типу является неопределенным поведением и, если вам повезет, приводит к сбою программы.

Вы, вероятно, не хотите использовать void* как Vec2. Какова цель приведения к void* тогда, когда вы все равно хотите сохранить первоначальное значение. Вместо этого вы хотите иметь две функции, getPtr и getVec2. Функция getPtr стирает тип и дает вам объект void*, который нельзя использовать в Lua, но который удобен для передачи в функции обратного вызова, которые принимают произвольные данные как void*. Функция getVec2 восстанавливает тип до Vec2, как только вы закончите.

В этом примере функция getVec2 возвращает по ссылке, то есть возвращаемый объект будет ссылкой на объект, который вы назвали getPtr. Это также означает, что если исходный объект является сборщиком мусора, у вас есть неверный указатель, который приведет к сбою вашего приложения.

MyBindings.h

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

void *getPtr(Vec2 &p) { return &p; }
Vec2 &getVec2(void *p) { return *static_cast<Vec2 *>(p); }

MyBindings.i

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

%include "MyBindings.h"

test.lua

local my = require("my")
local vec = my.Vec2(3, 4)
-- Erase the type of vec to pass it around
local p = my.getPtr(vec)
-- Then restore the type using getVec2
local v = my.getVec2(p)
-- Take care! v is a reference to vec
v.x = 1.0
v.y = 2.0
print(v.x, v.y)
print(vec.x, vec.y)

Пример вызова:

$ swig -lua -c++ MyBindings.i
$ clang++ -Wall -Wextra -Wpedantic -I/usr/include/lua5.3 -fPIC -shared MyBindings_wrap.cxx -o my.so -llua5.3
$ lua5.3 test.lua 
1.0 2.0
1.0 2.0

Чтобы увидеть, что ссылочная семантика не работает, поставьте vec = nil collectgarbage() после local p = my.getPtr(vec). Он не падает на моей машине, но Valgrind сообщает о недопустимых операциях чтения и записи.

...