I'm trying to integrate Lua with Qt's QMetaObject system. I have a class that derives from QObject
that I bind to Lua based on the class name using QObject::staticMetaObject
.
main.h:
#ifndef MAIN_H
#define MAIN_H
class Test : public QObject
{
Q_OBJECT
public:
Q_INVOKABLE Test(QObject *parent = 0) : QObject(parent){}
~Test(){}
};
Q_DECLARE_METATYPE(Test*)
#endif
main.cpp
#include <QCoreApplication>
#include <QDebug>
#include "main.h"
#include "lua_src/lua.hpp" //Lua include
int CreateUserData(lua_State *L)
{
const QMetaObject *metaObject = (const QMetaObject*)lua_touserdata(L, lua_upvalueindex(1));
//PROBLEM AREA
int typeId = QMetaType::type(metaObject->className());
if(typeId != QMetaType::UnknownType)//typeId is always unknown
{
QMetaType meta(typeId);
void *ptr = lua_newuserdata(L, meta.sizeOf());
meta.construct(ptr);
}
//PROBLEM AREA
lua_newtable(L);
lua_setuservalue(L, 1);
return 1;
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
QString luaScript("local test = Test.new()");
lua_State *L = luaL_newstate();
//bind Test class to lua
lua_newtable(L);
lua_pushvalue(L, -1);
lua_setglobal(L, "Test");
lua_pushvalue(L, -1);
lua_pushlightuserdata(L, (void*)&Test::staticMetaObject);
lua_pushcclosure(L, CreateUserData, 1);
lua_setfield(L, -2, "new");
//start script
luaL_dostring(L, luaScript.toStdString().c_str());
lua_close(L);
}
The issue is that lua will allocate memory for userdata but will not construct the object it represents. All documentation says to use placement new to construct your object at the ptr
of the lua userdata, however QMetaObject
doesn't allow placement new out of the box.
I've included suggestions from ixSci about using QMetaType
to construct the object at ptr
. However, typeId
always comes back as unknown.
I have found a solution for my situation.
After reviewing the answers from Moia and ixSci, I have realized that I was correct in my statement that placement new cannot be used on a QObject
because QObject
has it's copy constructor private (and shouldn't be made public).
A more efficient method is to (obviously) store pointers to the QObject*
created from metaObject->newInstance()
. That's right, pointers to pointers.
New code is as follows:
const QMetaObject *metaObject = (const QMetaObject*)lua_touserdata(L, lua_upvalueindex(1));
uintptr_t *ptr = (uintptr_t*)lua_newuserdata(L, sizeof(QObject*));
QObject *object = metaObject->newInstance();
*ptr = reinterpret_cast<uintptr_t>(object);
And for retrieving:
uintptr_t *objectPointer = (uintptr_t*)lua_touserdata(L, -1);
QObject *object = static_cast<QObject*>((void*)*objectPointer);
The upside is that lua can allocate fixed size for any class object since it is always 4 (just a pointer). This means I don't have to do any type checking.
The obvious downside to this is that I can't do any type checking since it will always just be pointers. Also, all interactions with these types inside the Lua script will behave as pointers. All copies will be pointer copies instead of QObject
copies. As a result, I will have to implement my own copy constructor for my QObject's
depending on my specific use case.
Thanks for all your assistance!