LINUX.ORG.RU

Типобезопасный QMetaObject::invokeMethod() и макросы

 ,


0

2

Собственно ищу типобезопасный QMetaObject::invokeMethod().

В гугле предлагают использовать костыль с QTimer:

QMetaObject::invokeMethod(obj, "doStuff", Qt::QueuedConnection);

// vs

QTimer::singleShot(0, obj, [obj](){ obj->doStuff(); });

Костыль конечно, плюс некоторые накладные расходы, но мне в целом подходит.

Вопрос в том, что хотелось бы генерировать код вида:

void MyObject::doStuff()
{
    // где d в отдельном потоке
    // и имена методов совпадают
    QTimer::singleShot(0, d, [this](){ d->doStuff(); });
}

То есть что-то типа:

#define INVOKE() \
    QTimer::singleShot(0, d, [this](){ d->__FUNCTION__(); });
Только работающее...

Может на шаблонах можно это провернуть?

★★★★★

Ты либо крест сними, либо трусы надень. Invoke по определению про динамическую типизацию.

Я не помню, можно ли соединять слоты со слотами, но если можно, это был бы выход.

pon4ik ★★★★★
()

В худшем случае - выход QtScript?

Проитерироваться по виртуальным слотам класса обёртки. Сделать в скрипте наследника, вызывать там слоты обёрнутого класса с любыми доп действиями. Скрипт можно будет генерировать в рантайме.

pon4ik ★★★★★
()

А ты любишь страдание, как я погляжу. Пожалуй, делай-ка на шаблонах. Делай и страдай полностью

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

И как же поступают истинные знатоки в таких случаях?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от pon4ik

Не-не-не. Пусть на шаблонах куячит. И QtScript тоже тема, это ты хорошо придумал, но лучше QWebEngine и QwebSockets. И внутрь вебэнжина наскриптовать чтоб логика была в облако вынесена

ckotinko ☆☆☆
()
Ответ на: комментарий от pon4ik

Господи какой ужас. Худший случай – делать новый сигнал, сохранять соединение при коннекте, эмитить сигнал и удалять соединение.

auto invokeConn = connect(this, &This::invokeSignal, obj, &Obj::TargetMethod, Qt::QueuedConnection);
Q_EMIT invokeSignal();
disconnect(invokeConn);
Stil ★★★★★
()
Ответ на: комментарий от Stil

Дык автору нужна прокси и что бы руками не писать. Ну или я не понял его посыл.

pon4ik ★★★★★
()
Ответ на: комментарий от RazrFalcon

А. Я то думал ты руками invoke писать не хочешь.

pon4ik ★★★★★
()
Ответ на: комментарий от ckotinko

Тем, что на этапе компиляции не известно, есть ли такой метод в дочернем классе и такие ли у него аргументы.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

открой для себя документацию по QMetaObject и узнай как там получить QMetaMethod по имени. В пятом qt у него отдаются типы из qmetatype. Но ты же выше чтения сраной документации.

ckotinko ☆☆☆
()
Ответ на: комментарий от RazrFalcon

Вот как по твоему qtшный connect не зная в compile time ничего про объект сигналы подключает? Наверно он тупит, да?

ckotinko ☆☆☆
()
Ответ на: комментарий от RazrFalcon

Кода не будет, мне за это не платят.

Я тебе последний раз говорю:

Ты пытаешься натянуть сову на глобус. Сто раз писал, что кукареки по поводу «плохого С++» идут от тех людей, кто работает над плохо/никак проработанными и/или спроектированными проектами. Поиск по коментам подтвердит. У тебя хуево спроектированная программа, раз тебе вылетели такие проблемы. Возможно не ты её придумывал. Ок, бывает. Но ты начинаешь удаляться от решения, городя шаблоны и прочую срамоту.

У тебя не должно изначально быть произвольных типов в сигналах между незнакомыми классами. С++ не Java, он не может преобразовать произвольный тип в любой другой произвольный(или зафейлиться), т.к. для преобразования или зафейливания ему надо эмитировать в выходной файл вполне конкретный код. А код должен знать о входных и выходных типах. Твоё требование «в compile time ничего не знают» нереализуемо. Но раз так получилось, то тебе придется где-то сохранить инфу о типах для рантайма. Прочитай про qRegisterMetaType, раз уж пошла такая пьянка. Это раз.

Далее. У тебя уже есть почти подходящий тебе QMetaMethod::invoke, но он не умеет приводить типы сам. Тебе придется это сделать руками.

