Я пытался преобразовать класс 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);
}
Результат, который я получаю:
[строка «local vec = my.Vec2 (3, 4) …»]: 3: попытка проиндексировать данные пользователя
значение (местное ‘p’)
Результат, который я ожидаю:
3
Что я должен сделать, чтобы получить ожидаемый результат?
Если вы хотите сделать это так, вы должны адаптировать свой дизайн. Прежде всего функция 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 сообщает о недопустимом чтении и записи.
Других решений пока нет …