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

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

我得到的结果:

[string "local vec = my.Vec2(3, 4)..."]:3: attempt to index a userdata value (local '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,通过指针获取和设置 vector 的元素。我在接口(interface)文件的文字 block (%{ ... %}) 中实现了这些,但您也可以将它们移动到 header 并将此 header 包含在文字 block 中。

现在你必须让Lua知道你定义的元方法并将它们插入到一个命名的元表中,这样你就可以区分Vec2类型的指针和其他指针。因此,您必须在接口(interface)文件的 %init 部分中添加一些位,以便在解释器启动时注册元表。

因为您必须删除 getPtrvoid* 输入参数、typecheckin类型映射可以被删除。 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* 称为类型删除,因为您会丢失有关所包含数据的所有信息。因此,在恢复类型时必须小心,确保恢复的是正确的类型。转换为不相关的类型是未定义的行为,如果幸运的话,会导致程序崩溃。

您可能不希望能够像 Vec2 一样使用 void*。那么,当您无论如何都想保留原始含义时,强制转换为 void* 的目的是什么。相反,您需要两个函数:getPtrgetVec2getPtr 函数会删除类型并为您提供一个 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 = nilcollectgarbage() 放在 local p = my.getPtr(vec) 之后。它不会在我的机器上崩溃,但 Valgrind 报告无效的读取和写入。

关于c++ - 如何索引转换后的用户数据值?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52232478/

相关文章:

c++ - 图论 - 从顶点 A 开始,沿两个方向遍历所有路径,以最短路线再次到达 A

c - 错误加载模块(Lua)

Lua = 操作符作为打印

Lua 插入表到表

python - 如何将 C 库包装在 SWIG 中,它通常在 C 编译期间链接?

c++ - SWIG 和 C++ 枚举类

python - SWIG Python 结构数组

c++ - 使用 IPython 调试 C/C++ 代码

C++:使用数组创建函数

c++ - 如何在写入文件之前编码/加密结构