lua_newuserdata QMetaObject 上的新位置

lua_newuserdata placement new on QMetaObject

我正在尝试将 Lua 与 Qt 的 QMetaObject 系统集成。我有一个 class 派生自 QObject,我使用 QObject::staticMetaObject.

基于 class 名称绑定到 Lua

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);
}

问题是 lua 会为用户数据分配内存,但不会构造它所代表的对象。所有文档都说使用 placement new 在 lua 用户数据的 ptr 处构建对象,但是 QMetaObject 不允许开箱即用的 placement new。

我已经包含了 ixSci 关于使用 QMetaTypeptr 处构建对象的建议。但是,typeId 总是返回未知。

看起来您需要的东西在 QMetaType class 中可用。

所以要得到你想要的东西,你需要这样的东西(未经测试!):

int typeId = QMetaType::type(metaObject->className());
if (typeId != QMetaType::UnknownType)
{
    QMetaType meta(typeId);
    meta.construct(ptr, objectToCopy);
}

你的 Test class 错过了一个

Q_DECLARE_METATYPE(Test*)

还有一个

qRegisterMetaType<Test*>("Test"); 

在 Qt 元系统中正确注册类型。

注意声明的指针。您需要声明一个指针,因为 QObject.

的复制构造函数被禁用

比你能正确调用:

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

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

编辑:一个完整​​的工作实现(它实际上添加了 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 和 ixSci 的答案后,我意识到我的陈述是正确的,即 placement new 不能用于 QObject,因为 QObject 的复制构造函数是私有的(并且不应该 public).

一种更有效的方法是(显然)存储指向从 metaObject->newInstance() 创建的 QObject* 的指针。没错,指针指向指针。

新代码如下:

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);

并用于检索:

uintptr_t *objectPointer = (uintptr_t*)lua_touserdata(L, -1);
QObject *object = static_cast<QObject*>((void*)*objectPointer);

优点是 lua 可以为任何 class 对象分配固定大小,因为它总是 4(只是一个指针)。这意味着我不必进行任何类型检查。

这样做的明显缺点是我无法进行任何类型检查,因为它始终只是指针。此外,在 Lua 脚本中与这些类型的所有交互都将表现为指针。所有副本都将是指针副本,而不是 QObject 副本。因此,我将不得不根据我的具体用例为我的 QObject's 实现我自己的复制构造函数。

感谢您的帮助!