Для осуществления вызова ты должен что-то знать о типах аргументов. Сами типы аргументов метода извлекаются в Qt5 из QMetaMethod. Я этим и еще тэгами пользуюсь для соединения реально ничего друг о друге не знающих плагинов. Ну у меня плагин могут заменить на другой и код шарить нельзя, кроме core библиотеки(и ту еще надо уговорить - ибо дебильная жадность начальства). Типы входящих аргументов ты можешь таки да - вытащить шаблонами.

Напиши функции call_method<T1,T2,T3....>(QObject* obj, QMetaMethod m ,T1 a,...) и do_really_call(QObject* obj, QMetaMethod m , int n, QMetaType::Type *types, void** ptrs).

Первая(их надо будет много - по числу агрументов) будут дрочить QMetaType и извлекать из них типы, а потом вызывать вторую. Чтоб не было code bloat.

Вторая функция(не шаблонная) должна будет:

1) узнать какие типы требуются для вызова метода. вычислить их размеры с учетом выравнивания. можешь на 16 байт равняться, чтоб точно работало. Размер типов узнаешь из QMetaType. Добавь к нему round_up_to_16(2*n*sizeof(void*)) байт для массива типов. Туда пойдут аргументы для QGenericArgument.

2) создать массив QByteArray чтоб туда всё влезло. Заполнить его сконвертированными значениями. Конвертить с помощью этой функции:

bool QMetaType::convert(const void *from, int fromTypeId, void *to, int toTypeId)
боль? да. но ты сам поставил себе такие условия. Массив аргументов для QGenericArgument тоже заполняешь по данным из QMetaType(имя аргумента + адрес который ты указываешь в to). Я надеюсь проблем с последовательным занесением в буфер данных в цикле не возникнет.

далее, самый простой способ это в любой класс, в который ты хочешь впредолить вызов без знания типов аргументов, вставить метод «вызвать это нахер» получающий этот QByteArray и QMetaMethod. Всего ему надо: n - колво аргументов, QByteArray чтоб аргументы «жили», QMetaMethod чтоб вызвать.

Далее дергаешь этот метод через invoke а он уже делает прямой вызов.

А как ты хотел? Поставил требование - ничего не известно в compile-time, получили страдание.

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

Наркоман? Простыня текста, без смысла и отношения к поставленной задаче. Хуже смайлика, ей богу.

ничего не известно в compile-time

Всё известно.

кто работает над плохо/никак проработанными и/или спроектированными проектами

Ну приведите мне пример хорошей архитектуры для асинхронного общения с дочерним потоком. Или опять сольётесь?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Всё известно.

Тем, что на этапе компиляции не известно, есть ли такой метод в дочернем классе и такие ли у него аргументы.

тогда я тебе ничем помощь не могу. шаблоны, QtScript и облако от амазона в помощь. Ну и жду душераздирающих рассказов о том, как С++ в очередной раз заставил тебя писать говнокод.

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

Поубавте свои больные фантазии. Я C++ ни с чем не сравнивал. Мне без разницы как это сделать в другом языке. Мне нужно решение на C++.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Ты что думаешь я тебя впервые вижу? Ты в каждой теме где С++ затрагивается громко страдаешь вместе с остальными обиженными С++ом. и вот такие вот посты при этом. Ты не один такой. Вон lovesan простыни писал про «убогий С++» а потом взял и выдал код от «синьора-помидора», насмешил народ. Жоббса постоянно переклинивает от С++ хотя он вроде вменяемый.

Ты даже не можешь сформулировать в чем твоя проблема: то ли неизвестно ничего то ли известно. Разберись с бардаком в своей голове для начала. Я тебе что фрейд что ли, чтоб психоанализом заниматься? Не хочешь - шаблоны и облака тебе в помощь. Кодать за тебя никто не подписывался.

Я тебе написал кратчайший путь для ситуации, когда классы реально друг о друге ничего не знают, кроме того, что у них есть метод-помошник чтоб аргументы дотащить в QByteArray. Чтоб они не удалились тупо. Тебя никто не заставляет им пользоваться. Не хочешь - рви себе жопу об шаблоны.

ckotinko ☆☆☆
()

я тебя даже пожалел и написал тебе как можно сделать

