QML 유형 컴파일러

QML 타입 컴파일러( qmltc)는 Qt와 함께 제공되는 도구로, 사용자 코드의 일부로 미리 컴파일된 QML 타입을 C++ 타입으로 변환하는 데 사용됩니다. qmltc를 사용하면 QQmlComponent 기반 객체 생성에 비해 컴파일러에서 더 많은 최적화 기회를 얻을 수 있기 때문에 런타임 성능이 향상될 수 있습니다. qmltc는 Qt Quick Compiler 툴체인의 일부입니다.

설계상 qmltc는 사용자 대상 코드를 출력합니다. 이 코드는 C++ 애플리케이션에서 직접 활용해야 하며, 그렇지 않으면 아무런 이점이 없습니다. 이렇게 생성된 코드는 기본적으로 QQmlComponent 및 해당 API를 대체하여 QML 문서에서 개체를 만듭니다. 자세한 내용은 QML 애플리케이션에서 qmltc 사용생성된 출력 기본사항에서 확인할 수 있습니다.

qmltc를 사용하려면 다음과 같이 하세요:

  • 애플리케이션에 적합한 QML 모듈을 만듭니다.
  • 예를 들어 CMake API를 통해 qmltc를 호출합니다.
  • #include 애플리케이션 소스 코드에 생성된 헤더 파일을 추가합니다.
  • 생성된 유형의 객체를 인스턴스화합니다.

이 워크플로에서는 일반적으로 빌드 프로세스 중에 qmltc가 실행됩니다. 따라서 오류나 경고 또는 qmltc가 아직 지원하지 않는 구성으로 인해 qmltc가 QML 문서를 거부하면 빌드 프로세스가 실패합니다. 이는 QML 모듈 생성 중에 린팅 타깃의 자동 생성을 활성화한 다음 이를 '빌드'하여 qmllint를 실행하려고 시도할 때 발생하는 오류와 유사합니다.

경고: qmltc는 현재 테크 프리뷰 단계에 있으며 임의의 QML 프로그램을 컴파일하지 못할 수 있습니다(자세한 내용은 알려진 제한 사항 참조). qmltc가 실패하면 애플리케이션에서 qmltc 출력을 적절하게 사용할 수 없으므로 아무 것도 생성되지 않습니다. 프로그램에 오류(또는 해결할 수 없는 경고)가 있는 경우 컴파일이 가능하도록 오류를 수정해야 합니다. 일반적인 규칙은 모범 사례를 준수하고 qmllint의 조언을 따르는 것입니다.

참고: qmltc 는 생성된 C++ 가 과거 또는 향후 버전, 심지어 패치 버전 간에 API, 소스 또는 바이너리 호환성을 유지한다는 것을 보장하지 않습니다. 또한 Qt의 QML 모듈을 사용하여 qmltc로 컴파일된 앱은 비공개 Qt API에 대한 링크가 필요합니다. 이는 Qt의 QML 모듈이 주로 QML을 통해 사용되기 때문에 일반적으로 공용 C++ API를 제공하지 않기 때문입니다.

QML 애플리케이션에서 qmltc 사용

빌드 시스템 관점에서 볼 때, qmltc 컴파일을 추가하는 것은 qml 캐시 생성을 추가하는 것과 크게 다르지 않습니다. 순진하게 빌드 프로세스는 다음과 같이 설명할 수 있습니다:

실제 컴파일 프로세스는 훨씬 더 까다롭지만, 이 다이어그램은 qmltc가 사용하는 핵심 구성 요소인 QML 파일 자체와 qmltypes 정보가 있는 qmldir을 캡처합니다. 간단한 애플리케이션은 일반적으로 다소 원시적인 qmldir을 가지고 있지만 일반적으로 qmldir은 복잡할 수 있으며, qmltc가 올바른 QML-C++ 변환을 수행하기 위해 의존하는 필수적이고 잘 포장된 유형 정보를 제공할 수 있습니다.

그럼에도 불구하고 qmltc의 경우 빌드 단계를 추가하는 것만으로는 충분하지 않습니다. 애플리케이션 코드도 QQmlComponent 또는 그 상위 수준의 대체 클래스 대신 qmltc 생성 클래스를 사용하도록 수정해야 합니다.

qmltc로 QML 코드 컴파일하기

Qt 6부터는 다양한 컴포넌트를 빌드할 때 CMake를 사용합니다. 사용자 프로젝트도 CMake를 사용하여 Qt를 사용하여 컴포넌트를 빌드할 수 있으며, 그렇게 하는 것이 좋습니다. 프로젝트에 즉시 사용 가능한 qmltc 컴파일 지원을 추가하려면 이 흐름이 적절한 QML 모듈과 해당 인프라를 중심으로 이루어지기 때문에 CMake 기반 빌드 흐름도 필요합니다.

