Если вы хотите сделать это так, вы должны адаптировать свой дизайн. Прежде всего, функция 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 сообщает о недопустимых операциях чтения и записи.