Skip to content
  • Categories
  • Recent
  • Tags
  • Popular
  • Users
  • Groups
  • Search
  • Get Qt Extensions
  • Unsolved
Collapse
Brand Logo
  1. Home
  2. Qt Development
  3. QML and Qt Quick
  4. QML files in C++ libraries
Forum Updated to NodeBB v4.3 + New Features

QML files in C++ libraries

Scheduled Pinned Locked Moved Solved QML and Qt Quick
16 Posts 3 Posters 1.9k Views 2 Watching
  • Oldest to Newest
  • Newest to Oldest
  • Most Votes
Reply
  • Reply as topic
Log in to reply
This topic has been deleted. Only users with topic management privileges can see it.
  • M mooglus

    Background: To solve a particular problem it would be convenient for me to be able to take past versions of my QML application and compile them into libraries. This way, a single app bundle could contain multiple versions of the app, which can then be selected at run time.

    I've created two C++ libraries, LibA and LibB, that contain QML files with common names, but different graphical content.
    Both libraries contain a start method that loads a QML engine, see below...

    int LibA::Start(QGuiApplication *GuiApp)
    {
      qDebug() << "LibA::" << __FUNCTION__;
      QQmlApplicationEngine engine;
      engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
      if (engine.rootObjects().isEmpty())
        return -1;
    
      return GuiApp->exec();
    }
    

    I link to these from a main project and call them with the following test code

    int main(int argc, char *argv[])
    {
      QGuiApplication app(argc, argv);
    
      bool bLibA = true;
      if(bLibA)
      {
        LibA AppA();
        return AppA.Start(&app);
      }
      else
      {
        LibB AppB();
        return AppB.Start(&app);
      }
    }
    

    I'm finding that the correct library is getting called for each state of bLibA. However, if any QML files have common names between the libraries, the QML from the first library declared in the main project's pro is always used, regardless of which library is being called.

    Does any one know exactly why this would be the case?
    Is there a way to force the QML from the libraries to be separated and allow common names?

    Hopefully I've explained clearly, any help would be most appreciated, thanks!

    raven-worxR Offline
    raven-worxR Offline
    raven-worx
    Moderators
    wrote on last edited by raven-worx
    #3

    @mooglus
    i assume you mean both libs do have qrc resources compiled in?

    Are you linking against the libs? Or are they (custom) plugins?

    If they are plugins you should load the needed plugin in the corresponding if-else case.

    If you link against them, you can simply add a different prefix path to each qrc of the lib.
    If you are using qmlRegisterType() and similiar you should register each libs types into an individual import url

    I recommend the plugin approch.

    --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
    If you have a question please use the forum so others can benefit from the solution in the future

    M 1 Reply Last reply
    3
    • raven-worxR raven-worx

      @mooglus
      i assume you mean both libs do have qrc resources compiled in?

      Are you linking against the libs? Or are they (custom) plugins?

      If they are plugins you should load the needed plugin in the corresponding if-else case.

      If you link against them, you can simply add a different prefix path to each qrc of the lib.
      If you are using qmlRegisterType() and similiar you should register each libs types into an individual import url

      I recommend the plugin approch.

      M Offline
      M Offline
      mooglus
      wrote on last edited by
      #4

      @raven-worx
      Yes, both libs have their own qrc resources compiled in and I am linking against the libs. If I were compiling past versions of my app as libraries, there would be many fundamental C++/ QML changes, but most of the file names would be common.

      I did try adding a prefix to the qrc in one library, but the problem of common QML file names persisted.

      Do you know how the qml is brought into the application in my case?

      If I have...
      LibA: with main_a.qml and Thing.qml
      LibB: with main_b.qml and Thing.qml

      With Thing.qml as a child of window in main_b.qml, I then call AppB.Start(&app) and I get main_b.qml with LibA's Thing.qml as a child view

      So, is it feasible to take an entire C++/ QML application and compile it as a plugin?
      You think that would be a better solution?

      raven-worxR 1 Reply Last reply
      0
      • M mooglus

        @raven-worx
        Yes, both libs have their own qrc resources compiled in and I am linking against the libs. If I were compiling past versions of my app as libraries, there would be many fundamental C++/ QML changes, but most of the file names would be common.

        I did try adding a prefix to the qrc in one library, but the problem of common QML file names persisted.

        Do you know how the qml is brought into the application in my case?

        If I have...
        LibA: with main_a.qml and Thing.qml
        LibB: with main_b.qml and Thing.qml

        With Thing.qml as a child of window in main_b.qml, I then call AppB.Start(&app) and I get main_b.qml with LibA's Thing.qml as a child view

        So, is it feasible to take an entire C++/ QML application and compile it as a plugin?
        You think that would be a better solution?

        raven-worxR Offline
        raven-worxR Offline
        raven-worx
        Moderators
        wrote on last edited by
        #5

        @mooglus
        you can also register qml files into a separate import url:

        // C++
        qmlRegisterType(QUrl("qrc:/LibA/path/to/MyType.qml"),"LibA",1,0,"MyTypeName");
        
        // QML
        import LibA 1.0
        MyTypeName {
        
        }
        

        --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
        If you have a question please use the forum so others can benefit from the solution in the future

        M 1 Reply Last reply
        2
        • raven-worxR raven-worx

          @mooglus
          you can also register qml files into a separate import url:

          // C++
          qmlRegisterType(QUrl("qrc:/LibA/path/to/MyType.qml"),"LibA",1,0,"MyTypeName");
          
          // QML
          import LibA 1.0
          MyTypeName {
          
          }
          
          M Offline
          M Offline
          mooglus
          wrote on last edited by
          #6

          Thanks @raven-worx!

          Before, I added a path prefix to the qml file paths and updated the qrc accordingly.
          However, I'd neglected to call that path in engine.load(). Now with explicit path set in LibB, the Libraries load their respective sets of QML files.

          engine.load(QUrl(QStringLiteral("qrc:/qml_b/main.qml")));
          

          It's less convenient than I'd hoped from a source tree perspective, i.e. branches would need a path prefix that differs from trunk. Though, I might be able to copy them from a common location and automate this with a script.

          That said, this seems to work and is a small price to pay for the massive benefit of supporting old versions of our app. Which should at least make our customers happier.

          raven-worxR 1 Reply Last reply
          0
          • M mooglus

            Thanks @raven-worx!

            Before, I added a path prefix to the qml file paths and updated the qrc accordingly.
            However, I'd neglected to call that path in engine.load(). Now with explicit path set in LibB, the Libraries load their respective sets of QML files.

            engine.load(QUrl(QStringLiteral("qrc:/qml_b/main.qml")));
            

            It's less convenient than I'd hoped from a source tree perspective, i.e. branches would need a path prefix that differs from trunk. Though, I might be able to copy them from a common location and automate this with a script.

            That said, this seems to work and is a small price to pay for the massive benefit of supporting old versions of our app. Which should at least make our customers happier.

            raven-worxR Offline
            raven-worxR Offline
            raven-worx
            Moderators
            wrote on last edited by
            #7

            @mooglus
            such cases where you want to support old and new versions (just like the Qt QML imports do) are addressed by QML module versioning.

            --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
            If you have a question please use the forum so others can benefit from the solution in the future

            1 Reply Last reply
            1
            • M Offline
              M Offline
              mooglus
              wrote on last edited by mooglus
              #8

              Hi @raven-worx

              It's been a year, but now I have a mandate to try and implement this idea.

              I've managed to build versions A & B of our app into two separate libraries.

              LibA and LibB contain many C++ classes with the same names, this doesn't seem to be a problem except in one case. I'm finding that QML instantiations of C++ classes registered using qmlRegisterType always use the class from LibA.

              For example...

              Different versions of MyCppClass are defined in LibA and LibB and I call the following code within LibB...

              qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );

              Then later on, LibB's QML Instantiates MyCppClass, but uses LibA's version of MyCppClass.

              I can work around this issue by adding a namespace to affected classes, but wondered if there was a better solution?

              Also, a general C++ question I can't quite find the answer to: Is it safe to have common C++ class names within 2 libraries in the same project? Should each Lib always call its own version of a class? Should I be adding namespaces to all C++ classes?

              You said "I recommend the plugin approach". Would you mind elaborating? What is the advantage over linking to a libraries? Does it avoid the pitfalls I've encountered.

              Thanks for reading!

              raven-worxR 1 Reply Last reply
              0
              • M mooglus

                Hi @raven-worx

                It's been a year, but now I have a mandate to try and implement this idea.

                I've managed to build versions A & B of our app into two separate libraries.

                LibA and LibB contain many C++ classes with the same names, this doesn't seem to be a problem except in one case. I'm finding that QML instantiations of C++ classes registered using qmlRegisterType always use the class from LibA.

                For example...

                Different versions of MyCppClass are defined in LibA and LibB and I call the following code within LibB...

                qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );

                Then later on, LibB's QML Instantiates MyCppClass, but uses LibA's version of MyCppClass.

                I can work around this issue by adding a namespace to affected classes, but wondered if there was a better solution?

                Also, a general C++ question I can't quite find the answer to: Is it safe to have common C++ class names within 2 libraries in the same project? Should each Lib always call its own version of a class? Should I be adding namespaces to all C++ classes?

                You said "I recommend the plugin approach". Would you mind elaborating? What is the advantage over linking to a libraries? Does it avoid the pitfalls I've encountered.

                Thanks for reading!

                raven-worxR Offline
                raven-worxR Offline
                raven-worx
                Moderators
                wrote on last edited by
                #9

                @mooglus said in QML files in C++ libraries:

                LibA and LibB contain many C++ classes with the same names

                are their headers guarded with the same macros?
                if not you either register them into the same import url?
                or do you import both urls in the same QML files?

                --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                If you have a question please use the forum so others can benefit from the solution in the future

                1 Reply Last reply
                0
                • M Offline
                  M Offline
                  mooglus
                  wrote on last edited by
                  #10

                  @raven-worx said in QML files in C++ libraries:

                  are their headers guarded with the same macros?

                  The C++ classes internal to the libraries with common names have the same guards in each library.
                  Library headers, A and B have guards: LIBAPPA_H and LIBAPPB_H respectively.

                  @raven-worx said in QML files in C++ libraries:

                  if not you either register them into the same import url?
                  or do you import both urls in the same QML files?

                  Both libraries register the C++ classes in their Start() functions, into uri: MyQml
                  qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );
                  Each QML file imports MyQml

                  I found changing the name or version of the MyQml import had no affect on my problem

                  To be absolutely clear I've tried to summarise the code below, apologies for long post!

                  LibA

                  // main.qml

                  import QtQuick.Window 2.10
                  import MyQml 1.0
                  
                  Window {
                    visible: true; width: 640; height: 480
                    title: qsTr("Lib App A: Hello World!")
                  
                    MyCppClass {
                    }
                  }
                  

                  // MyCppClass.h

                  #ifndef MyCppClass_h
                  #define MyCppClass_h
                  #include <QQuickItem>
                  class MyCppClass : public QQuickItem
                  {
                    Q_OBJECT
                  public:
                    MyCppClass(QQuickItem *Parent = 0);
                  };
                  #endif // MyCppClass_h
                  

                  // MyCppClass.cpp

                  #include "MyCppClass.h"
                  MyCppClass::MyCppClass(QQuickItem *Parent) : QQuickItem(Parent) { qDebug() << __FUNCTION__ << "from LibA"; }
                  

                  // libappa_global.h

                  #ifndef LIBAPPA_GLOBAL_H
                  #define LIBAPPA_GLOBAL_H
                  
                  #include <QtCore/qglobal.h>
                  #include <QGuiApplication>
                  
                  #if defined(LIBAPPA_LIBRARY)
                  #  define LIBAPPASHARED_EXPORT Q_DECL_EXPORT
                  #else
                  #  define LIBAPPASHARED_EXPORT Q_DECL_IMPORT
                  #endif
                  
                  #endif // LIBAPPA_GLOBAL_H
                  

                  // LibAppA.h

                  #ifndef LIBAPPA_H
                  #define LIBAPPA_H
                  
                  #include "libappa_global.h"
                  
                  class LIBAPPASHARED_EXPORT LibAppA
                  {
                  public:
                    int Start(QGuiApplication *GuiApp);
                  };
                  #endif // LIBAPPA_H
                  

                  // LibAppA.cpp

                  #include "LibAppA.h"
                  #include "MyCppClass.h"
                  
                  #include <QQmlApplicationEngine>
                  #include <QDebug>
                  
                  int LibAppA::Start(QGuiApplication *GuiApp) {
                    qDebug() << "LibAppA::" << __FUNCTION__;
                    QQmlApplicationEngine engine;
                  
                    qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );
                  
                    engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
                    if (engine.rootObjects().isEmpty())
                      return -1;
                  
                    return GuiApp->exec();
                  }
                  

                  Main Application
                  // main.cpp

                  #include <QGuiApplication>
                  #include <QQmlApplicationEngine>
                  
                  #include "LibAppA.h"
                  #include "LibAppB.h"
                  
                  int main(int argc, char *argv[])
                  {
                    QGuiApplication app(argc, argv);
                    bool bLibA = false;
                    if(bLibA) {
                      LibAppA AppA;
                      return AppA.Start(&app);
                    } else {
                      LibAppB AppB;
                      return AppB.Start(&app);
                    }
                  }
                  

                  Lib B is essentially the same as Lib A except for: code below in Start(), library headers, qDebug() calls to identify which class is being called, and the QML Window has a "B" title.

                  engine.load(QUrl(QStringLiteral("qrc:/qml_b/main.qml")));
                  

                  Thanks again for reading.

                  raven-worxR 1 Reply Last reply
                  0
                  • M mooglus

                    @raven-worx said in QML files in C++ libraries:

                    are their headers guarded with the same macros?

                    The C++ classes internal to the libraries with common names have the same guards in each library.
                    Library headers, A and B have guards: LIBAPPA_H and LIBAPPB_H respectively.

                    @raven-worx said in QML files in C++ libraries:

                    if not you either register them into the same import url?
                    or do you import both urls in the same QML files?

                    Both libraries register the C++ classes in their Start() functions, into uri: MyQml
                    qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );
                    Each QML file imports MyQml

                    I found changing the name or version of the MyQml import had no affect on my problem

                    To be absolutely clear I've tried to summarise the code below, apologies for long post!

                    LibA

                    // main.qml

                    import QtQuick.Window 2.10
                    import MyQml 1.0
                    
                    Window {
                      visible: true; width: 640; height: 480
                      title: qsTr("Lib App A: Hello World!")
                    
                      MyCppClass {
                      }
                    }
                    

                    // MyCppClass.h

                    #ifndef MyCppClass_h
                    #define MyCppClass_h
                    #include <QQuickItem>
                    class MyCppClass : public QQuickItem
                    {
                      Q_OBJECT
                    public:
                      MyCppClass(QQuickItem *Parent = 0);
                    };
                    #endif // MyCppClass_h
                    

                    // MyCppClass.cpp

                    #include "MyCppClass.h"
                    MyCppClass::MyCppClass(QQuickItem *Parent) : QQuickItem(Parent) { qDebug() << __FUNCTION__ << "from LibA"; }
                    

                    // libappa_global.h

                    #ifndef LIBAPPA_GLOBAL_H
                    #define LIBAPPA_GLOBAL_H
                    
                    #include <QtCore/qglobal.h>
                    #include <QGuiApplication>
                    
                    #if defined(LIBAPPA_LIBRARY)
                    #  define LIBAPPASHARED_EXPORT Q_DECL_EXPORT
                    #else
                    #  define LIBAPPASHARED_EXPORT Q_DECL_IMPORT
                    #endif
                    
                    #endif // LIBAPPA_GLOBAL_H
                    

                    // LibAppA.h

                    #ifndef LIBAPPA_H
                    #define LIBAPPA_H
                    
                    #include "libappa_global.h"
                    
                    class LIBAPPASHARED_EXPORT LibAppA
                    {
                    public:
                      int Start(QGuiApplication *GuiApp);
                    };
                    #endif // LIBAPPA_H
                    

                    // LibAppA.cpp

                    #include "LibAppA.h"
                    #include "MyCppClass.h"
                    
                    #include <QQmlApplicationEngine>
                    #include <QDebug>
                    
                    int LibAppA::Start(QGuiApplication *GuiApp) {
                      qDebug() << "LibAppA::" << __FUNCTION__;
                      QQmlApplicationEngine engine;
                    
                      qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );
                    
                      engine.load(QUrl(QStringLiteral("qrc:/main.qml")));
                      if (engine.rootObjects().isEmpty())
                        return -1;
                    
                      return GuiApp->exec();
                    }
                    

                    Main Application
                    // main.cpp

                    #include <QGuiApplication>
                    #include <QQmlApplicationEngine>
                    
                    #include "LibAppA.h"
                    #include "LibAppB.h"
                    
                    int main(int argc, char *argv[])
                    {
                      QGuiApplication app(argc, argv);
                      bool bLibA = false;
                      if(bLibA) {
                        LibAppA AppA;
                        return AppA.Start(&app);
                      } else {
                        LibAppB AppB;
                        return AppB.Start(&app);
                      }
                    }
                    

                    Lib B is essentially the same as Lib A except for: code below in Start(), library headers, qDebug() calls to identify which class is being called, and the QML Window has a "B" title.

                    engine.load(QUrl(QStringLiteral("qrc:/qml_b/main.qml")));
                    

                    Thanks again for reading.

                    raven-worxR Offline
                    raven-worxR Offline
                    raven-worx
                    Moderators
                    wrote on last edited by
                    #11

                    @mooglus said in QML files in C++ libraries:

                    Both libraries register the C++ classes in their Start() functions, into uri: MyQml
                    qmlRegisterType<MyCppClass>("MyQml", 1,0, "MyCppClass" );
                    Each QML file imports MyQml

                    so you are importing the types with the same name into the same uri.
                    How should the QML engine know which one you would actually like to use, when all you do is to tell it import MyQml 1.0

                    I found changing the name or version of the MyQml import had no affect on my problem

                    did you just change the minor version number?
                    Changing the major version number (e.g. to "2.0") wont contain any types registered in "1.x"

                    --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                    If you have a question please use the forum so others can benefit from the solution in the future

                    1 Reply Last reply
                    0
                    • M Offline
                      M Offline
                      mooglus
                      wrote on last edited by mooglus
                      #12

                      @raven-worx said in QML files in C++ libraries:

                      did you just change the minor version number?
                      Changing the major version number (e.g. to "2.0") wont contain any types registered in "1.x"

                      Changing the major version in LibB to 2.0, results in the same behaviour, LibB's main.qml still loads LibA's MyCppClass

                      Surely the if/ else below from main.cpp prevents LibA calling qmlRegisterType anyway.

                        if(bLibA) {
                          LibAppA AppA;
                          return AppA.Start(&app);
                        } else {
                          LibAppB AppB;
                          return AppB.Start(&app);
                        }
                      

                      Changing the version wouldn't explain why namespacing LibB's MyCppClass fixes the issue, either?

                      Apologies, I should have perhaps been clearer: AppX.Start() is never called on both LibA and LibB in this case.

                      raven-worxR 1 Reply Last reply
                      0
                      • M mooglus

                        @raven-worx said in QML files in C++ libraries:

                        did you just change the minor version number?
                        Changing the major version number (e.g. to "2.0") wont contain any types registered in "1.x"

                        Changing the major version in LibB to 2.0, results in the same behaviour, LibB's main.qml still loads LibA's MyCppClass

                        Surely the if/ else below from main.cpp prevents LibA calling qmlRegisterType anyway.

                          if(bLibA) {
                            LibAppA AppA;
                            return AppA.Start(&app);
                          } else {
                            LibAppB AppB;
                            return AppB.Start(&app);
                          }
                        

                        Changing the version wouldn't explain why namespacing LibB's MyCppClass fixes the issue, either?

                        Apologies, I should have perhaps been clearer: AppX.Start() is never called on both LibA and LibB in this case.

                        raven-worxR Offline
                        raven-worxR Offline
                        raven-worx
                        Moderators
                        wrote on last edited by
                        #13

                        @mooglus
                        i think i still dont know what you are trying to achieve.
                        A C++ type can't have the same QML type name, import uri and version number, but a different implementation.
                        How do you think the engine should differentiate those, when all you do is to import the exact same URI for both versions.

                        In QML, where exactly would YOU want to differentiate between the versions?!

                        --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                        If you have a question please use the forum so others can benefit from the solution in the future

                        M 1 Reply Last reply
                        0
                        • raven-worxR raven-worx

                          @mooglus
                          i think i still dont know what you are trying to achieve.
                          A C++ type can't have the same QML type name, import uri and version number, but a different implementation.
                          How do you think the engine should differentiate those, when all you do is to import the exact same URI for both versions.

                          In QML, where exactly would YOU want to differentiate between the versions?!

                          M Offline
                          M Offline
                          mooglus
                          wrote on last edited by
                          #14

                          @raven-worx

                          My aims:

                          I want to be able to take an old version of my app and bundle it up in a library so it can be loaded instead of the current version. This is because mobile app stores don't allow downgrading of an app.

                          This might flow something like this...

                          1. At startup: User is prompted "Would you like App A, or B?"
                          2. User chooses: the appropriate library is started

                          In this case, I don't see why the QML engine would need to differentiate between implementations, because only one implementation is actually registered. It seems the C++ always registers LibA's MyCppClass even when qmlRegisterType is called in LibB.

                          raven-worxR 1 Reply Last reply
                          0
                          • M mooglus

                            @raven-worx

                            My aims:

                            I want to be able to take an old version of my app and bundle it up in a library so it can be loaded instead of the current version. This is because mobile app stores don't allow downgrading of an app.

                            This might flow something like this...

                            1. At startup: User is prompted "Would you like App A, or B?"
                            2. User chooses: the appropriate library is started

                            In this case, I don't see why the QML engine would need to differentiate between implementations, because only one implementation is actually registered. It seems the C++ always registers LibA's MyCppClass even when qmlRegisterType is called in LibB.

                            raven-worxR Offline
                            raven-worxR Offline
                            raven-worx
                            Moderators
                            wrote on last edited by
                            #15

                            @mooglus said in QML files in C++ libraries:

                            This might flow something like this...

                            At startup: User is prompted "Would you like App A, or B?"
                            User chooses: the appropriate library is started

                            ok, then do it simple.
                            register each lib types in a separate uri.
                            Your main qml then shows the choosing UI and then use a Loader element to load the QML files of the desired version. The QML files of libA use the uri one and the libB files use the other uri.

                            --- SUPPORT REQUESTS VIA CHAT WILL BE IGNORED ---
                            If you have a question please use the forum so others can benefit from the solution in the future

                            M 1 Reply Last reply
                            0
                            • raven-worxR raven-worx

                              @mooglus said in QML files in C++ libraries:

                              This might flow something like this...

                              At startup: User is prompted "Would you like App A, or B?"
                              User chooses: the appropriate library is started

                              ok, then do it simple.
                              register each lib types in a separate uri.
                              Your main qml then shows the choosing UI and then use a Loader element to load the QML files of the desired version. The QML files of libA use the uri one and the libB files use the other uri.

                              M Offline
                              M Offline
                              mooglus
                              wrote on last edited by
                              #16

                              @raven-worx

                              Thanks for your time.

                              I think I need to spend some more time reading about Qt's MetaType system.

                              1 Reply Last reply
                              0

                              • Login

                              • Login or register to search.
                              • First post
                                Last post
                              0
                              • Categories
                              • Recent
                              • Tags
                              • Popular
                              • Users
                              • Groups
                              • Search
                              • Get Qt Extensions
                              • Unsolved