2

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.

Jonathan Hall
  • 75,165
  • 16
  • 143
  • 189
mrg95
  • 2,371
  • 11
  • 46
  • 89
  • Could you please provide a minimal code without pseudo-code? – Moia Aug 30 '18 at 07:52
  • My project involves Lua and wouldn't be very minimal if I made it without any pseudo-code. The preallocation is handled in that library and I don't know how that works. The issue isn't a logic one, but instead a question about placement new and QObject's. Is there any part in my question you are particularly confused about? – mrg95 Aug 30 '18 at 07:59
  • Aside from the fact that it's obviously not possible through the Qt APIs, I don't see how you could ever expect placement new to work here. You would use QMetaObject::newInstance() when you have a meta-object but don't know the exact type of the QObject to be created. How then could you know how much memory to pre-allocate for it? – Dan Milburn Aug 30 '18 at 08:31
  • I've updated my question. My project involves Lua and it handles the preallocation. I could get the class type of the `QMetaObject` and allocate the size of that type. – mrg95 Aug 30 '18 at 08:35
  • Download source code of Qt and study it. – Basile Starynkevitch Aug 30 '18 at 08:50
  • Looks like it makes a call to `static_metacall(CreateInstance, idx, param)` where idx is the constructor index and param is an array that contains the returned `QObject` and each argument. Got stuck after that... – mrg95 Aug 30 '18 at 08:58
  • @Moia I've edited my question – mrg95 Aug 30 '18 at 10:05
  • btw, The question has nothing to do with lua, you should edit it – Moia Aug 30 '18 at 10:49

3 Answers3

3

Looks like what you need is available in the QMetaType class.

So to get what you ask for you need something like this (not tested!):

int typeId = QMetaType::type(metaObject->className());
if (typeId != QMetaType::UnknownType)
{
    QMetaType meta(typeId);
    meta.construct(ptr, objectToCopy);
}
ixSci
  • 13,100
  • 5
  • 45
  • 79
  • This definitely seems right! I'll do some testing and accept this answer when I figure it out :) – mrg95 Aug 30 '18 at 09:11
  • Looks like this is not an option because `Q_DECLARE_METATYPE` will not accept `QObject` because it doesn't have a copy constructor. Which means I cannot return a typeId for my object. – mrg95 Aug 30 '18 at 09:18
  • @mrg95, I don't get it. Add to your question what exactly object you want to create. You just need to create a vanilla `QObject` object or what? – ixSci Aug 30 '18 at 09:29
  • I need to create an instance of `metaObject->classType()` which could be a class that inherits from `QObject` at the memory address pointed to by `ptr`. I'll keep messing around with `QMetaType` because this seems like the closest answer so far. – mrg95 Aug 30 '18 at 09:33
  • @mrg95, but your object "frame" (class with `Q_OBJECT`) does exist somewhere, right? So you can actually declare it in the metatype system? Because if you can't then you can't use Qt metasystem to deal with it — it just won't know how. – ixSci Aug 30 '18 at 09:36
  • Correct that exists, yet I can't declare it in the metatype system because it derives from QObject. I did not expect this question to be this difficult. I will update my question with much more sample code. Thanks for helping me out! – mrg95 Aug 30 '18 at 09:39
  • @mrg95, when you work with Qt almost everything derives from `QObject` and it is perfectly fine. You should have no problems with registering custom type with the metasystem (if you happened to read the comment I removed it contained an irrelevant example, so I killed it), – ixSci Aug 30 '18 at 09:46
  • Thanks, yeah I read it and couldn't find the relevant code. I'm updating my question now to be much more specific :) – mrg95 Aug 30 '18 at 09:49
  • I've completely rewritten my question to pertain more to my exact use case. – mrg95 Aug 30 '18 at 10:02
  • @mrg95, if you need to create just default versions of your objects (not copy of some other objects) then your best bet is to add stub copy ctors to your classes and register them as usual. If you need copies then you will have to create non-stub copy ctors which might not be possible to write correctly. `QObject` is noncopyable for a reason. Looks like I have never had such a need and confused it with non-QObject types. Sorry for the confusion. – ixSci Aug 30 '18 at 12:04
  • 1
    @mrg95, also you might consider getting rid of `QObject` if [`Q_GADGET`](http://doc.qt.io/qt-5/qobject.html#Q_GADGET) is enough for your needs. – ixSci Aug 30 '18 at 12:08
  • I like that! I will definitely make use of that :) That actually seems like it might solve my problem too for some of my classes. Thanks – mrg95 Aug 30 '18 at 12:09
1

Your Test class miss a

Q_DECLARE_METATYPE(Test*)

and a

qRegisterMetaType<Test*>("Test"); 

to have the type correctly registered in Qt Meta-system.

Note the pointer declared. You need to declare a pointer because the copy constructor is disabled for QObject.