애플리케이션에 대한 QML 모듈 생성의 일부로 전용 CMake API를 사용하는 것이 qmltc 컴파일을 쉽게 추가할 수 있는 방법입니다. 간단한 애플리케이션 디렉토리 구조를 생각해 보세요:

.
├── 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

그러면 CMake 코드는 일반적으로 다음과 비슷하게 보일 것입니다:

# 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)

생성된 C++ 사용

QQmlComponent 인스턴스화의 경우와 달리, C++ 코드인 qmltc의 출력은 애플리케이션에서 직접 사용합니다. 일반적으로 C++에서 새 객체를 생성하는 것은 QQmlComponent::create()를 통해 새 객체를 생성하는 것과 동일합니다. 일단 생성된 객체는 C++에서 조작하거나 예를 들어 QQuickWindow 와 결합하여 화면에 그릴 수 있습니다.

컴파일된 유형이 일부 필수 속성을 노출하는 경우 `qmltc`는 생성된 객체의 생성자에서 해당 속성에 대한 초기 값을 요구합니다.

또한 컴포넌트의 속성에 대한 초기 값을 설정하는 콜백을 qmltc 객체의 생성자에 제공할 수 있습니다.

myApp.qml 파일이 주어지면 애플리케이션 코드(두 경우 모두)는 일반적으로 다음과 같이 보입니다:

#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 엔진

생성된 코드는 QQmlEngine 을 사용하여 QML 문서의 동적 부분(주로 JavaScript 코드)과 상호 작용합니다. 이를 위해 특별한 설정이 필요하지 않습니다. qmltc로 생성된 클래스 객체의 생성자에 전달된 QQmlEngine 인스턴스는 QQmlComponent(engine) 과 마찬가지로 올바르게 작동해야 합니다. 이는 또한 QML 동작에 영향을 주는 QQmlEngine methods 을 사용할 수 있음을 의미합니다. 하지만 주의할 점이 있습니다. QQmlComponent -기반 객체 생성과는 달리, 코드를 C++로 컴파일할 때 qmltc 자체는 QQmlEngine의존하지 않습니다. 예를 들어, 일반적으로 검색할 추가 가져오기 경로를 생성하는 QQmlEngine::addImportPath("/foo/bar/") 은 사전 qmltc 절차에 의해 완전히 무시됩니다.

참고: qmltc 컴파일에 가져오기 경로를 추가하려면 CMake 명령의 관련 인수를 대신 사용하는 것이 좋습니다.

일반적으로 다음과 같이 생각할 수 있습니다. QQmlEngine 은 애플리케이션 프로세스가 실행되는 반면, qmltc 는 애플리케이션이 컴파일되기도 전에 작동하므로 그렇지 않습니다. qmltc는 애플리케이션의 C++ 소스 코드를 인트로스펙트하지 않기 때문에 사용자가 수행하는 특정 종류의 QML 조작에 대해 알 수 있는 방법이 없습니다. QQmlEngine 및 관련 런타임 루틴을 사용하여 QML에 유형을 노출하고 가져오기 경로 등을 추가하는 대신, 실제로는 제대로 작동하는 QML 모듈을 만들고 선언적 QML 유형 등록을 사용해야 합니다.

경고: qmltc가 QQmlEngine 와 긴밀히 협력하여 C++ 코드를 생성하더라도 생성된 클래스는 QML에 더 이상 노출될 수 없으며 QQmlComponent 을 통해 사용할 수 없습니다.

생성된 출력 기본 사항

qmltc 는 기존 QML 실행 모델과 호환되는 것을 목표로 합니다. 이는 생성된 코드가 내부 QQmlComponent 설정 로직과 거의 동일하다는 것을 의미하므로 해당 QML 문서를 시각적으로 검사하여 현재와 동일한 방식으로 QML 유형의 동작, 의미 및 API를 이해할 수 있어야 한다는 뜻입니다.

그러나 애플리케이션에서 C++ 쪽의 qmltc 출력을 직접 사용해야 하기 때문에 생성된 코드는 여전히 다소 혼란스러울 수 있습니다. 생성된 코드에는 두 가지 부분이 있습니다: CMake 빌드 파일 구조와 생성된 C++ 형식입니다. 전자는 qmltc의 CMake API에서 다루고 후자는 여기에서 다룹니다.

hello 프로퍼티, 해당 프로퍼티를 출력하는 함수, 해당 프로퍼티의 객체가 생성될 때 발생하는 신호가 있는 간단한 HelloWorld 유형을 예로 들어 보겠습니다:

// 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();
}

