Solved QML files in C++ libraries
-
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!
-
Separate out Qml files into two different qrc files and generate two rcc files. load the rcc file at run time using registeresource. This way you can achieve two different versions and use only one.
-
@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 urlI recommend the plugin approch.
-
@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.qmlWith 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? -
@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 { }
-
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.
-
@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. -
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!
-
@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? -
@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 MyQmlI 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.
-
@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 MyQmlso 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 itimport 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" -
@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.
-
@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?!
-
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...
- At startup: User is prompted "Would you like App A, or B?"
- 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.
-
@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 startedok, 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. -
Thanks for your time.
I think I need to spend some more time reading about Qt's MetaType system.