Rejig native interface plumbing

The initial approach for providing public access to native
interfaces via T::nativeInteface<I>() was based on the template
not being defined, and then having explicit instantiations of
the supported types in a source file, so that the accessors
were exported and available to the user.

This worked fine for "simple" types such as QOpenGLContext
and QOffscreenSurface, but presented a problem in the context
of classes with subclasses, such as Q{Core,Gui}Application.

To ensure that a native interface for QCoreApplication was
accessible both from QCoreApplication and its subclasses,
while at the same time preventing a native interface for
QGuiApplication to be accessible for QCoreApplication, the
nativeInterface() template function had to be declared in
each subclass. Which in turn meant specializing each native
interface once for each subclass it was available in.

This quickly became tedious to manage, and the requirements
for exposing a new native interface wasn't very clear with
all these template specializations and explicit instantiations
spread around.

To improve on this situation, while also squashing a few
other birds at the same time, we change the approach to
use type erasure. The definition of T::nativeInteface<I>()
is now inline, passing on the requested interface to a per
type (T, not I) helper function, with the interface type
flattened to a std::type_info.

The type_info requested by the user is then compared to the
available types in a single per-type (T) "switch statement",
which is a lot easier to follow for someone trying to trace
the logic of how a native interface is resolved.

We can safely rely on type_info being stable between the user
application and the Qt library as a result of exporting the
type info for each native interface, by explicitly ensuring
they have a key function. This is the same mechanism that
ensures we can safely dynamic_cast these interfaces, even
across library boundaries.

The use of a free standing templated helper function instead
of a member function in the type T, is to avoid shadowing issues,
and to not pollute the class namespace of T with the helper
function.

Since we are already changing the plumbing for how a user
resolves a native interface for a type T, we take the opportunity
to add a few extra safeguards to the machinery.

First, we add a static assert in the T::nativeInteface<I>()
definition, that ensures that only compatible interfaces,
as declared by the interface themselves, are allowed.
This ensures a compile time error when an incompatible
interface is requested, which improves on the link time
errors we had prior to this patch, and also offsets the
one downside of type erasure, namely that errors are only
caught at runtime.

Secondly, each interface meant for public consumption through
T::nativeInteface<I>() is declared with a revision, which
is checked when requesting the interface. This allows us
to bump the revision when we make breaking changes to the
interface that would have otherwise been binary incompatible.
Since the user will never see this interface due to the
revision check, they will not end up calling methods that
have been removed or renamed.

One advantage of moving to a type-erased approach for the
plumbing is that we're not longer exposing the native
interface types as part of the T::nativeInteface symbols.
This means that if we ever want to rename a native interface,
the only exported symbol that the user code relies on is
the type info. Renaming is then possible by just exporting
the type info for the old interface, but leaving it empty.
Since no class in Qt implements the old native interface,
the user will just get a nullptr back, similarly to bumping
the revision of an interface.

Change-Id: Ie50d8fb536aafe2836370caacb22afbcfaf1712a
Reviewed-by: Friedemann Kleint <Friedemann.Kleint@qt.io>
This commit is contained in:
Tor Arne Vestbø 2021-05-07 13:16:36 +02:00
parent 3224c6d7d1
commit 7feeb7c34b
23 changed files with 193 additions and 47 deletions

View File

@ -50,6 +50,7 @@
# include "private/qwinregistry_p.h"
#endif // Q_OS_WIN || Q_OS_CYGWIN
#include <private/qlocale_tools_p.h>
#include "qnativeinterface.h"
#include <qmutex.h>
#include <QtCore/private/qlocking_p.h>
@ -4888,4 +4889,8 @@ bool QInternal::activateCallbacks(Callback cb, void **parameters)
declares __CFMutableString and CFMutableStringRef.
*/
namespace QNativeInterface::Private {
Q_LOGGING_CATEGORY(lcNativeInterface, "qt.nativeinterface")
}
QT_END_NAMESPACE

View File

