Uploaded image for project: 'Qt'
  1. Qt
  2. QTBUG-135814

Make it easier to implement (and get guidance for) statically-linked, custom-defined QQmlEngineExtensionPlugin

    XMLWordPrintable

Details

    • iOS/tvOS/watchOS, WebAssembly

    Description

      https://doc.qt.io/qt-6/qqmlengineextensionplugin.html#details says that, for a static plugin, we need to maintain a "synthetic volatile pointer" to the qml_register_types_<URI> function to prevent it from being optimized away by the linker. However, this alone is not sufficient in the scenario where the plugin and the executable are in separate CMake projects (see the attached example).

       

      Code
      MyModule/CMakeLists.txt

      # ...
      set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_SOURCE_DIR}/../qml)
      qt6_add_qml_module(MyPlugin
          STATIC
          URI MyModule
          PLUGIN_TARGET MyPlugin
          NO_PLUGIN_OPTIONAL
          NO_GENERATE_PLUGIN_SOURCE
          CLASS_NAME MyWrongPluginClassName # <-- NOTE: qmldir classname is different from the real class name
          SOURCES mycustomqmlplugin.h
          QML_FILES MyItem.qml
      )
      

       

      MyApp/CMakeLists.txt

      # ...
      set(MY_QML_DIR ${CMAKE_SOURCE_DIR}/../qml)
      target_link_libraries(appMyApp PRIVATE
          Qt6::Quick
          ${MY_QML_DIR}/MyModule/libMyPlugin.a
      )
      

       

      MyModule/mycustomplugin.h (adapted from https://doc.qt.io/qt-6/qtquick-imageprovider-example.html )

      // ...
      
      //#define USE_UNDOCUMENTED_SYMBOL_KEEPERS <-- Uncomment this to make it work
      #ifdef USE_UNDOCUMENTED_SYMBOL_KEEPERS
      #include <QtCore/qtsymbolmacros.h>
          QT_DECLARE_EXTERN_SYMBOL_VOID(qml_register_types_MyModule)
          QT_DECLARE_EXTERN_RESOURCE(qmlcache_MyPlugin)
          QT_DECLARE_EXTERN_RESOURCE(qmake_MyModule) // Not strictly necessary?
          QT_DECLARE_EXTERN_RESOURCE(MyPlugin_raw_qml_0)
      #else
          extern void qml_register_types_MyModule();
      #endif
      
      class ImageProviderExtensionPlugin : public QQmlEngineExtensionPlugin
      {
          Q_OBJECT
          Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
      
      public:
          void initializeEngine(QQmlEngine *engine, const char *uri) override
          {
              Q_UNUSED(uri);
              engine->addImageProvider("colors", new ColorImageProvider);
      
      #ifdef USE_UNDOCUMENTED_SYMBOL_KEEPERS
              QT_KEEP_SYMBOL(qml_register_types_MyModule)
              QT_KEEP_RESOURCE(qmlcache_MyPlugin)
              QT_KEEP_RESOURCE(qmake_MyModule) // Not strictly necessary?
              QT_KEEP_RESOURCE(MyPlugin_raw_qml_0)
      #else
              volatile auto registration = &qml_register_types_MyModule;
              Q_UNUSED(registration)
      #endif
          }
      };
      

       

      MyApp/main.cpp

      // ...
      #include <QQmlEngineExtensionPlugin>
      Q_IMPORT_QML_PLUGIN(ImageProviderExtensionPlugin)
      
      int main(int argc, char *argv[]) {
      // ...
      }
      

       

      MyApp/Main.qml

      import QtQuick
      import MyModule
      
      Window {
          width: 640
          height: 480
          visible: true
      
          Column {
              MyItem {}
              Image { source: "image://colors/yellow" }
              Image { source: "image://colors/red" }
          }
      }
      

       

      Steps to test

      1. Load MyModule/CMakeLists.txt and build it
      2. Load MyApp/CMakeLists.txt, manually adjust the target_link_libraries() path if necessary, and build + run it

       

      Bad Outcomes

      • As-is, the project builds fine on Windows-MSVC/WASM/iOS but fails at runtime:
        QQmlApplicationEngine failed to load component
        qrc:/qt/qml/MyApp/Main.qml:10:9: MyItem is not a type
        

        Defining USE_UNDOCUMENTED_SYMBOL_KEEPERS and rebuilding the plugin will fix this problem.

      • The plugin fails to link when building with GCC (Windows/Linux):
        ld.exe: <ROOT>/MyApp/../qml/MyModule/libMyPlugin.a(mocs_compilation.cpp.obj):mocs_compilation.cpp:(.text$_ZN18ColorImageProviderD1Ev[_ZN18ColorImageProviderD1Ev]+0xd): undefined reference to `__imp__ZN19QQuickImageProviderD2Ev'

       

      Other observations

      • If both the plugin and the executable are part of the same CMake project, then CMake seems to do some extra magic:
        • Preserving qml_register_types_<URI> is enough. The other symbols aren't needed.
        • Q_IMPORT_QML_PLUGIN() is not needed.
        • The correct CLASS_NAME is needed (in contrast, the wrong class name in the attached example seems to be inconsequential).
      • qmake_MyModule does not seem to be needed in this example, but our auto-generated plugin code contains it. Why?

       

      Suggestions

      • Document the QT_DECLARE_EXTERN_SYMBOL_X() and QT_KEEP_X() macros
        • Bonus: Provide convenience macros that simply take the dotted-URI and target name as inputs?
      • Expand https://doc.qt.io/qt-6/qqmlengineextensionplugin.html#details to discuss the other symbols/resources and the new macros
      • Expland https://doc.qt.io/qt-6/qtquick-imageprovider-example.html to:
        • Include the bits needed for using it as a static plugin (note that some platforms, like iOS and WASM, require static plugins)
        • Include a complete executable project to consume the plugin
      • Provide a mechanism (or documentation, if it's already possible) to make the example work with GCC

      Attachments

        Issue Links

          No reviews matched the request. Check your Options in the drop-down menu of this sections header.

          Activity

            People

              qtqmlteam Qt Qml Team User
              skoh-qt Sze Howe Koh
              Votes:
              0 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

                Created:
                Updated:

                Gerrit Reviews

                  There are no open Gerrit changes