QML-Typ-Compiler

Der QML Type Compiler, qmltc, ist ein mit Qt geliefertes Werkzeug zur Übersetzung von QML-Typen in C++-Typen, die als Teil des Anwendercodes vorzeitig kompiliert werden. Die Verwendung von qmltc kann zu einer besseren Laufzeitleistung führen, da dem Compiler mehr Optimierungsmöglichkeiten zur Verfügung stehen als bei einer QQmlComponent-basierten Objekterzeugung. Der qmltc ist Teil der Qt Quick Compiler Toolchain.

Vom Design her gibt qmltc benutzerseitigen Code aus. Dieser Code soll direkt von der C++-Anwendung verwendet werden, da man sonst keinen Nutzen daraus ziehen kann. Dieser generierte Code ersetzt im Wesentlichen QQmlComponent und seine APIs zur Erstellung von Objekten aus QML-Dokumenten. Weitere Informationen finden Sie unter Verwendung von qmltc in einer QML-Anwendung und Generierte Ausgabe - Grundlagen.

Um qmltc zu aktivieren:

  • Erstellen Sie ein geeignetes QML-Modul für Ihre Anwendung.
  • Rufen Sie qmltc auf, zum Beispiel über die CMake-API.
  • #include die generierte(n) Header-Datei(en) in den Quellcode der Anwendung einfügen.
  • Instanziieren Sie ein Objekt des generierten Typs.

In diesem Workflow läuft qmltc normalerweise während des Build-Prozesses. Wenn also qmltc ein QML-Dokument zurückweist (sei es aufgrund von Fehlern oder Warnungen oder aufgrund von Konstrukten, die qmltc noch nicht unterstützt), wird der Build-Prozess fehlschlagen. Dies ist vergleichbar mit qmllint-Fehlern, die auftreten, wenn Sie die automatische Generierung von Linting-Zielen während der Erstellung von QML-Modulen aktivieren und dann versuchen, sie zu "bauen", um qmllint auszuführen.

Warnung: qmltc befindet sich derzeit in einem Tech Preview Stadium und kompiliert möglicherweise kein beliebiges QML-Programm (siehe Bekannte Einschränkungen für weitere Details). Wenn qmltc fehlschlägt, wird nichts erzeugt, da Ihre Anwendung die qmltc-Ausgabe nicht sinnvoll nutzen kann. Wenn Ihr Programm Fehler (oder unlösbare Warnungen) enthält, sollten diese behoben werden, um die Kompilierung zu ermöglichen. Die allgemeine Regel ist, sich an die besten Praktiken zu halten und den Ratschlägen von qmllint zu folgen.

Hinweis: qmltc garantiert nicht, dass das generierte C++ API-, Source- oder Binärkompatibel zwischen vergangenen oder zukünftigen Versionen bleibt, auch nicht mit Patch-Versionen. Außerdem müssen mit qmltc kompilierte Anwendungen, die Qt QML Module verwenden, gegen die private Qt API gelinkt werden. Dies liegt daran, dass die QML-Module von Qt in der Regel keine öffentliche C++-API bereitstellen, da ihre primäre Verwendung über QML erfolgt.

Verwendung von qmltc in einer QML-Anwendung

Aus der Sicht des Build-Systems unterscheidet sich das Hinzufügen der qmltc-Kompilierung nicht wesentlich vom Hinzufügen der Qml-Cache-Generierung. Naiv betrachtet könnte man den Build-Prozess wie folgt beschreiben:

Während der tatsächliche Kompilierungsprozess viel komplizierter ist, fasst dieses Diagramm die Kernkomponenten zusammen, die qmltc verwendet: die QML-Dateien selbst und qmldir mit den qmltypes-Informationen. Einfachere Anwendungen haben typischerweise ein eher primitives qmldir, doch im Allgemeinen kann qmldir komplex sein und wichtige, gut verpackte Typinformationen bereitstellen, auf die sich qmltc verlässt, um eine korrekte Übersetzung von QML nach C++ durchzuführen.

Dennoch reicht es im Fall von qmltc nicht aus, einen zusätzlichen Build-Schritt hinzuzufügen. Der Anwendungscode muss auch so modifiziert werden, dass er von qmltc generierte Klassen anstelle von QQmlComponent oder seinen höherwertigen Alternativen verwendet.

Kompilieren von QML-Code mit qmltc

Qt, beginnend mit Qt 6, verwendet CMake, um seine verschiedenen Komponenten zu kompilieren. Benutzerprojekte können - und sollten - CMake auch verwenden, um ihre Komponenten mit Qt zu erstellen. Das Hinzufügen einer sofort einsatzbereiten qmltc-Kompilierungsunterstützung zu Ihrem Projekt würde ebenfalls einen CMake-gesteuerten Build-Flow erfordern, da dieser Flow auf die richtigen QML-Module und deren Infrastruktur ausgerichtet ist.