@ -37,37 +37,104 @@
**
****************************************************************************/
#include <QtCore/qglobal.h>
#ifndef QNATIVEINTERFACE_H
#define QNATIVEINTERFACE_H
#include <QtCore/qglobal.h>
#include <QtCore/qloggingcategory.h>
#include <typeinfo>
#ifndef QT_STATIC
# define Q_NATIVE_INTERFACE_EXPORT Q_DECL_EXPORT
# define Q_NATIVE_INTERFACE_IMPORT Q_DECL_IMPORT
#else
# define Q_NATIVE_INTERFACE_EXPORT
# define Q_NATIVE_INTERFACE_IMPORT
#endif
QT_BEGIN_NAMESPACE
// Ensures that the interface's typeinfo is exported so that
// dynamic casts work reliably, and protects the destructor
// so that pointers to the interface can't be deleted.
#define QT_DECLARE_NATIVE_INTERFACE(InterfaceClass) \
protected: virtual ~InterfaceClass(); public:
// We declare a virtual non-inline function in the form
// of the destructor, making it the key function. This
// ensures that the typeinfo of the class is exported.
// By being protected, we also ensure that pointers to
// the interface can't be deleted.
#define QT_DECLARE_NATIVE_INTERFACE_3(NativeInterface, Revision, BaseType) \
protected: \
virtual ~NativeInterface(); \
struct TypeInfo { \
using baseType = BaseType; \
static constexpr int revision = Revision; \
}; \
public: \
// Revisioned interfaces only make sense when exposed through a base
// type via QT_DECLARE_NATIVE_INTERFACE_ACCESSOR, as the revision
// checks happen at that level (and not for normal dynamic_casts).
#define QT_DECLARE_NATIVE_INTERFACE_2(NativeInterface, Revision) \
static_assert(false, "Must provide a base type when specifying revision");
#define QT_DECLARE_NATIVE_INTERFACE_1(NativeInterface) \
QT_DECLARE_NATIVE_INTERFACE_3(NativeInterface, 0, void)
#define QT_DECLARE_NATIVE_INTERFACE(...) \
QT_OVERLOADED_MACRO(QT_DECLARE_NATIVE_INTERFACE, __VA_ARGS__)
namespace QNativeInterface::Private {
template <typename NativeInterface>
struct TypeInfo : private NativeInterface
{
static constexpr int revision() { return NativeInterface::TypeInfo::revision; }
template<typename BaseType>
static constexpr bool isCompatibleWith =
std::is_base_of<typename NativeInterface::TypeInfo::baseType, BaseType>::value;
};
template <typename T>
Q_NATIVE_INTERFACE_IMPORT void *resolveInterface(const T *that, const std::type_info &type, int revision);
Q_CORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcNativeInterface)
}
// Declares an accessor for the native interface
#define QT_DECLARE_NATIVE_INTERFACE_ACCESSOR \
template <typename QNativeInterface> \
QNativeInterface *nativeInterface() const;
template <typename I> \
I *nativeInterface() const \
{ \
using T = std::decay_t<decltype(*this)>; \
using namespace QNativeInterface::Private; \
static_assert(TypeInfo<I>::template isCompatibleWith<T>, \
"T::nativeInterface<I>() requires that native interface I is compatible with T"); \
\
return static_cast<I*>(resolveInterface(this, typeid(I), TypeInfo<I>::revision())); \
}
// Provides a definition for the interface destructor
#define QT_DEFINE_NATIVE_INTERFACE_2(Namespace, InterfaceClass) \
QT_PREPEND_NAMESPACE(Namespace)::InterfaceClass::~InterfaceClass() = default
// Provides a definition for the destructor, and an explicit
// template instantiation of the native interface accessor.
#define QT_DEFINE_NATIVE_INTERFACE_3(Namespace, InterfaceClass, PublicClass) \
QT_DEFINE_NATIVE_INTERFACE_2(Namespace, InterfaceClass); \
template Q_DECL_EXPORT QT_PREPEND_NAMESPACE(Namespace)::InterfaceClass *PublicClass::nativeInterface() const
#define QT_DEFINE_NATIVE_INTERFACE(...) QT_OVERLOADED_MACRO(QT_DEFINE_NATIVE_INTERFACE, QNativeInterface, __VA_ARGS__)
#define QT_DEFINE_PRIVATE_NATIVE_INTERFACE(...) QT_OVERLOADED_MACRO(QT_DEFINE_NATIVE_INTERFACE, QNativeInterface::Private, __VA_ARGS__)
#define QT_NATIVE_INTERFACE_RETURN_IF(NativeInterface, baseType) \
using QNativeInterface::Private::lcNativeInterface; \
qCDebug(lcNativeInterface, "Comparing requested type id %s with available %s", \
type.name(), typeid(NativeInterface).name()); \
if (type == typeid(NativeInterface)) { \
qCDebug(lcNativeInterface, "Match for type id %s. Comparing revisions (requested %d / available %d)", \
type.name(), revision, TypeInfo<NativeInterface>::revision()); \
if (revision == TypeInfo<NativeInterface>::revision()) { \
qCDebug(lcNativeInterface) << "Full match. Returning dynamic cast of" << baseType; \
return dynamic_cast<NativeInterface*>(baseType); \
} else { \
qCWarning(lcNativeInterface, "Native interface revision mismatch (requested %d / available %d) for interface %s", \
revision, TypeInfo<NativeInterface>::revision(), type.name()); \
return nullptr; \
} \
}
QT_END_NAMESPACE
#endif // QNATIVEINTERFACE_H