than you can correctly call:

Test* test = new Test();
auto name = test.metaObject()->className();
auto type = QMetaType::type(name);

Test* instance = static_cast<Test*>(QMetaType::construct(type));

Edit: A complete working implementation (it actually add the qMetaTypeConstructHelper)

somevalue.h

#include <QObject>
#include <QMetaType>

class SomeValue : public QObject
{
   Q_OBJECT
   Q_PROPERTY(int value READ value WRITE setValue NOTIFY valueChanged)

public:
   explicit Q_INVOKABLE SomeValue(QObject* parent = nullptr);
   ~SomeValue() override = default;

   int value() const;

signals:
   void valueChanged(int value);

public slots:
   void setValue(int value);

private:
   int _value;
};

somevalue.cpp

#include "somevalue.h"

Q_DECLARE_METATYPE(SomeValue*)

template <>
void* qMetaTypeConstructHelper<SomeValue>(const SomeValue*)
{
    return new SomeValue();
}

static struct SomeValueMetaId
{
  SomeValueMetaId()
  {
    qRegisterMetaType<SomeValue>("SomeValue");
  }
} _SomeValueMetaId;

SomeValue::SomeValue(QObject* parent)
   : QObject(parent),
     _value{100}
{
}

int SomeValue::value() const
{
   return _value;
}


void SomeValue::setValue(int value)
{
   if (_value == value)
      return;

   _value = value;
   emit valueChanged(_value);
}

main.cpp

int main()
{
   SomeValue pint;
   auto pintName = pint.metaObject()->className();
   auto pintType = QMetaType::type("SomeValue");

   qDebug() << pintName << pintType << QMetaType::typeName(pintType);
   qDebug() << QMetaType::isRegistered(QMetaType::type("SomeValue*"));

   auto otherObj = static_cast<SomeValue*>(QMetaType::construct(pintType));
   qDebug() << pint.value();
   qDebug() << otherObj->value();
   qDebug() << "new classname" << otherObj->metaObject()->className();
   qDebug() << otherObj->metaObject()->propertyCount();

   int valueId = pint.metaObject()->indexOfProperty("value");
   auto minname = pint.metaObject()->property(valueId).name();
   qDebug() << "value name" << minname;
   auto minvariant = pint.property(minname);
   qDebug() << "value type name" << minvariant << minvariant.typeName();
   qDebug() << QMetaType::type(minvariant.typeName());

   return 0;
}
Moia
  • 2,216
  • 1
  • 12
  • 34
  • Turns out it is only constructing a pointer to the instance and not the instance. Which brings me right back to my original problem. – mrg95 Aug 30 '18 at 11:11
  • My project is exactly as what is posted in my question. No, your answer only constructs a pointer https://stackoverflow.com/questions/7872578/how-to-properly-use-qregistermetatype-on-a-class-derived-from-qobject – mrg95 Aug 30 '18 at 11:14
  • @mrg95 you are using QObject, so is better to construct by pointer. i'm well aware of that post, but it is a workaround and can bring drawbacks. I still suggest to use the pointers – Moia Aug 30 '18 at 11:17
  • Perhaps I'm not explaining it well enough... But this doesn't create the new Test instance at all. It's just not being created anywhere. Only a dangling pointer is being created by QMetaType from what I can see as the Test constructor is not being called and I can't call any methods of Test. – mrg95 Aug 30 '18 at 11:19
  • @mrg95 it does. If not you're missing something in declaration and registration of the type. I'm going to post a working example – Moia Aug 30 '18 at 11:22
  • @mrg95 are you sure are you registering correctly the class? – Moia Aug 30 '18 at 11:23
  • I have the declaration and registration added exactly as your answer states. The type comes back as 1024 which it should, everything gets created without errors, except what was being created was just a pointer and nothing else. Please do post an example – mrg95 Aug 30 '18 at 11:23
  • 1
    Moia, you can't cheat the metasystem. If you register pointers then it will allocate the pointers. It means that it won't create correct objects at memory but will allocate pointers. – ixSci Aug 30 '18 at 11:29
  • I get so many compilation errors from this, mainly *unrecognized template declaration/definition* and *'QMetaType::construct' : none of the 2 overloads could convert all the argument types* – mrg95 Aug 30 '18 at 20:19
  • which version of qt are you using? – Moia Aug 31 '18 at 06:05
0

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!

mrg95
  • 2,371
  • 11
  • 46
  • 89
  • Placement new *can* be used with `QObject` there is just no easy way to get to the placement new which is buried under the Qt metasystem abstractions. The only way (it seems) is described in my answer but it requires copy-ctor because of Qt metasystem peculiarities not because placement new needs it. But it looks like you will need copy-ctors anyway. – ixSci Aug 30 '18 at 12:14