I tried to convert C++ class to a void pointer using lua_touserdata() and then convert it back to C++ class using lua_pushlightuserdata().
However, I can't index variables in a class once I do the conversion.
Here's my test code:
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);
}
The Result I get :
[string "local vec = my.Vec2(3, 4)..."]:3: attempt to index a userdata value (local 'p')
The Result I expect :
3
What should I do to get the result I expect?
If you want to do it like this you have to adapt you design. First of all the function
getPtrcannot work because it is too generic. There is no way SWIG will magically guess the type and do the right thing. You will have to at least fix the type of the input.MyBindings.hAgain, are you really sure you want to do this? Because it is going to get ugly!
You need at least two metamethods,
__indexand__newindex, to get and set elements of the vector through the pointer. I implemented these in the literal block (%{ ... %}) of the interface file but you can also move them to a header and include this header in the literal block.Now you have to let Lua know about the metamethods you defined and insert them in a named metatable, so that you can distinguish pointers of type
Vec2from other pointers. Therefore you have to put a bit in the%initsection of the interface file to register the metatable when the interpreter starts up.Because you had to get rid of the
void*input argument togetPtr, thetypecheckandintypemaps can be removed. Theouttypemap has to be adapted. We have to allocate memory for a userdata which fits a pointer toVec2. We set the userdata to this pointer and slap theVec2metatable onto it. Now that was super easy wasn't it?(sarcasm)MyBindings.iLet's see whether it works.
test.luaIt might be a little easier if you used light userdata, but that has the downside that all light userdata will share the same metatable, so you can only do this for one kind of object.
Answer to the comment
Casting a pointer to a certain type to
void*is called type erasure, because you lose all information about the data contained. Therefore you have to be careful when restoring the type, that you actually restore the correct type. Casting to an unrelated type is undefined behaviour and, if you are lucky, results in a program crash.You probably don't want to be able to use
void*like aVec2. What's the purpose of casting tovoid*then, when you want to retain the original meaning anyway. Instead you want to have two functions,getPtrandgetVec2. ThegetPtrfunction erases the type and gives you avoid*object which is unusable in Lua, but is handy to pass to callback functions which accept arbitrary data asvoid*. ThegetVec2function restores the type toVec2once you are done.In the example the
getVec2function returns by reference, i.e. the returned object will be a reference to the object you calledgetPtron. That also means that if the original object is garbage collected you have an invalid pointer, which will crash your application.MyBindings.hMyBindings.itest.luaExample invocation:
To see the reference semantics fail, place
vec = nil collectgarbage()afterlocal p = my.getPtr(vec). It doesn't crash on my machine but Valgrind reports invalid reads and writes.