View File

@ -3227,6 +3227,13 @@ QCoreApplication::checkPermission(const QString &permission)
}
#endif // future && QT_NO_QOBJECT
template <>
Q_NATIVE_INTERFACE_EXPORT void *QNativeInterface::Private::resolveInterface(const QCoreApplication *that, const std::type_info &type, int revision)
{
Q_UNUSED(that); Q_UNUSED(type); Q_UNUSED(revision);
return nullptr;
}
QT_END_NAMESPACE
#ifndef QT_NO_QOBJECT

View File

@ -53,6 +53,7 @@
#else
#include <QtCore/qscopedpointer.h>
#endif
#include <QtCore/qnativeinterface.h>
#ifndef QT_NO_DEBUGSTREAM
#include <QtCore/qdebug.h>
#endif
@ -159,6 +160,8 @@ public:
const char * disambiguation = nullptr,
int n = -1);
QT_DECLARE_NATIVE_INTERFACE_ACCESSOR
#ifndef QT_NO_QOBJECT
#if QT_CONFIG(future)
static QFuture<QPermission::PermissionResult> requestPermission(

View File

@ -55,7 +55,7 @@ namespace QNativeInterface
#if defined(Q_OS_ANDROID) && !defined(Q_OS_ANDROID_EMBEDDED) || defined(Q_CLANG_QDOC)
struct Q_CORE_EXPORT QAndroidApplication
{
QT_DECLARE_NATIVE_INTERFACE(QAndroidApplication)
QT_DECLARE_NATIVE_INTERFACE(QAndroidApplication, 1, QCoreApplication)
static jobject context();
static bool isActivityContext();
static int sdkVersion();

View File

@ -4196,6 +4196,21 @@ QInputDeviceManager *QGuiApplicationPrivate::inputDeviceManager()
return m_inputDeviceManager;
}
template <>
Q_NATIVE_INTERFACE_EXPORT void *QNativeInterface::Private::resolveInterface(const QGuiApplication *that, const std::type_info &type, int revision)
{
using namespace QNativeInterface::Private;
auto *platformIntegration = QGuiApplicationPrivate::platformIntegration();
Q_UNUSED(platformIntegration);
#if defined(Q_OS_WIN)
QT_NATIVE_INTERFACE_RETURN_IF(QWindowsApplication, platformIntegration);
#endif
return resolveInterface<QCoreApplication>(that, type, revision);
}
#include "moc_qguiapplication.cpp"
QT_END_NAMESPACE

View File

@ -170,6 +170,8 @@ public:
bool isSavingSession() const;
#endif
QT_DECLARE_NATIVE_INTERFACE_ACCESSOR
static void sync();
Q_SIGNALS:
void fontDatabaseChanged();

View File

@ -354,7 +354,7 @@ class QWindowsMime;
struct Q_GUI_EXPORT QWindowsApplication
{
QT_DECLARE_NATIVE_INTERFACE(QWindowsApplication)
QT_DECLARE_NATIVE_INTERFACE(QWindowsApplication, 1, QGuiApplication)
enum WindowActivationBehavior {
DefaultActivateWindow,

View File

@ -135,4 +135,17 @@ QList<int> QKeyMapperPrivate::possibleKeys(QKeyEvent *e)
return extractKeyFromEvent(e);
}
template <>
Q_NATIVE_INTERFACE_EXPORT void *QNativeInterface::Private::resolveInterface(const QKeyMapper *that, const std::type_info &type, int revision)
{
Q_UNUSED(that); Q_UNUSED(type); Q_UNUSED(revision);
using namespace QNativeInterface::Private;
#if QT_CONFIG(evdev)
QT_NATIVE_INTERFACE_RETURN_IF(QEvdevKeyMapper, QGuiApplicationPrivate::platformIntegration());
#endif
return nullptr;
}
QT_END_NAMESPACE

View File

@ -106,7 +106,7 @@ namespace QNativeInterface::Private {
#if QT_CONFIG(evdev) || defined(Q_CLANG_QDOC)
struct Q_GUI_EXPORT QEvdevKeyMapper
{
QT_DECLARE_NATIVE_INTERFACE(QEvdevKeyMapper)
QT_DECLARE_NATIVE_INTERFACE(QEvdevKeyMapper, 1, QKeyMapper)
virtual void loadKeymap(const QString &filename) = 0;
virtual void switchLang() = 0;
};

View File

@ -369,4 +369,21 @@ QPlatformSurface *QOffscreenSurface::surfaceHandle() const
return d->platformOffscreenSurface;
}
using namespace QNativeInterface;
template <>
Q_NATIVE_INTERFACE_EXPORT void *QNativeInterface::Private::resolveInterface(const QOffscreenSurface *that, const std::type_info &type, int revision)
{
Q_UNUSED(that); Q_UNUSED(type); Q_UNUSED(revision);
auto *surfacePrivate = QOffscreenSurfacePrivate::get(const_cast<QOffscreenSurface*>(that));
Q_UNUSED(surfacePrivate);
#if defined(Q_OS_ANDROID)
QT_NATIVE_INTERFACE_RETURN_IF(QAndroidOffscreenSurface, surfacePrivate->platformOffscreenSurface);
#endif
return nullptr;
}
QT_END_NAMESPACE

View File

@ -55,7 +55,7 @@ namespace QNativeInterface {
#if defined(Q_OS_ANDROID) || defined(Q_CLANG_QDOC)
struct Q_GUI_EXPORT QAndroidOffscreenSurface
{
QT_DECLARE_NATIVE_INTERFACE(QAndroidOffscreenSurface)
QT_DECLARE_NATIVE_INTERFACE(QAndroidOffscreenSurface, 1, QOffscreenSurface)
static QOffscreenSurface *fromNative(ANativeWindow *nativeSurface);
virtual ANativeWindow *nativeSurface() const = 0;
};

View File

@ -1309,6 +1309,32 @@ QDebug operator<<(QDebug debug, const QOpenGLContextGroup *cg)
}
#endif // QT_NO_DEBUG_STREAM
using namespace QNativeInterface;
template <>
Q_NATIVE_INTERFACE_EXPORT void *QNativeInterface::Private::resolveInterface(const QOpenGLContext *that, const std::type_info &type, int revision)
{
Q_UNUSED(that); Q_UNUSED(type); Q_UNUSED(revision);
auto *platformContext = that->handle();
Q_UNUSED(platformContext);
#if defined(Q_OS_MACOS)
QT_NATIVE_INTERFACE_RETURN_IF(QCocoaGLContext, platformContext);
#endif
#if defined(Q_OS_WIN)
QT_NATIVE_INTERFACE_RETURN_IF(QWGLContext, platformContext);
#endif
#if QT_CONFIG(xcb_glx_plugin)
QT_NATIVE_INTERFACE_RETURN_IF(QGLXContext, platformContext);
#endif
#if QT_CONFIG(egl)
QT_NATIVE_INTERFACE_RETURN_IF(QEGLContext, platformContext);
#endif
return nullptr;
}
#include "moc_qopenglcontext.cpp"
QT_END_NAMESPACE

View File

@ -71,7 +71,7 @@ namespace QNativeInterface {
#if defined(Q_OS_MACOS) || defined(Q_CLANG_QDOC)
struct Q_GUI_EXPORT QCocoaGLContext
{
QT_DECLARE_NATIVE_INTERFACE(QCocoaGLContext)
QT_DECLARE_NATIVE_INTERFACE(QCocoaGLContext, 1, QOpenGLContext)
static QOpenGLContext *fromNative(QT_IGNORE_DEPRECATIONS(NSOpenGLContext) *context, QOpenGLContext *shareContext = nullptr);
virtual QT_IGNORE_DEPRECATIONS(NSOpenGLContext) *nativeContext() const = 0;
};
@ -80,7 +80,7 @@ struct Q_GUI_EXPORT QCocoaGLContext
#if defined(Q_OS_WIN) || defined(Q_CLANG_QDOC)
struct Q_GUI_EXPORT QWGLContext
{
QT_DECLARE_NATIVE_INTERFACE(QWGLContext)
QT_DECLARE_NATIVE_INTERFACE(QWGLContext, 1, QOpenGLContext)
static HMODULE openGLModuleHandle();
static QOpenGLContext *fromNative(HGLRC context, HWND window, QOpenGLContext *shareContext = nullptr);
virtual HGLRC nativeContext() const = 0;
@ -90,7 +90,7 @@ struct Q_GUI_EXPORT QWGLContext
#if QT_CONFIG(xcb_glx_plugin) || defined(Q_CLANG_QDOC)
struct Q_GUI_EXPORT QGLXContext
{
QT_DECLARE_NATIVE_INTERFACE(QGLXContext)
QT_DECLARE_NATIVE_INTERFACE(QGLXContext, 1, QOpenGLContext)
static QOpenGLContext *fromNative(GLXContext configBasedContext, QOpenGLContext *shareContext = nullptr);
static QOpenGLContext *fromNative(GLXContext visualBasedContext, void *visualInfo, QOpenGLContext *shareContext = nullptr);
virtual GLXContext nativeContext() const = 0;
@ -100,7 +100,7 @@ struct Q_GUI_EXPORT QGLXContext
#if QT_CONFIG(egl) || defined(Q_CLANG_QDOC)
struct Q_GUI_EXPORT QEGLContext
{
QT_DECLARE_NATIVE_INTERFACE(QEGLContext)
QT_DECLARE_NATIVE_INTERFACE(QEGLContext, 1, QOpenGLContext)
static QOpenGLContext *fromNative(EGLContext context, EGLDisplay display, QOpenGLContext *shareContext = nullptr);
virtual EGLContext nativeContext() const = 0;
};

View File

@ -83,12 +83,6 @@ private:
Q_DISABLE_COPY(QPlatformOffscreenSurface)
};
template <typename NativeInterface>
NativeInterface *QOffscreenSurface::nativeInterface() const
{
return dynamic_cast<NativeInterface*>(surfaceHandle());
}
namespace QNativeInterface::Private {
#if defined(Q_OS_ANDROID)

View File

@ -104,12 +104,6 @@ private:
Q_DISABLE_COPY(QPlatformOpenGLContext)
};
template <typename NativeInterface>
NativeInterface *QOpenGLContext::nativeInterface() const
{
return dynamic_cast<NativeInterface*>(handle());
}
namespace QNativeInterface::Private {
#if defined(Q_OS_MACOS)

View File

@ -60,7 +60,7 @@ using namespace QNativeInterface::Private;
\ingroup native-interfaces-qoffscreensurface
*/
QT_DEFINE_NATIVE_INTERFACE(QAndroidOffscreenSurface, QOffscreenSurface);
QT_DEFINE_NATIVE_INTERFACE(QAndroidOffscreenSurface);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QAndroidOffScreenIntegration);
QOffscreenSurface *QNativeInterface::QAndroidOffscreenSurface::fromNative(ANativeWindow *nativeSurface)

View File

@ -104,7 +104,7 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QCocoaMenuBar);
\return the underlying NSOpenGLContext.
*/
QT_DEFINE_NATIVE_INTERFACE(QCocoaGLContext, QOpenGLContext);
QT_DEFINE_NATIVE_INTERFACE(QCocoaGLContext);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QCocoaGLIntegration);
QOpenGLContext *QNativeInterface::QCocoaGLContext::fromNative(NSOpenGLContext *nativeContext, QOpenGLContext *shareContext)

View File

@ -96,7 +96,7 @@ using namespace QNativeInterface::Private;
\return the underlying GLXContext.
*/
QT_DEFINE_NATIVE_INTERFACE(QGLXContext, QOpenGLContext);
QT_DEFINE_NATIVE_INTERFACE(QGLXContext);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QGLXIntegration);
QOpenGLContext *QNativeInterface::QGLXContext::fromNative(GLXContext configBasedContext, QOpenGLContext *shareContext)
@ -143,7 +143,7 @@ QOpenGLContext *QNativeInterface::QGLXContext::fromNative(GLXContext visualBased
\return the underlying EGLContext.
*/
QT_DEFINE_NATIVE_INTERFACE(QEGLContext, QOpenGLContext);
QT_DEFINE_NATIVE_INTERFACE(QEGLContext);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEGLIntegration);
QOpenGLContext *QNativeInterface::QEGLContext::fromNative(EGLContext context, EGLDisplay display, QOpenGLContext *shareContext)
@ -198,11 +198,6 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QVsp2Screen);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEvdevKeyMapper);
template <>
QEvdevKeyMapper *QKeyMapper::nativeInterface<QEvdevKeyMapper>() const
{
return dynamic_cast<QEvdevKeyMapper*>(QGuiApplicationPrivate::platformIntegration());
}
#endif // QT_CONFIG(evdev)
QT_END_NAMESPACE

