如何索引转换后的用户数据值?
How to index a converted userdata value?
我尝试使用lua_touserdata()
将类转换为 void 指针C++然后使用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 = 我的。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
. 我们将用户数据设置为此指针,并将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*
的目的是什么,反正你想保留原意。 相反,您希望有两个功能,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 报告无效的读写。
- C++如何通过用户输入删除列表元素
- 创建一个函数以在输入为负数或零时输出字符串.第一次执行用户定义的函数
- 如何使用用户输入在C++中正确填充2D数组
- C++MySQL C api用户输入行
- 使用用户定义函数的字符串反转
- 用户定义函数中的指针和输入
- 为什么即使使用-cudart-static进行编译,库用户仍然需要链接到cuda运行时
- 如何只允许用户输入正整数
- C++如何计算用户输入的数字中的偶数位数
- 通过for循环使用用户输入填充列表
- 这是我尝试让用户将值输入到数组中.然后将其隐藏为大量的星号
- 在C++中,我如何接受不同于同一行的用户输入
- 当用户超过按钮点击限制时报告
- Visual C++(VS2017)中用户定义的转换不明确
- 如果用户输入无效,如何使用字符串变量-C++重复输入命令
- C++问题:用户认为数字1-100,程序提出问题不超过6次即可得到答案。无法正确
- 当用户键入分隔符时,停止getline()输入
- 通过错误处理,在C++中可靠地获得用户十六进制输入
- RegGetValue在当前用户下找不到名称
- 函数计算用户按下按钮的频率