QDBusAbstractInterface: make call() and asyncCall() variadic templates

... and use a variant of the QString::multiArg() trick to pass the
variable number of arguments into an out-of-line function.

The idea of this patch is to make the lowly asyncCall() and call()
functions the principal interface for DBus calls again.

Currently, it is more efficient to build up a QList<QVariant> and pass
that to the *WithArgumentList() methods. With more efficient, I mean
that the equivalent calls with QList expand to less client code,
probably because of the need to construct eight QVariant instances,
destroy them again, and then take the hit for a function call with so
many arguments, which can all but be efficient.

Consequently, when porting the NM bearer plugin to use call() instead
of callWithArgumentList(), text size increased by ~3KiB on my machine.

So, looking for a way to avoid the overhead of so many default
arguments, while at the same time allowing to pass even more arguments
than the predefined eight, I considered the QString::arg() method, but
discarded it because it does not scale to arbitrarily
(ie. SEP-limited) many arguments.

Variadic templates scale to SEP-limited many arguments, but they are
templates and deduce the exact type, so constraining the template to
only QVariants would have broken all users of the API which pass
non-QVariants and expect an implicit con- version to resolve the call.

So I decided to make a virtue of necessity, accept all argument types
and convert them to QVariant when constructing the
QString::multiArg()-inspired QVariant array.

To bring this patch to its consequential end would require to pass the
arguments as arrays instead of QLists, but to get a feeling of how
much impact this new API has, I backed the new implementation by
converting the array to a QList and calling the *WithArgumentList()
methods.

The result is, if I may say so, satisfying. Said bearer plugin drops
from +3KiB (baseline: callWithArgumentList()) to -5KiB (2.0% and 3.2%,
resp.); the (unmodified) connman bearer plugin, which already used
call(), dropped by almost 6KiB (2.5%).

While GCC can deal with the zero-sized array in a nullary call to the
variadic templates, MSVC cannot, so an extra overload provides an
alternative for zero arguments.

[ChangeLog][QtDBus][QDBusAbstractInterface] The call() and asyncCall()
methods now accept more than eight QVariant arguments.

Change-Id: Ic85faca40949f9bb8c08756be3bfe945c9cdd952
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2016-09-01 20:37:12 +02:00
parent 6bcfe04535
commit 08103d3a52
2 changed files with 159 additions and 48 deletions

View File