Der einfachste Weg, die qmltc-Kompilierung hinzuzufügen, ist die Verwendung der dedizierten CMake-API als Teil der Erstellung eines QML-Moduls für die Anwendung. Betrachten Sie eine einfache Verzeichnisstruktur der Anwendung:

.
├── CMakeLists.txt
├── myspecialtype.h     // C++ type exposed to QML
├── myspecialtype.cpp
├── myApp.qml           // main QML page
├── MyButton.qml        // custom UI button
├── MySlider.qml        // custom UI slider
└── main.cpp            // main C++ application file

Dann würde der CMake-Code in der Regel ähnlich wie der folgende aussehen:

# Use "my_qmltc_example" as an application name:
set(application_name my_qmltc_example)

# Create a CMake target, add C++ source files, link libraries, etc...

# Specify a list of QML files to be compiled:
set(application_qml_files
    myApp.qml
    MyButton.qml
    MySlider.qml
)

# Make the application into a proper QML module:
qt6_add_qml_module(${application_name}
    URI QmltcExample
    QML_FILES ${application_qml_files}

    # Compile qml files (listed in QML_FILES) to C++ using qmltc and add these
    # files to the application binary:
    ENABLE_TYPE_COMPILER
    NO_GENERATE_EXTRA_QMLDIRS
)

# (qmltc-specific) Link *private* libraries that correspond to QML modules:
target_link_libraries(${application_name} PRIVATE Qt::QmlPrivate Qt::QuickPrivate)

Verwendung des generierten C++

Anders als bei der QQmlComponent Instanziierung wird die Ausgabe von qmltc als C++-Code direkt von der Anwendung verwendet. Im Allgemeinen ist die Erstellung eines neuen Objekts in C++ gleichbedeutend mit der Erstellung eines neuen Objekts durch QQmlComponent::create(). Nach der Erstellung kann das Objekt von C++ aus manipuliert oder z. B. mit QQuickWindow kombiniert werden, um es auf dem Bildschirm zu zeichnen.

Wenn ein kompilierter Typ einige erforderliche Eigenschaften aufweist, verlangt `qmltc` einen Anfangswert für diese Eigenschaften im Konstruktor für das erzeugte Objekt.

Zusätzlich kann der Konstruktor für ein qmltc-Objekt mit einem Callback versehen werden, um Anfangswerte für die Eigenschaften der Komponente zu setzen.

Ausgehend von einer myApp.qml Datei würde der Anwendungscode (in beiden Fällen) typischerweise wie folgt aussehen:

#include <QtQml/qqmlcomponent.h>

QGuiApplication app(argc, argv);
app.setApplicationDisplayName(QStringLiteral("This example is powered by QQmlComponent :("));

QQmlEngine e;
// If the root element is Window, you don't need to create a Window.
// The snippet is for the cases where the root element is not a Window.
QQuickWindow window;

QQmlComponent component(&e);
component.loadUrl(
            QUrl(QStringLiteral("qrc:/qt/qml/QmltcExample/myApp.qml")));

QScopedPointer<QObject> documentRoot(component.create());
QQuickItem *documentRootItem = qobject_cast<QQuickItem *>(documentRoot.get());

documentRootItem->setParentItem(window.contentItem());
window.setHeight(documentRootItem->height());
window.setWidth(documentRootItem->width());
// ...

window.show();
app.exec();
#include "myapp.h" // include generated C++ header

QGuiApplication app(argc, argv);
app.setApplicationDisplayName(QStringLiteral("This example is powered by qmltc!"));

QQmlEngine e;
// If the root element is Window, you don't need to create a Window.
// The snippet is for the cases where the root element is not a Window.
QQuickWindow window;

QScopedPointer<QmltcExample::myApp> documentRoot(
    new QmltcExample::myApp(&e, nullptr, [](auto& component){
        component.setWidth(800);
}));

documentRoot->setParentItem(window.contentItem());
window.setHeight(documentRoot->height());
window.setWidth(documentRoot->width());
// ...

window.show();
app.exec();

QML-Engine

