c++qtqmetaobject

lua_newuserdata placement new on QMetaObject


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.


Solution

  • 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!