이 QML 유형의 C++ 대안을 제공할 때, C++ 클래스에는 QML 전용 메타 객체 시스템 매크로, hello 프로퍼티에 대한 Q_PROPERTY 장식, Q_INVOKABLE C++ 인쇄 함수 및 일반 Qt 신호 정의가 필요할 것입니다. 마찬가지로, qmltc는 주어진 HelloWorld 형을 대략 다음과 같이 변환합니다:

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);

    // ...
};

생성된 타입의 구체적인 세부 사항은 다를 수 있지만, 보편적인 측면은 그대로 유지됩니다. 예를 들어

  • 문서 내의 QML 유형은 컴파일러에서 볼 수 있는 정보에 따라 C++ 유형으로 변환됩니다.
  • 프로퍼티는 Q_PROPERTY 선언을 사용하여 C++ 프로퍼티로 변환됩니다.
  • JavaScript 함수는 Q_INVOKABLE C++ 함수가 됩니다.
  • QML 신호는 C++ Qt 신호로 변환됩니다.
  • QML 열거형은 Q_ENUM 선언을 사용하여 C++ 열거형으로 변환됩니다.

추가 세부 사항은 qmltc 클래스 이름을 생성하는 방식입니다. 주어진 QML 유형의 클래스 이름은 해당 유형을 정의하는 QML 문서에서 자동으로 추론됩니다. 확장자가 없는 QML 파일 이름(첫 번째 ., 기본 이름이라고도 함)까지가 클래스 이름이 됩니다. 파일 이름 대소문자는 유지됩니다. 따라서 HelloWorld.qmlclass HelloWorld 이 되고 helloWoRlD.qmlclass helloWoRlD 이 됩니다. QML 규칙에 따라 QML 문서 파일 이름이 소문자로 시작하면 생성된 C++ 클래스는 익명으로 간주되고 QML_ANONYMOUS 로 표시됩니다.

현재로서는 생성된 코드를 C++ 애플리케이션 측에서 바로 사용할 수 있지만 일반적으로 생성된 API에 대한 호출을 제한해야 합니다. 대신, 간단한 객체 인스턴스화를 위해 qmltc에서 생성된 클래스를 사용하여 QML/JavaScript로 애플리케이션 로직을 구현하고 QML에 노출된 C++ 유형을 직접 작성하는 것을 선호하세요. 생성된 C++를 사용하면 해당 유형의 QML 정의 요소에 직접(그리고 일반적으로 더 빠르게) 액세스할 수 있지만 이러한 코드를 이해하는 것은 어려울 수 있습니다.

알려진 제한 사항

많은 일반적인 QML 기능을 다루고 있음에도 불구하고, qmltc는 아직 개발 초기 단계에 있으며 아직 지원되지 않는 기능도 있습니다.

QML 정의 유형(예: QtQuick.Controls)으로 구성된 가져온 QML 모듈은 qmltc로 컴파일되었더라도 올바르게 컴파일되지 않을 수 있습니다. 현재로서는 QtQmlQtQuick 모듈은 물론 QML에 노출된 C++ 클래스만 포함된 다른 모든 QML 모듈을 안정적으로 사용할 수 있습니다.

이 외에도 고려해야 할 몇 가지 근본적인 특성이 있습니다:

  • Qt의 QML 모듈은 일반적으로 무거운 작업을 수행하기 위해 C++ 라이브러리에 의존합니다. 대부분의 경우 이러한 라이브러리는 공용 C++ API를 제공하지 않습니다(주요 용도가 QML을 통한 것이기 때문입니다). qmltc 사용자의 경우, 이는 앱이 비공개 Qt 라이브러리에 연결해야 한다는 것을 의미합니다.
  • qmltc 코드 생성의 특성으로 인해 QML 플러그인은 컴파일 목적으로 사용할 수 없습니다. 대신 플러그인을 사용하는 QML 모듈은 컴파일 시 플러그인 데이터에 액세스할 수 있는지 확인해야 합니다. 그런 다음 이러한 QML 모듈에는 선택적 플러그인이 있습니다. 대부분의 경우 컴파일 시 정보는 헤더 파일(C++ 선언 포함)과 링크 가능한 라이브러리(C++ 정의 포함)를 통해 제공될 수 있습니다. 사용자 코드는 헤더 파일에 대한 경로를 포함하고 QML 모듈 라이브러리에 대한 링크를 담당하는 역할을 합니다(보통 CMake를 통해).

참고: 컴파일러의 기술 미리보기 상태를 고려할 때, 생성된 코드 또는 기타 관련 부분에서 qmltc에 버그가 발생할 수도 있습니다. 이 경우 버그 리포트를 제출하는 것이 좋습니다.

© 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.