Der generierte Code verwendet QQmlEngine, um mit dynamischen Teilen eines QML-Dokuments zu interagieren - hauptsächlich mit dem JavaScript-Code. Damit dies funktioniert, sind keine besonderen Vorkehrungen erforderlich. Jede QQmlEngine -Instanz, die an den Konstruktor eines von qmltc generierten Klassenobjekts übergeben wird, sollte korrekt funktionieren, ebenso wie QQmlComponent(engine). Das bedeutet auch, dass Sie QQmlEngine methods verwenden können, die das QML-Verhalten beeinflussen. Es gibt jedoch einige Vorbehalte. Im Gegensatz zur QQmlComponent-basierten Objekterzeugung verlässt sich qmltc selbst nicht auf QQmlEngine, wenn es den Code nach C++ kompiliert. Zum Beispiel würde QQmlEngine::addImportPath("/foo/bar/") - was normalerweise zu einem zusätzlichen Importpfad führt, nach dem gesucht werden muss - von der vorausschauenden qmltc-Prozedur vollständig ignoriert werden.

Hinweis: Um Importpfade zur qmltc-Kompilierung hinzuzufügen, sollten Sie stattdessen ein entsprechendes Argument des CMake-Befehls verwenden.

Im Allgemeinen können Sie sich das so vorstellen: QQmlEngine bezieht den auszuführenden Anwendungsprozess mit ein, während qmltc dies nicht tut, da es arbeitet , bevor Ihre Anwendung überhaupt kompiliert ist. Da qmltc keinen Versuch unternimmt, den C++-Quellcode Ihrer Anwendung einzusehen, gibt es keine Möglichkeit für qmltc, von bestimmten QML-Manipulationen zu erfahren, die Sie als Benutzer vornehmen. Anstatt QQmlEngine und verwandte Laufzeitroutinen zu verwenden, um Typen für QML freizugeben, Importpfade hinzuzufügen usw. sind Sie praktisch gezwungen, gut funktionierende QML-Module zu erstellen und die deklarative QML-Typenregistrierung zu verwenden.

Warnung: Obwohl qmltc eng mit QQmlEngine zusammenarbeitet und C++-Code erzeugt, können die erzeugten Klassen nicht weiter in QML exponiert und über QQmlComponent verwendet werden.

Grundlagen der generierten Ausgabe

qmltc zielt darauf ab, mit dem bestehenden QML-Ausführungsmodell kompatibel zu sein. Das bedeutet, dass der generierte Code in etwa der internen QQmlComponent Setup-Logik entspricht und Sie daher in der Lage sein sollten, das Verhalten, die Semantik und die API Ihres QML-Typs auf die gleiche Weise zu verstehen, wie Sie es derzeit tun - durch visuelle Inspektion des entsprechenden QML-Dokuments.

Der generierte Code ist jedoch immer noch etwas verwirrend, insbesondere wenn man bedenkt, dass Ihre Anwendung die qmltc-Ausgabe auf der C++-Seite direkt verwenden sollte. Es gibt zwei Teile des generierten Codes: Die Struktur der CMake-Build-Dateien und das generierte C++-Format. Ersteres wird in der CMake-API von qmltc behandelt, letzteres hier.

Betrachten wir einen einfachen HelloWorld-Typ, der eine hello -Eigenschaft, eine Funktion zum Drucken dieser Eigenschaft und ein Signal hat, das ausgegeben wird, wenn das Objekt dieses Typs erzeugt wird:

// HelloWorld.qml
import QtQml

QtObject {
    id: me
    property string hello: "Hello, qmltc!"

    function printHello(prefix: string, suffix: string) {
        console.log(prefix + me.hello + suffix);
    }

    signal created()
    Component.onCompleted: me.created();
}

Bei der Bereitstellung einer C++-Alternative dieses QML-Typs würde die C++-Klasse ein QML-spezifisches Meta-Objektsystemmakro, Q_PROPERTY Dekoration für die Eigenschaft hello, Q_INVOKABLE C++-Druckfunktion und eine reguläre Qt-Signaldefinition benötigen. In ähnlicher Weise würde qmltc den gegebenen HelloWorld-Typ in etwa wie folgt übersetzen:

class HelloWorld : public QObject
{
    Q_OBJECT
    QML_ELEMENT
    Q_PROPERTY(QString hello WRITE setHello READ hello BINDABLE bindableHello)

public:
    HelloWorld(QQmlEngine* engine, QObject* parent = nullptr, [[maybe_unused]] qxp::function_ref<void(PropertyInitializer&)> initializer = [](PropertyInitializer&){});

Q_SIGNALS:
    void created();

public:
    void setHello(const QString& hello_);
    QString hello();
    QBindable<QString> bindableHello();
    Q_INVOKABLE void printHello(passByConstRefOrValue<QString> prefix, passByConstRefOrValue<QString> suffix);

    // ...
};