View File

@ -95,7 +95,7 @@ using namespace QNativeInterface::Private;
\note This function requires that the QGuiApplication instance is already created.
*/
QT_DEFINE_NATIVE_INTERFACE(QWGLContext, QOpenGLContext);
QT_DEFINE_NATIVE_INTERFACE(QWGLContext);
QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWindowsGLIntegration);
HMODULE QNativeInterface::QWGLContext::openGLModuleHandle()

View File

@ -4100,6 +4100,12 @@ QPixmap QApplicationPrivate::applyQIconStyleHelper(QIcon::Mode mode, const QPixm
return QApplication::style()->generatedIconPixmap(mode, base, &opt);
}
template <>
Q_NATIVE_INTERFACE_EXPORT void *QNativeInterface::Private::resolveInterface(const QApplication *that, const std::type_info &type, int revision)
{
return resolveInterface<QGuiApplication>(that, type, revision);
}
QT_END_NAMESPACE
#include "moc_qapplication.cpp"

View File

@ -154,6 +154,8 @@ public:
static Qt::NavigationMode navigationMode();
#endif
QT_DECLARE_NATIVE_INTERFACE_ACCESSOR
Q_SIGNALS:
void focusChanged(QWidget *old, QWidget *now);

View File

@ -444,7 +444,7 @@ SettingsDialog::SettingsDialog(QWidget *parent) : QDialog(parent)
auto layout = new QVBoxLayout(this);
TouchWindowTouchTypes touchTypes;
if (auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration()))
if (auto nativeWindowsApp = qGuiApp->nativeInterface<QWindowsApplication>())
touchTypes = nativeWindowsApp->touchWindowTouchType();
m_fineCheckBox = new QCheckBox("Fine Touch", this);
@ -468,7 +468,7 @@ void SettingsDialog::touchTypeToggled()
types.setFlag(TouchWindowTouchType::FineTouch);
if (m_palmCheckBox->isChecked())
types.setFlag(TouchWindowTouchType::WantPalmTouch);
if (auto nativeWindowsApp = dynamic_cast<QWindowsApplication *>(QGuiApplicationPrivate::platformIntegration()))
if (auto nativeWindowsApp = qGuiApp->nativeInterface<QWindowsApplication>())
nativeWindowsApp->setTouchWindowTouchType(types);
else
qWarning("Missing Interface QWindowsApplication");