/* * Copyright (C) 2006 Justin Karneges * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * This library is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA * 02110-1301 USA * */ #include "qca_support.h" #include #include #include #include namespace QCA { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) int methodReturnType(const QMetaObject *obj, const QByteArray &method, const QList &argTypes) #else QByteArray methodReturnType( const QMetaObject *obj, const QByteArray &method, const QList argTypes) // clazy:exclude=function-args-by-ref NOLINT(performance-unnecessary-value-param) // TODO make argTypes const & when we break ABI #endif { for (int n = 0; n < obj->methodCount(); ++n) { QMetaMethod m = obj->method(n); const QByteArray sig = m.methodSignature(); int offset = sig.indexOf('('); if (offset == -1) continue; const QByteArray name = sig.mid(0, offset); if (name != method) continue; if (m.parameterTypes() != argTypes) continue; #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) return m.returnType(); #else return m.typeName(); #endif } #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) return QMetaType::UnknownType; #else return QByteArray(); #endif } bool invokeMethodWithVariants(QObject *obj, const QByteArray &method, const QVariantList &args, QVariant *ret, Qt::ConnectionType type) { // QMetaObject::invokeMethod() has a 10 argument maximum if (args.count() > 10) return false; QList argTypes; for (int n = 0; n < args.count(); ++n) { argTypes += args[n].typeName(); } // get return type #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) const auto metatype = methodReturnType(obj->metaObject(), method, argTypes); if (metatype == QMetaType::UnknownType) { return false; } #else int metatype = QMetaType::Void; const QByteArray retTypeName = methodReturnType(obj->metaObject(), method, argTypes); if (!retTypeName.isEmpty() && retTypeName != "void") { metatype = QMetaType::type(retTypeName.data()); if (metatype == QMetaType::UnknownType) // lookup failed return false; } #endif QGenericArgument arg[10]; for (int n = 0; n < args.count(); ++n) arg[n] = QGenericArgument(args[n].typeName(), args[n].constData()); QGenericReturnArgument retarg; QVariant retval; if (metatype != QMetaType::Void) { #if QT_VERSION >= QT_VERSION_CHECK(6, 0, 0) retval = QVariant(QMetaType {metatype}, (const void *)nullptr); #else retval = QVariant(metatype, (const void *)nullptr); #endif retarg = QGenericReturnArgument(retval.typeName(), retval.data()); } if (!QMetaObject::invokeMethod(obj, method.data(), type, retarg, arg[0], arg[1], arg[2], arg[3], arg[4], arg[5], arg[6], arg[7], arg[8], arg[9])) return false; if (retval.isValid() && ret) *ret = retval; return true; } //---------------------------------------------------------------------------- // SyncThread //---------------------------------------------------------------------------- class SyncThreadAgent; class SyncThread::Private : public QObject { Q_OBJECT public: SyncThread *q; QMutex m; QWaitCondition w; QEventLoop *loop; SyncThreadAgent *agent; bool last_success; QVariant last_ret; Private(SyncThread *_q) : QObject(_q) , q(_q) { loop = nullptr; agent = nullptr; } public Q_SLOTS: void agent_started(); void agent_call_ret(bool success, const QVariant &ret); }; class SyncThreadAgent : public QObject { Q_OBJECT public: SyncThreadAgent(QObject *parent = nullptr) : QObject(parent) { QMetaObject::invokeMethod(this, "started", Qt::QueuedConnection); } Q_SIGNALS: void started(); void call_ret(bool success, const QVariant &ret); public Q_SLOTS: void call_do(QObject *obj, const QByteArray &method, const QVariantList &args) { QVariant ret; bool ok = invokeMethodWithVariants(obj, method, args, &ret, Qt::DirectConnection); emit call_ret(ok, ret); } }; SyncThread::SyncThread(QObject *parent) : QThread(parent) { d = new Private(this); qRegisterMetaType("QVariant"); qRegisterMetaType("QVariantList"); } SyncThread::~SyncThread() { stop(); delete d; } void SyncThread::start() { QMutexLocker locker(&d->m); Q_ASSERT(!d->loop); QThread::start(); d->w.wait(&d->m); } void SyncThread::stop() { QMutexLocker locker(&d->m); if (!d->loop) return; QMetaObject::invokeMethod(d->loop, "quit"); d->w.wait(&d->m); wait(); } QVariant SyncThread::call(QObject *obj, const QByteArray &method, const QVariantList &args, bool *ok) { QMutexLocker locker(&d->m); bool ret; Q_UNUSED(ret); // In really ret is used. I use this hack to suppress a compiler warning // clang-format off // Otherwise the QObject* gets turned into Object * that is not normalized and is slightly slower ret = QMetaObject::invokeMethod(d->agent, "call_do", Qt::QueuedConnection, Q_ARG(QObject*, obj), Q_ARG(QByteArray, method), Q_ARG(QVariantList, args)); // clang-format on Q_ASSERT(ret); d->w.wait(&d->m); if (ok) *ok = d->last_success; QVariant v = d->last_ret; d->last_ret = QVariant(); return v; } void SyncThread::run() { d->m.lock(); d->loop = new QEventLoop; d->agent = new SyncThreadAgent; connect(d->agent, &SyncThreadAgent::started, d, &Private::agent_started, Qt::DirectConnection); connect(d->agent, &SyncThreadAgent::call_ret, d, &Private::agent_call_ret, Qt::DirectConnection); d->loop->exec(); d->m.lock(); atEnd(); delete d->agent; delete d->loop; d->agent = nullptr; d->loop = nullptr; d->w.wakeOne(); d->m.unlock(); } void SyncThread::Private::agent_started() { q->atStart(); w.wakeOne(); m.unlock(); } void SyncThread::Private::agent_call_ret(bool success, const QVariant &ret) { QMutexLocker locker(&m); last_success = success; last_ret = ret; w.wakeOne(); } } #include "syncthread.moc"