@ -691,18 +691,20 @@ void QDBusAbstractInterface::internalPropSet(const char *propname, const QVarian
}
/*!
Calls the method \a method on this interface and passes the parameters to this function to the
method.
\fn QDBusAbstractInterface::call(const QString &message)
\internal
*/
/*!
\fn QDBusAbstractInterface::call(const QString &message, Args&&...args)
Calls the method \a method on this interface and passes \a args to the method.
All \a args must be convertible to QVariant.
The parameters to \c call are passed on to the remote function via D-Bus as input
arguments. Output arguments are returned in the QDBusMessage reply. If the reply is an error
reply, lastError() will also be set to the contents of the error message.
This function can be used with up to 8 parameters, passed in arguments \a arg1, \a arg2,
\a arg3, \a arg4, \a arg5, \a arg6, \a arg7 and \a arg8. If you need more than 8
parameters or if you have a variable number of parameters to be passed, use
callWithArgumentList().
It can be used the following way:
\snippet code/src_qdbus_qdbusabstractinterface.cpp 0
@ -710,6 +712,19 @@ void QDBusAbstractInterface::internalPropSet(const char *propname, const QVarian
This example illustrates function calling with 0, 1 and 2 parameters and illustrates different
parameter types passed in each (the first call to \c "ProcessWorkUnicode" will contain one
Unicode string, the second call to \c "ProcessWork" will contain one string and one byte array).
\note Before Qt 5.14, this function accepted a maximum of just eight (8) arguments.
\sa callWithArgumentList()
*/
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
/*!
\internal
This function exists for binary compatibility with Qt versions < 5.14.
Programs recompiled against Qt 5.14 will use the variadic template function
instead.
*/
QDBusMessage QDBusAbstractInterface::call(const QString &method, const QVariant &arg1,
const QVariant &arg2,
@ -722,27 +737,44 @@ QDBusMessage QDBusAbstractInterface::call(const QString &method, const QVariant
{
return call(QDBus::AutoDetect, method, arg1, arg2, arg3, arg4, arg5, arg6, arg7, arg8);
}
#endif
/*!
\fn QDBusAbstractInterface::call(QDBus::CallMode mode, const QString &message)
\internal
*/
/*!
\fn QDBusAbstractInterface::call(QDBus::CallMode mode, const QString &message, Args&&...args)
\overload
Calls the method \a method on this interface and passes the
parameters to this function to the method. If \a mode is \c
NoWaitForReply, then this function will return immediately after
Calls the method \a method on this interface and passes \a args to the method.
All \a args must be convertible to QVariant.
If \a mode is \c NoWaitForReply, then this function will return immediately after
placing the call, without waiting for a reply from the remote
method. Otherwise, \a mode indicates whether this function should
activate the Qt Event Loop while waiting for the reply to arrive.
This function can be used with up to 8 parameters, passed in arguments \a arg1, \a arg2,
\a arg3, \a arg4, \a arg5, \a arg6, \a arg7 and \a arg8. If you need more than 8
parameters or if you have a variable number of parameters to be passed, use
callWithArgumentList().
If this function reenters the Qt event loop in order to wait for the
reply, it will exclude user input. During the wait, it may deliver
signals and other method calls to your application. Therefore, it
must be prepared to handle a reentrancy whenever a call is placed
with call().
\note Before Qt 5.14, this function accepted a maximum of just eight (8) arguments.
\sa callWithArgumentList()
*/
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
/*!
\internal
This function exists for binary compatibility with Qt versions < 5.14.
Programs recompiled against Qt 5.14 will use the variadic template function
instead.
*/
QDBusMessage QDBusAbstractInterface::call(QDBus::CallMode mode, const QString &method,
const QVariant &arg1,
@ -787,22 +819,23 @@ QDBusMessage QDBusAbstractInterface::call(QDBus::CallMode mode, const QString &m
return callWithArgumentList(mode, method, argList);
}
#endif // Qt 5
/*!
\since 4.5
Calls the method \a method on this interface and passes the parameters to this function to the
method.
\fn QDBusAbstractInterface::asyncCall(const QString &message)
\internal
*/
/*!
\fn QDBusAbstractInterface::asyncCall(const QString &message, Args&&...args)
Calls the method \a method on this interface and passes \a args to the method.
All \a args must be convertible to QVariant.
The parameters to \c call are passed on to the remote function via D-Bus as input
arguments. The returned QDBusPendingCall object can be used to find out information about
the reply.
This function can be used with up to 8 parameters, passed in arguments \a arg1, \a arg2,
\a arg3, \a arg4, \a arg5, \a arg6, \a arg7 and \a arg8. If you need more than 8
parameters or if you have a variable number of parameters to be passed, use
asyncCallWithArgumentList().
It can be used the following way:
\snippet code/src_qdbus_qdbusabstractinterface.cpp 1
@ -810,6 +843,19 @@ QDBusMessage QDBusAbstractInterface::call(QDBus::CallMode mode, const QString &m
This example illustrates function calling with 0, 1 and 2 parameters and illustrates different
parameter types passed in each (the first call to \c "ProcessWorkUnicode" will contain one
Unicode string, the second call to \c "ProcessWork" will contain one string and one byte array).
\note Before Qt 5.14, this function accepted a maximum of just eight (8) arguments.
\sa asyncCallWithArgumentList()
*/
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
/*!
\internal
This function exists for binary compatibility with Qt versions < 5.14.
Programs recompiled against Qt 5.14 will use the variadic template function
instead.
*/
QDBusPendingCall QDBusAbstractInterface::asyncCall(const QString &method, const QVariant &arg1,
const QVariant &arg2,
@ -853,6 +899,7 @@ QDBusPendingCall QDBusAbstractInterface::asyncCall(const QString &method, const
return asyncCallWithArgumentList(method, argList);
}
#endif // Qt 5
/*!
\internal
@ -865,6 +912,24 @@ QDBusMessage QDBusAbstractInterface::internalConstCall(QDBus::CallMode mode,
return const_cast<QDBusAbstractInterface*>(this)->callWithArgumentList(mode, method, args);
}
QDBusMessage QDBusAbstractInterface::doCall(QDBus::CallMode mode, const QString &method, const QVariant *args, size_t numArgs)
{
QList<QVariant> list;
list.reserve(int(numArgs));
for (size_t i = 0; i < numArgs; ++i)
list.append(args[i]);
return callWithArgumentList(mode, method, list);
}
QDBusPendingCall QDBusAbstractInterface::doAsyncCall(const QString &method, const QVariant *args, size_t numArgs)
{
QList<QVariant> list;
list.reserve(int(numArgs));
for (size_t i = 0; i < numArgs; ++i)
list.append(args[i]);
return asyncCallWithArgumentList(method, list);
}
QT_END_NAMESPACE
#endif // QT_NO_DBUS

View File

@ -49,6 +49,7 @@
#include <QtDBus/qdbusmessage.h>
#include <QtDBus/qdbusextratypes.h>
#include <QtDBus/qdbusconnection.h>
#include <QtDBus/qdbuspendingcall.h>
#ifdef interface
#undef interface
@ -98,26 +99,52 @@ public:
void setTimeout(int timeout);
int timeout() const;
QDBusMessage call(const QString &method)
{
return doCall(QDBus::AutoDetect, method, nullptr, 0);
}
template <typename...Args>
QDBusMessage call(const QString &method, Args &&...args)
{
const QVariant variants[] = { QVariant(std::forward<Args>(args))... };
return doCall(QDBus::AutoDetect, method, variants, sizeof...(args));
}
QDBusMessage call(QDBus::CallMode mode, const QString &method)
{
return doCall(mode, method, nullptr, 0);
}
template <typename...Args>
QDBusMessage call(QDBus::CallMode mode, const QString &method, Args &&...args)
{
const QVariant variants[] = { QVariant(std::forward<Args>(args))... };
return doCall(mode, method, variants, sizeof...(args));
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QDBusMessage call(const QString &method,
const QVariant &arg1 = QVariant(),
const QVariant &arg2 = QVariant(),
const QVariant &arg3 = QVariant(),
const QVariant &arg4 = QVariant(),
const QVariant &arg5 = QVariant(),
const QVariant &arg6 = QVariant(),
const QVariant &arg7 = QVariant(),
const QVariant &arg8 = QVariant());
const QVariant &arg1,
const QVariant &arg2,
const QVariant &arg3,
const QVariant &arg4,
const QVariant &arg5,
const QVariant &arg6,
const QVariant &arg7,
const QVariant &arg8);
QDBusMessage call(QDBus::CallMode mode,
const QString &method,
const QVariant &arg1 = QVariant(),
const QVariant &arg2 = QVariant(),
const QVariant &arg3 = QVariant(),
const QVariant &arg4 = QVariant(),
const QVariant &arg5 = QVariant(),
const QVariant &arg6 = QVariant(),
const QVariant &arg7 = QVariant(),
const QVariant &arg8 = QVariant());
const QVariant &arg1,
const QVariant &arg2,
const QVariant &arg3,
const QVariant &arg4,
const QVariant &arg5,
const QVariant &arg6,
const QVariant &arg7,
const QVariant &arg8);
#endif // Qt 5
QDBusMessage callWithArgumentList(QDBus::CallMode mode,
const QString &method,
@ -130,15 +157,30 @@ public:
const QList<QVariant> &args,
QObject *receiver, const char *member);
QDBusPendingCall asyncCall(const QString &method)
{
return doAsyncCall(method, nullptr, 0);
}
template <typename...Args>
QDBusPendingCall asyncCall(const QString &method, Args&&...args)
{
const QVariant variants[] = { QVariant(std::forward<Args>(args))... };
return doAsyncCall(method, variants, sizeof...(args));
}
#if QT_VERSION < QT_VERSION_CHECK(6, 0, 0)
QDBusPendingCall asyncCall(const QString &method,
const QVariant &arg1 = QVariant(),
const QVariant &arg2 = QVariant(),
const QVariant &arg3 = QVariant(),
const QVariant &arg4 = QVariant(),
const QVariant &arg5 = QVariant(),
const QVariant &arg6 = QVariant(),
const QVariant &arg7 = QVariant(),
const QVariant &arg8 = QVariant());
const QVariant &arg1,
const QVariant &arg2,
const QVariant &arg3,
const QVariant &arg4,
const QVariant &arg5,
const QVariant &arg6,
const QVariant &arg7,
const QVariant &arg8);
#endif // Qt 5
QDBusPendingCall asyncCallWithArgumentList(const QString &method,
const QList<QVariant> &args);
@ -155,6 +197,10 @@ protected:
const QString &method,
const QList<QVariant> &args = QList<QVariant>()) const;
private:
QDBusMessage doCall(QDBus::CallMode mode, const QString &method, const QVariant *args, size_t numArgs);
QDBusPendingCall doAsyncCall(const QString &method, const QVariant *args, size_t numArgs);
private:
Q_DECLARE_PRIVATE(QDBusAbstractInterface)
Q_PRIVATE_SLOT(d_func(), void _q_serviceOwnerChanged(QString,QString,QString))