Auch wenn sich spezifische Details des generierten Typs unterscheiden können, bleiben die universellen Aspekte erhalten. Zum Beispiel:

  • QML-Typen innerhalb eines Dokuments werden in C++-Typen übersetzt, entsprechend den für den Compiler sichtbaren Informationen.
  • Eigenschaften werden in C++-Eigenschaften mit Q_PROPERTY Deklarationen übersetzt.
  • JavaScript-Funktionen werden zu Q_INVOKABLE C++-Funktionen.
  • QML-Signale werden in C++-Qt-Signale umgewandelt.
  • QML-Aufzählungen werden in C++-Aufzählungen mit Q_ENUM Deklarationen umgewandelt.

Ein weiteres Detail ist die Art und Weise, wie qmltc Klassennamen erzeugt. Ein Klassenname für einen bestimmten QML-Typ wird automatisch aus dem QML-Dokument abgeleitet, das diesen Typ definiert: Der QML-Dateiname ohne Erweiterungen (bis auf den ersten ., auch bekannt als Basisname) wird zum Klassennamen. Die Groß- und Kleinschreibung des Dateinamens wird beibehalten. So würde HelloWorld.qml zu class HelloWorld und helloWoRlD.qml zu class helloWoRlD führen. Wenn der Dateiname eines QML-Dokuments mit einem Kleinbuchstaben beginnt, wird gemäß der QML-Konvention davon ausgegangen, dass die generierte C++-Klasse anonym ist und mit QML_ANONYMOUS gekennzeichnet.

Obwohl der generierte Code für die Verwendung in der C++-Anwendung bereit ist, sollten Sie die Aufrufe der generierten APIs generell beschränken. Implementieren Sie stattdessen lieber die Anwendungslogik in QML/JavaScript und handgeschriebene C++-Typen, die in QML exponiert sind, und verwenden Sie die von qmltc erstellten Klassen für die einfache Objektinstanziierung. Während generiertes C++ Ihnen direkten (und in der Regel schnelleren) Zugriff auf QML-definierte Elemente des Typs bietet, kann das Verständnis dieses Codes eine Herausforderung darstellen.

Bekannte Beschränkungen

Obwohl qmltc viele gängige QML-Funktionen abdeckt, befindet es sich noch in einem frühen Entwicklungsstadium, und einige Dinge müssen noch unterstützt werden.

Importierte QML-Module, die aus QML-definierten Typen bestehen (wie z.B. QtQuick.Controls), werden möglicherweise nicht korrekt kompiliert, selbst wenn diese QML-definierten Typen von qmltc. kompiliert wurden. Gegenwärtig können Sie die Module QtQml und QtQuick sowie alle anderen QML-Module, die nur C++-Klassen enthalten, die in QML exponiert sind, zuverlässig verwenden.

Darüber hinaus gibt es einige weitere grundlegende Besonderheiten zu beachten:

  • Die QML-Module von Qt verlassen sich in der Regel auf C++-Bibliotheken, um die schwere Arbeit zu erledigen. Oft genug stellen diese Bibliotheken keine öffentliche C++-API zur Verfügung (da ihre primäre Verwendung durch QML erfolgt). Für die Benutzer von qmltc bedeutet dies, dass ihre Anwendungen gegen private Qt-Bibliotheken linken müssen.
  • Aufgrund der Natur der qmltc-Codegenerierung sind QML-Plugins für Kompilierungszwecke unbrauchbar. Stattdessen müssen QML-Module - die ein Plugin verwenden - sicherstellen, dass die Plugin-Daten zur Kompilierzeit zugänglich sind. Solche QML-Module würden dann über optionale Plugins verfügen. In den meisten Fällen können die Informationen zur Kompilierzeit durch eine Header-Datei (mit C++-Deklarationen) und eine linkbare Bibliothek (mit C++-Definitionen) bereitgestellt werden. Der Benutzercode ist (in der Regel über CMake) dafür verantwortlich, einen Pfad zur Header-Datei anzugeben und gegen die QML-Modulbibliothek zu linken.

Hinweis: Angesichts des Tech-Preview-Status des Compilers kann es vorkommen, dass Sie Fehler in qmltc, im generierten Code oder in einem anderen damit verbundenen Teil finden. Wir ermutigen Sie, in diesem Fall einen Fehlerbericht einzureichen.

© 2025 The Qt Company Ltd. Documentation contributions included herein are the copyrights of their respective owners. The documentation provided herein is licensed under the terms of the GNU Free Documentation License version 1.3 as published by the Free Software Foundation. Qt and respective logos are trademarks of The Qt Company Ltd. in Finland and/or other countries worldwide. All other trademarks are property of their respective owners.