много таких:
template<T1,T2...>
bool call(QObject * obj, char * method, const T1& t1, const T2& t2...) {
    //N = number of arguments
    QMetaMethod m = obj->metaObject()->methodIndex(method);
    if (!m.isValid() || m.argumentCount() != N) {
        //shit!
        return false;
    }
    int types[N] = {
        qMetaTypeId<T1>(),
        qMetaTypeId<T2>(),
        ...
    };
    const void ** args[N] = {
        &t1,
        &t2,
        ...
    };
    return do_really_call(obj, m, N, types, data);
}
static void do_clean_up(const QByteArray& data, int n) {
     Q_ASSERT(data.size() >= n * (sizeof(void*)*3);
     void ** hptr = (void**)data.data();
     for(int i = 0; i < n; ++i) {
         int t = PTR_TO_INT(hptr[2]);
         QMetaType::destruct(t, hptr[1]);
         hptr+=3;
     }
}
static bool do_really_call(QObject * obj, const QMetaMethod& m, int n, int ** const types, const void ** data) 
{
     int hdr_size = round_up_to_16(sizeof(void*)*3*n);
     int data_size = hdr_size;
     int offsets[n];//больше 10 аргументов все равно не поддерживают
     int dtypes[n];
     const char * names[n];
     for(int i = 0; i < n; ++i) {
         int pt = m.parameterType(i);
         if (pt != types[i]) {
            if (!QMetaType::hasRegisteredConvertedFunction(types[i], pt)) {
              return false;
            }
         }
         offsets[i] = data_size;
         dtypes[i] = pt;
         names[i] = QMetaType::typeName(pt);
         data_size += align_up_to_16(QMetaType::sizeOf(pt));
     }
     
     QByteArray data;
     data.resize(data_size);
     void ** hptr = (void**)data.data();
     void *  wptr = (void*)(data.data()+hdr_size);

     int J = 0;
     bool ok = true;
     for(J = 0; J < n; ++J) {
         *hptr = names[J];
         ++hptr;
         *hptr = wptr+offsets[J];
         ++hptr;
         *hptr = INT_TO_PTR(dtypes[J]);
         ++hptr;
         if (types[J] == dtypes[J]) 
            QMetaType::construct(types[J], wptr+offsets[J], data[J]);
         else
            ok = QMetaType::convert(data[J], types[J], wptr+offsets[J], dtypes[J]);
         if (!ok) break;
     }

     if (!ok) {
         do_clean_up(data, J);
         return false;
     }
     m.invoke(obj, Qt::QueuedConnection, "really_really_call", m.index(), n, data);
     return true;
}

//Q_INVOKABLE
void MyObject::really_really_call(int mi, int n, const QByteArray& args) {
   QMetaMethod m = metaObject()->method(mi);
   void ** hptr = (void**)args.data();
   //check for errors
   switch(n) {
      ...
   case 4:
      m.invoke(this, Qt::DirectConnection, 
               QGenericArgument(hptr[0*3], hptr[0*3+1]),
               QGenericArgument(hptr[1*3], hptr[1*3+1]),
               QGenericArgument(hptr[2*3], hptr[2*3+1]),
               QGenericArgument(hptr[3*3], hptr[3*3+1]));
      break;
   }
   do_clean_up(n, args);
}
это самый универсальный способ, который правда ничего не знает о типах, кроме того, что ты скажешь Qt

ckotinko ☆☆☆
()
Последнее исправление: ckotinko (всего исправлений: 2)
Ответ на: комментарий от ckotinko

Единственный, кто тут смешит народ - это вы, со своими больными фантазиями и во всём виноватыми джавистами.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от ckotinko

Что это за ад? Я не понимаю что оно делает и зачем, но это не то, что мне нужно.

Я хочу вместо:

void MyObject::doStuff(int value)
{
    QTimer::singleShot(0, d, [=](){ d->doStuff(value); });
}
писать
void MyObject::doStuff(int value)
{
    MY_MACRO(value);
}

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

М.б. подойдет такой вариант:

template <typename ... Ts>
void safeCall(QObject *obj, const char * const methodName, Ts ... ts) {
    auto metaObject    = obj->metaObject();
    auto metaMethodIdx = metaObject->indexOfMethod(methodName);
    const auto NTs     = sizeof ... (Ts);

    if (metaMethodIdx < 0)
        throw std::runtime_error("unable to find method!");

    auto&& metaMethod = metaObject->method(metaMethodIdx);

    if (!(metaMethod.isValid() && metaMethod.parameterCount() == NTs))
        throw std::runtime_error("unable to find method!");

    int argsMetaIds[NTs] = {qMetaTypeId<typename std::decay<Ts>::type>() ...};

    for (unsigned i = 0; i < NTs; ++i)
        if (argsMetaIds[i] != metaMethod.parameterType(i))
            throw std::runtime_error("incompatible arg type!");

    metaMethod.invoke(obj, Q_ARG(Ts, ts) ...);
}

cyanide_regime
()
Ответ на: комментарий от RazrFalcon

на компилируемость не проверял, но должно быть что-то вроде:

template <typename ... Ts>
void safeCall(QObject *obj, const char * const methodName, Ts ... ts) {
    auto metaObject    = obj->metaObject();
    auto metaMethodIdx = metaObject->indexOfMethod(methodName);

    if (metaMethodIdx < 0)
        throw std::runtime_error("unable to find method!");

    auto&& metaMethod = metaObject->method(metaMethodIdx);

    metaMethod.invoke(obj, Q_ARG(Ts, ts) ...);
}

#define INVOKE(obj, method, ...) do { using __type = decltype(obj->method(__VA_ARGS__)); safeCall(obj, #method, __VA_ARGS__); } while (0); }

NegatiV
()
Ответ на: комментарий от RazrFalcon

Ну приведите мне пример хорошей архитектуры для асинхронного общения с дочерним потоком.

Запустить в дочернем потоке qt event loop и общатся с ним через сигналы и слоты? «да не бред какой-то»....

dhampire ★★★
()
Ответ на: комментарий от RazrFalcon

Код:

using __type = decltype(obj->method(__VA_ARGS__));

в макросе INVOKE проверит возможность вызова obj->method с задаными параметрами в compile-time же. Поэтому портянка проверок из safeCall и пропала.

NegatiV
()
Последнее исправление: NegatiV (всего исправлений: 1)
Ответ на: комментарий от x905

Я просто скопипастил реализацию safeCall. indexOfMethod можно заменить обходом всех метаметодов и поиском в них нужного по имени.

NegatiV
()
Ответ на: комментарий от x905

С какого перепуга? Вы предлагаете создать по сигналу и коннекту на метод и вместо таймера писать emit privateSignal*()?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Вы предлагаете создать по сигналу и коннекту на метод и вместо таймера писать emit privateSignal*()?

да, так или иначе
т.е. действовать в рамках модели сигнал/слота, иначе костыль с таймером

x905 ★★★★★
()
Ответ на: комментарий от RazrFalcon

Тем, что на этапе компиляции не известно, есть ли такой метод в дочернем классе и такие ли у него аргументы.

Мне бы аналог static_assert.

так ты разобрался, известно в рантайме или нет? давай это уже выясним.

ckotinko ☆☆☆
()
Ответ на: комментарий от ckotinko

Объясняю на пальцах:

QMetaObject::invokeMethod(obj, "doStuff", Qt::QueuedConnection);
Я только в рантайме узнаю, есть ли doStuff или нету, и то только при обращении. А мне нужно знать в компайлтайме. Для этого я использую костыль с таймером и лямбдой.

Теперь понятно?

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от cyanide_regime

Ещё руки не дошли его опробовать.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от NegatiV

Так это замена таймера? Имя метода всё равно нужно указывать самому?

А как убрать:

warning: unused type alias '__type' [-Wunused-local-typedef]

RazrFalcon ★★★★★
() автор топика
Последнее исправление: RazrFalcon (всего исправлений: 1)
Ответ на: комментарий от RazrFalcon

Хотя особо профита нету:

void QTimer::singleShot(int msec, Qt::TimerType timerType, const QObject *receiver, const char *member)
{
    if (Q_UNLIKELY(msec < 0)) {
        qWarning("QTimer::singleShot: Timers cannot have negative timeouts");
        return;
    }
    if (receiver && member) {
        if (msec == 0) {
            // special code shortpath for 0-timers
            const char* bracketPosition = strchr(member, '(');
            if (!bracketPosition || !(member[0] >= '0' && member[0] <= '2')) {
                qWarning("QTimer::singleShot: Invalid slot specification");
                return;
            }
            QByteArray methodName(member+1, bracketPosition - 1 - member); // extract method name
            QMetaObject::invokeMethod(const_cast<QObject *>(receiver), methodName.constData(), Qt::QueuedConnection);
            return;
        }
        (void) new QSingleShotTimer(msec, timerType, receiver, member);
    }
}

То есть таймер делает тоже самое. И с таймером у меня рефакторинг работает.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon

Так это замена таймера?

Да, но safeCall надо доработать чтобы он правильно искал мета-метод (просто перебором всех мета-методов в объекте).

Имя метода всё равно нужно указывать самому?

Имя метода в любом случае придется указывать - оно должно быть известно на этапе компиляции же.

cyanide_regime
()
Ответ на: комментарий от cyanide_regime

Имя метода в любом случае придется указывать - оно должно быть известно на этапе компиляции же.

В посте я писал о том, что имя метода совпадает с методом, из которого вызываем. То есть нужен аналог __FUNCTION__.

RazrFalcon ★★★★★
() автор топика
Ответ на: комментарий от RazrFalcon
#define FUNCTION(name) void name() { INVOKE(d, name); }

Видимо, больше никак. Анквоттинг __FUNCTION__ сделать невозможно.

cyanide_regime
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.