Qt 6.2 + CMake + extending QML with a C++ class
-
I'm experienced in extending QML with C++ for Qt 5.9 - 5.12 using qmake.
I want to switch to the recommended current approach in Qt 6.2 with CMake but I can't work out how to get all the pieces to fit together.
Let's say I have a C++ class Person, which has QML_ELEMENT.
I want to be able to use it in my QtQuick application main.qml by including import People.
e.g.import QtQuick import People Window { width: 640 height: 480 visible: true text: personBob.name + " wears size " + personBob.shoeSize + " shoes." Person { id: personBob name: "Bob Jones" shoeSize: 12 } }
How do I write CMakeList.txt and main.cpp to make this possible?
Here's my attempt, which compiled but gives the error
QML module not found (People)
againstimport People
in main.qml:- Don't add anything related to Person into main.cpp
- Here's my CMakeList.txt
cmake_minimum_required(VERSION 3.16) project(myproject VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_executable(myproject main.cpp Person.h Person.cpp ) qt_add_qml_module(myproject URI myproject VERSION 1.0 QML_FILES main.qml ) qt_add_qml_module(People URI People VERSION 1.0 SOURCES Person.h Person.cpp )
-
So for clarity for anyone with the same question, the following CMakeLists.txt makes the class
Person
available to QML viaimport People
, by packaging it behind the scenes asPeopleplugin
.cmake_minimum_required(VERSION 3.16) project(MyProject VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_executable(appMyProject main.cpp ) qt_add_qml_module(appMyProject URI MyProject VERSION 1.0 QML_FILES main.qml ) qt_add_qml_module(People URI People VERSION 1.0 SOURCES Person.h Person.cpp ) target_include_directories(Peopleplugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) target_compile_definitions(appMyProject PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(appMyProject PRIVATE Qt6::Quick People)
and for those eagle-eyed,
main.qml
was buggy and should be:import QtQuick import People Window { width: 640 height: 480 visible: true Text { text: personBob.name + " wears size " + personBob.shoeSize + " shoes." } Person { id: personBob name: "Bob Jones" shoeSize: 12 } }
The C++ class
Person
is derived fromQObject
:#include <QObject> #include <QtQml/qqml.h> class Person : public QObject { Q_OBJECT Q_PROPERTY(QString name READ name WRITE setName NOTIFY nameChanged) Q_PROPERTY(int shoeSize READ shoeSize WRITE setShoeSize NOTIFY shoeSizeChanged) QML_ELEMENT ...
main.cpp
is as autogenerated when creating a new Qt Quick 6.2 project in Qt Creator and makes no mention ofPerson
or thePeople
plugin.Other C++ classes can
#include "Person.h"
and make use of thePerson
class. -
From CMake's perspective you haven't created any relationship between the target "myproject" (your executable) and the target "People" (a library).
Now if we'd ignore Qt for a moment all it would need to satisfy CMake would be something along the lines of:
target_link_libraries(myproject PRIVATE People)
This tells CMake that the executable "myproject" must link against "People". This is important to understand and fundamentally the same for all modern CMake.
However with Qt things get a little more complicated...
qt_add_qml_module actually creates two targets:- a backing target (People) and
- a plugin target (Peopleplugin)
The problem I have with qt_add_qml_module is that depending on your Qt installation the types of those targets might differ.
If your Qt installation is for shared libraries then the backing target will be a shared library (.so or .dll) and the plugin target will be a module library (from CMake's point of view some kind of plugin thingy).
If your Qt installation is for static libraries then the backing target will be static (.a) and the plugin target will be static (.a).
The implicit behavior of qt_add_qml_module can be overwritten by adding either the STATIC or the SHARED keyword.
Now the problem that arises here is that depending on the target type you need to link EITHER the backing OR the plugin library.
# This could be written shorter using so called "generator expressions", but let's keep it simple for now get_target_property(target_type People TYPE) if (target_type STREQUAL "STATIC_LIBRARY") target_link_libraries(myproject PRIVATE People) elseif() target_link_libraries(myproject PRIVATE Peopleplugin) endif ()
Good look explaining that to people new to CMake...
And then, even if you got the linking part right you still need to worry about the URI matching your directory layout... that's a whole other story I myself currently struggle with. Basically I'd advise to follow the directory layout presented here in order for the QML engine to find all relevant files.
-
@vinci said in Qt 6.2 + CMake + extending QML with a C++ class:
If your Qt installation is for shared libraries then the backing target will be a shared library (.so or .dll) and the plugin target will be a module library (from CMake's point of view some kind of plugin thingy).
The backing target can be either shared or static independent of what Qt was compiled with. The plugin is going to be either dynamic or module based on the backing target type.
-
@kshegunov said in Qt 6.2 + CMake + extending QML with a C++ class:
@vinci said in Qt 6.2 + CMake + extending QML with a C++ class:
If your Qt installation is for shared libraries then the backing target will be a shared library (.so or .dll) and the plugin target will be a module library (from CMake's point of view some kind of plugin thingy).
The backing target can be either shared or static independent of what Qt was compiled with. The plugin is going to be either dynamic or module based on the backing target type.
Not according to my tests. When I overwrite the implicit behavior of qt_add_qml_module I get a STATIC_LIBRARY CMake target and a *plugin.a output.
-
@vinci said in Qt 6.2 + CMake + extending QML with a C++ class:
Not according to my tests. When I overwrite the implicit behavior of qt_add_qml_module I get a STATIC_LIBRARY CMake target and a *plugin.a output.
Create the backing target with
add_library
and add the QML module to the existing target. -
@kshegunov said in Qt 6.2 + CMake + extending QML with a C++ class:
@vinci said in Qt 6.2 + CMake + extending QML with a C++ class:
Not according to my tests. When I overwrite the implicit behavior of qt_add_qml_module I get a STATIC_LIBRARY CMake target and a *plugin.a output.
Create the backing target with
add_library
and add the QML module to the existing target.Still static.
add_library(QmlElement STATIC) qt_add_qml_module( QmlElement URI QmlElement VERSION 0.0.1 SOURCES qml_element.cpp qml_element.hpp) target_include_directories(QmlElementplugin PUBLIC ./)
-
We probably talk past each other. You've said that the "plugin target" is going to be either dynamic or module based.
Translating that to CMake's target TYPE that would be either SHARED_LIBRARY or MODULE_LIBRARY right?Now according to my tests the plugin target which gets created is either of TYPE STATIC_LIBRARY or of MODULE_LIBRARY, but never of TYPE SHARED_LIBRARY.
-
@vinci said in Qt 6.2 + CMake + extending QML with a C++ class:
You've said that the "plugin target" is going to be either dynamic or module based.
Yes, I misspoke. I meant to write that it's either static or module. If you want to link explicitly as a shared library, then use NO_PLUGIN and link against the backing target directly.
-
@vinci said in Qt 6.2 + CMake + extending QML with a C++ class:
Thank you for clearing that up!
No problem.
I really hope there will be more blog posts / tutorials on this in the future.
There probably will be, I imagine. Take note that if you link explicitly to the backing target without a plugin you must use some symbol from it. (look at the blogpost for the volatile pointer trick).
-
I'm grateful to have some responses to my question, but as a total novice when it comes to CMake, I'm still confused and unable to get something to work. The conversation about backing targets and plugins goes over my head.
I am building a standalone app (executable), so I think that what I'm after is a statically linked plugin.
From what you've said, I've tried appending the following to my CMakeLists.txt shown above...
qt_add_library(People STATIC) target_include_directories(Peopleplugin PUBLIC ./) target_link_libraries(myproject PRIVATE People)
This builds fine but I still get "QML module not found" in main.qml.
Can you provide a cookie-cutter CMakeLists.txt to get me off and running?
-
@paulmasri said in Qt 6.2 + CMake + extending QML with a C++ class:
I am building a standalone app (executable), so I think that what I'm after is a statically linked plugin.
I don't believe so. If you're building a 'bundle-all' executable, you wouldn't need a plugin to begin with. I'd try something like this:
qt_add_qml_module(People URI People VERSION 1.0 STATIC SOURCES Person.h Person.cpp NO_PLUGIN OUTPUT_TARGETS PEOPLE_DEPS ) target_link_library(myproject PRIVATE People ${PEOPLE_DEPS})
Also you may need to initialize the resources manually, as static builds are pita to begin with. Personally, I wouldn't do it.
PS.
Also remove the duplicating source files:qt_add_executable(myproject main.cpp Person.h Person.cpp #< Remove this )
-
@kshegunov said in Qt 6.2 + CMake + extending QML with a C++ class:
@paulmasri said in Qt 6.2 + CMake + extending QML with a C++ class:
I am building a standalone app (executable), so I think that what I'm after is a statically linked plugin.
I don't believe so. If you're building a 'bundle-all' executable, you wouldn't need a plugin to begin with.
I am building a cross-platform app for deployment to Microsoft Store (UWP for Windows 10) and Apple App Store (for iPad), as well as for manual download to Windows 10.
I'd try something like this:
... (code)I've tried this but get 2 build errors. See below for my full CMakeLists.txt file and the errors.
Also you may need to initialize the resources manually, as static builds are pita to begin with. Personally, I wouldn't do it.
Sorry I didn't understand what you're saying. What do you mean by "initialize the resources manually"? And what is it that you wouldn't do?
PS.
Also remove the duplicating source files:qt_add_executable(myproject main.cpp Person.h Person.cpp #< Remove this )
I want to use this class within C++ too. Surely I need to keep retain this line in this case?
Here is my CMakeLists.txt file now:
cmake_minimum_required(VERSION 3.16) project(myproject VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_executable(myproject main.cpp ) qt_add_qml_module(myproject URI myproject VERSION 1.0 QML_FILES main.qml ) qt_add_qml_module(People URI People VERSION 1.0 STATIC SOURCES Person.h Person.cpp NO_PLUGIN OUTPUT_TARGETS PEOPLE_DEPS ) target_compile_definitions(myproject PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(myproject PRIVATE Qt6::Quick) target_link_libraries(myproject PRIVATE People ${PEOPLE_DEPS})
And these are the errors I get:
[ 96%] Linking CXX executable myproject duplicate symbol 'qInitResources_qmake_People()' in: CMakeFiles/People_resources_1.dir/.rcc/qrc_qmake_People.cpp.o duplicate symbol 'qCleanupResources_qmake_People()' in: CMakeFiles/People_resources_1.dir/.rcc/qrc_qmake_People.cpp.o ld: 2 duplicate symbols for architecture arm64 clang: error: linker command failed with exit code 1 (use -v to see invocation) make[2]: *** [myproject] Error 1 make[1]: *** [CMakeFiles/myproject.dir/all] Error 2 make: *** [all] Error 2
-
@paulmasri said in Qt 6.2 + CMake + extending QML with a C++ class:
I am building a cross-platform app for deployment to Microsoft Store (UWP for Windows 10) and Apple App Store (for iPad), as well as for manual download to Windows 10.
To my knowledge none of these require static linking.
I've tried this but get 2 build errors. See below for my full CMakeLists.txt file and the errors.
These are link errors, look below for the explanation.
Sorry I didn't understand what you're saying. What do you mean by "initialize the resources manually"?
Look here:
https://doc.qt.io/qt-6/resources.html#explicit-loading-and-unloading-of-embedded-resourcesAnd what is it that you wouldn't do?
I wouldn't go for statically linking unless I have a really good reason.
I want to use this class within C++ too. Surely I need to keep retain this line in this case?
Then, you need to setup the backing target properly to provide the headers. Look below for more.
And these are the errors I get ...
I guess that's my fault, link the
PEOPLE_DEPS
that already should include the needed dependencies and linkingPeople
explicitly leads to the symbol duplication.
Example below (not tested, but should work with a bit of tweaking if necessary):... qt_add_executable(myproject main.cpp ) qt_add_qml_module(myproject URI myproject VERSION 1.0 QML_FILES main.qml ) qt_add_qml_module(People URI People VERSION 1.0 STATIC SOURCES Person.h Person.cpp NO_PLUGIN OUTPUT_TARGETS PEOPLE_DEPS ) target_include_directories(People PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) target_compile_definitions(myproject PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(myproject PRIVATE Qt6::Quick ${PEOPLE_DEPS} )
You may need to do the volatile pointer trick from the blog post as well.
-
@kshegunov said in Qt 6.2 + CMake + extending QML with a C++ class:
Look here:
https://doc.qt.io/qt-6/resources.html#explicit-loading-and-unloading-of-embedded-resourcesThanks!
Then, you need to setup the backing target properly to provide the headers. Look below for more.
So... if I understand right, the instruction starting
qt_add_qml_module(People,...)
is where I'm setting up the backing target for People. Is that right? And by including the .h & .cpp sources here, they should become available to C++?Example below (not tested, but should work with a bit of tweaking if necessary):
Some forward progress...
- Build & link works fine, no errors.
- After closing and reopening the project,
main.qml
stopped complaining aboutimport People
and is now recognising and formattingPerson
correctly. - However when I run the application, it fails with:
QQmlApplicationEngine failed to load component qrc:/myproject/main.qml:2:1: module "People" is not installed
Any ideas? Do I somehow need to link People to main.qml in the CMake?
(Also, you refer to volatile pointers. Even if it's not relevant here, that's something I should read up on. Which blog article were you referring to?)
-
OK... with a bit of fiddling, I've got something that builds & links, shows no errors in
main.qml
and runs without error.As I don't honestly have a clue about CMake yet, this may technically have problems or cause me problems down the road — do let me know if that looks likely — but it does appear to work properly and it lets me progress with coding.
Below is the complete CMakeLists.txt.
For the benefit of others finding this thread, I've made some subtle alterations that help distinguish between what is a target and what's a URI:
- Folder structure is:
- Parent folder MyProject
- Files CMakeLists.txt, main.cpp, main.qml, Person.h, Person.cpp all sit in this folder
- The project is
MyProject
- The executable that is generated (the target) is
appMyProject
- The class Person becomes target
Peopleplugin
with URIPeople
(accessed in QML viaimport People
) - Within Qt Creator a virtual folder structure is autogenerated with
main.cpp
&main.qml
in virtual folderappMyProject
; andPerson.h
&Person.cpp
in virtual folderPeopleplugin
- Following any changes, to be sure of a good build, delete the build folder and close QML files. Then make changes to CMakeLists.txt, build and then you can open QML files.
CMakeLists.txt
cmake_minimum_required(VERSION 3.16) project(MyProject VERSION 0.1 LANGUAGES CXX) set(CMAKE_AUTOMOC ON) set(CMAKE_CXX_STANDARD_REQUIRED ON) find_package(Qt6 6.2 COMPONENTS Quick REQUIRED) qt_add_executable(appMyProject main.cpp ) qt_add_qml_module(appMyProject URI MyProject VERSION 1.0 QML_FILES main.qml ) qt_add_qml_module(Peopleplugin URI People VERSION 1.0 SOURCES Person.h Person.cpp ) target_include_directories(Peopleplugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR} ) target_compile_definitions(appMyProject PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>) target_link_libraries(appMyProject PRIVATE Qt6::Quick Peopleplugin)
I took out the
STATIC
,NO_PLUGIN
&OUTPUT_TARGETS
and instead let it create a plugin calledPeopleplugin
.I think the key changes from when I posted the original question are:
- Change
qt_add_qml_module(People URI People...
toqt_add_qml_module(Peopleplugin URI People...
- Add
target_include_directories(Peopleplugin PUBLIC ${CMAKE_CURRENT_SOURCE_DIR})
- Add
Peopleplugin
to the list withintarget_link_libraries(... PRIVATE...)
Thoughts & comments welcome.
- Folder structure is:
-
Yes, looks correct, although I'd say use
qt_add_qml_module(People ...
The cmake function already creates a plugin for you (as you'd removed theNO_PLUGIN
and that binary is going to be called Peoplepuginplugin - e.g.libPeoplepluginplugin.so
, which is somewhat confusing).So... if I understand right, the instruction starting qt_add_qml_module(People,...) is where I'm setting up the backing target for People. Is that right? And by including the .h & .cpp sources here, they should become available to C++?
Yes. Although as usual those headers must be supplied to the consuming target (myproject), whence the
target_include_directories
)Which blog article were you referring to?
It was mentioned somewhere above, I belive: https://www.qt.io/blog/qml-modules-in-qt-6.2