如何索引转换后的用户数据值?

时间:2018-09-08 05:56:52

标签: c++ lua swig lua-userdata typemaps

我尝试使用lua_touserdata()将C ++类转换为void指针,然后使用lua_pushlightuserdata()将其转换回C ++类。

但是,一旦完成转换,就无法在类中为变量编制索引。

这是我的测试代码:

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

我得到的结果:

  

[字符串“本地vec = my.Vec2(3,4)...”]:3:尝试索引用户数据   值(本地“ p”)

我期望的结果:

  

3

我应该怎么做才能获得预期的结果?

1 个答案:

答案 0 :(得分:2)

如果要这样做,则必须调整设计。首先,功能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*的{​​{1}}输入参数,因此可以删除getPtrtypecheck类型映射。 in类型图必须进行调整。我们必须为适合指向out的指针的userdata分配内存。我们将userdata设置为此指针,然后将Vec2元表拍打到该指针上。现在这很容易吗?(讽刺)

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*getPtrgetVec2函数将删除类型,并为您提供一个getPtr对象,该对象在Lua中不可用,但很容易传递给接受任意数据作为void*的回调函数。完成后,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()之后。它不会在我的计算机上崩溃,但是Valgrind报告无效的读写。