이 페이지에서

QML 모듈 작성

CMake QML 모듈 API 를 사용하여 QML 모듈을 선언해야 합니다. 이를 통해 다음을 수행할 수 있습니다:

  • qmldir 및 *.qmltypes 파일을 생성합니다.
  • QML_ELEMENT 로 주석이 달린 C++ 타입을 등록합니다.
  • 동일한 모듈 내에서 QML 파일과 C++ 기반 타입을 결합합니다.
  • 모든 QML 파일에 대해 qmlcachegen을 호출합니다.
  • 모듈 내에서 QML 파일의 사전 컴파일된 버전을 사용합니다.
  • 물리적 파일 시스템과 리소스 파일 시스템 모두에 모듈을 제공합니다.
  • 백업 라이브러리와 선택적 플러그인을 생성합니다. 런타임에 플러그인이 로드되지 않도록 백업 라이브러리를 애플리케이션에 링크합니다.

위의 모든 작업은 개별적으로 구성할 수도 있습니다. 자세한 내용은 CMake QML 모듈 API를 참조하십시오.

하나의 바이너리에 여러 QML 모듈 포함

동일한 바이너리에 여러 QML 모듈을 추가할 수 있습니다. 각 모듈에 대한 CMake 타깃을 정의한 다음, 해당 타깃들을 실행 파일에 링크하십시오. 추가 타깃이 모두 정적 라이브러리인 경우, 결과물은 여러 QML 모듈을 포함하는 하나의 바이너리가 됩니다. 간단히 말해 다음과 같은 애플리케이션을 만들 수 있습니다:

myProject
    | - CMakeLists.txt
    | - main.cpp
    | - main.qml
    | - onething.h
    | - onething.cpp
    | - ExtraModule
        | - CMakeLists.txt
        | - Extra.qml
        | - extrathing.h
        | - extrathing.cpp

먼저, main.qml 파일에 Extra.qml의 인스턴스화가 포함되어 있다고 가정해 봅시다:

import ExtraModule
Extra { ... }

추가 모듈은 메인 프로그램에 링크할 수 있도록 정적 라이브러리여야 합니다. 따라서 ExtraModule/CMakeLists.txt에 다음과 같이 명시합니다:

# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause

qt_add_library(extra_module STATIC)
qt_add_qml_module(extra_module
    URI "ExtraModule"
    VERSION 1.0
    QML_FILES
        Extra.qml
    SOURCES
        extrathing.cpp extrathing.h
    RESOURCE_PREFIX /
)

이렇게 하면 두 개의 타깃이 생성됩니다. 백엔드 라이브러리용 ` extra_module `와 플러그인용 ` extra_moduleplugin `입니다. 플러그인도 정적 라이브러리이므로 런타임에 로드할 수 없습니다.

myProject/CMakeLists.txt 파일에서는 main.qml과 onething.h에 선언된 모든 타입이 속한 QML 모듈을 지정해야 합니다:

qt_add_executable(main_program main.cpp)

qt_add_qml_module(main_program
    VERSION 1.0
    URI myProject
    QML_FILES
        main.qml
    SOURCES
        onething.cpp onething.h

)

그런 다음, 추가 모듈을 위한 하위 디렉터리를 추가합니다:

add_subdirectory(ExtraModule)

추가 모듈의 링크가 올바르게 작동하도록 하려면 다음을 수행해야 합니다:

  • 추가 모듈에 심볼을 정의합니다.
  • 메인 프로그램에서 해당 심볼에 대한 참조를 생성합니다.

QML 플러그인에는 이 목적으로 사용할 수 있는 심볼이 포함되어 있습니다. Q_IMPORT_QML_PLUGIN 매크로를 사용하여 이 심볼에 대한 참조를 생성할 수 있습니다. main.cpp에 다음 코드를 추가하십시오:

#include <QtQml/QQmlExtensionPlugin>
Q_IMPORT_QML_PLUGIN(ExtraModulePlugin)

ExtraModulePlugin 는 생성된 플러그인 클래스의 이름입니다. 이 이름은 모듈 URI에 Plugin 가 추가된 형태로 구성됩니다. 그런 다음, 메인 프로그램의 CMakeLists.txt에서 백엔드 라이브러리가 아닌 플러그인을 메인 프로그램에 링크하십시오:

target_link_libraries(main_program PRIVATE extra_moduleplugin)

버전

QML은 컴포넌트와 모듈에 버전을 할당하는 복잡한 시스템을 갖추고 있습니다. 대부분의 경우 다음 방법을 통해 이 모든 것을 무시해야 합니다:

  1. import 문에 버전을 절대 추가하지 마십시오
  2. qt_add_q ml_module에서 버전을 절대 지정하지 마십시오
  3. QML_ADDED_IN_VERSION 또는 QT_QML_SOURCE_VERSIONS를 절대 사용하지 마십시오
  4. Q_REVISION 이나 다음의 ` REVISION() ` 속성을 절대 사용하지 마십시오: Q_PROPERTY
  5. 무격식 액세스를 피하십시오
  6. 임포트 네임스페이스를 아낌없이 사용하기

버전 관리는 이상적으로는 언어 자체 외부에서 처리되어야 합니다. 예를 들어, 서로 다른 QML 모듈 세트에 대해 별도의 가져오기 경로를 유지할 수 있습니다. 또는 운영 체제에서 제공하는 버전 관리 메커니즘을 사용하여 QML 모듈이 포함된 패키지를 설치하거나 제거할 수도 있습니다.

경우에 따라 Qt Qml 모듈은 임포트된 버전에 따라 다른 동작을 보일 수 있습니다. 특히, QML 컴포넌트에 속성이 추가되었는데 코드 내에 동일한 이름의 다른 속성에 대한 비정규화된 접근이 포함되어 있다면, 코드가 정상적으로 작동하지 않게 됩니다. 다음 예제에서 코드는 Qt 버전에 따라 다르게 동작합니다. 이는 ` topLeftRadius ` 속성이 Qt 6.7에서 추가되었기 때문입니다:

import QtQuick

Item {
    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // correct for Qt version < 6.7 but uses Rectangle's topLeftRadius in 6.7
        objectName: "top left radius:" + topLeftRadius
    }
}

이 경우의 해결책은 한정되지 않은 접근을 피하는 것입니다. qmllint를 사용하면 이러한 문제를 찾을 수 있습니다. 다음 예제는 의도한 속성에 안전하고 한정된 방식으로 접근합니다:

import QtQuick

Item {
    id: root

    // property you want to use
    property real topLeftRadius: 24

    Rectangle {

        // never mixes up topLeftRadius with unrelated Rectangle's topLeftRadius
        objectName: "top left radius:" + root.topLeftRadius
    }
}

QtQuick 의 특정 버전을 임포트하여 비호환성을 피할 수도 있습니다:

// make sure Rectangle has no topLeftRadius property
import QtQuick 6.6

Item {
    property real topLeftRadius: 24
    Rectangle {
        objectName: "top left radius:" + topLeftRadius
    }
}

버전 관리를 통해 해결되는 또 다른 문제는 서로 다른 모듈에서 임포트한 QML 컴포넌트가 서로를 가릴 수 있다는 점입니다. 다음 예제에서, 만약 MyModule 가 최신 버전에서 Rectangle 라는 이름의 컴포넌트를 도입한다면, 이 문서가 생성하는 Rectangle 는 더 이상 QQuickRectangle 가 아니라, MyModule 에서 도입된 새로운 Rectangle 가 될 것입니다.

import QtQuick
import MyModule

Rectangle {
    // MyModule's Rectangle, not QtQuick's
}

이러한 이름 충돌을 피하는 좋은 방법은 다음과 같이 QtQuick 및/또는 MyModule 를 타입 네임스페이스로 임포트하는 것입니다:

import QtQuick as QQ
import MyModule as MM

QQ.Rectangle {
   // QtQuick's Rectangle
}

또는, MyModule 을 고정된 버전으로 임포트하고, 새로운 컴포넌트가 QML_ADDED_IN_VERSION 또는 QT_QML_SOURCE_VERSIONS를 통해 올바른 버전 태그를 수신하는 경우에도 가림 현상을 피할 수 있습니다:

import QtQuick 6.6

// Types introduced after 1.0 are not available, like Rectangle for example
import MyModule 1.0

Rectangle {
    // QtQuick's Rectangle
}

이 방법이 작동하려면 MyModule 에 버전을 명시해야 합니다. 이때 유의해야 할 몇 가지 사항이 있습니다.

버전을 추가할 경우, 모든 곳에 추가해야 합니다

qt_add_qml_module에 VERSION 속성을 추가해야 합니다. 버전은 모듈에서 제공하는 최신 버전이어야 합니다. 동일한 메이저 버전의 이전 마이너 버전은 자동으로 등록됩니다. 이전 메이저 버전에 대해서는 아래를 참조하십시오.

모듈의 x.0 버전에서 도입되지 않은 모든 유형에 QML_ADDED_IN_VERSION 또는 QT_QML_SOURCE_VERSIONS를 추가해야 합니다. 여기서 x 는 현재 메이저 버전입니다.

버전 태그를 추가하는 것을 잊으면 해당 컴포넌트가 모든 버전에서 사용 가능해져 버전 관리가 무의미해집니다.

그러나 QML에 정의된 속성, 메서드 및 신호에는 버전을 추가할 방법이 없습니다. QML 문서에 버전을 지정하는 유일한 방법은 변경 사항마다 별도의 QT_QML_SOURCE_VERSIONS를 사용하여 새 문서를 추가하는 것입니다.

버전은 전이적이지 않습니다

모듈 A 의 컴포넌트가 다른 모듈 B 을 임포트하고 해당 모듈의 유형을 루트 요소로 인스턴스화하는 경우, 사용자가 A 의 어떤 버전을 임포트하든 상관없이 B 의 임포트 버전이 결과 컴포넌트에서 사용할 수 있는 속성에 적용됩니다.

모듈 A 에 버전 2.6 을 가진 파일 TypeFromA.qml 을 고려해 보겠습니다:

import B 2.7

// Exposes TypeFromB 2.7, no matter what version of A is imported
TypeFromB { }

이제 TypeFromA 를 사용하는 사용자를 생각해 봅시다:

import A 2.6

// This is TypeFromB 2.7.
TypeFromA { }

사용자는 버전 2.6 을 기대하지만, 실제로는 기본 클래스 TypeFromB 의 버전 2.7 을 받게 됩니다.

따라서 안전을 기하기 위해서는, 직접 속성을 추가할 때뿐만 아니라 임포트하는 모듈의 버전을 올릴 때에도 QML 파일을 복사하고 새로운 버전을 부여해야 합니다.

자격을 갖춘 액세스는 버전 관리를 따르지 않습니다

버전 관리는 유형의 멤버나 유형 자체에 대한 한정되지 않은 액세스에만 영향을 미칩니다. topLeftRadius 예제에서, this.topLeftRadius 라고 작성하면, Qt 6.7을 사용하는 경우 import QtQuick 6.6 라 하더라도 속성이 해결됩니다.

버전 및 개정판

QML_ADDED_IN_VERSION 및 Q_REVISION 의 두 인자 변형, 그리고 Q_PROPERTY 의 REVISION() 을 사용할 때는, QMetaMethod::revision 및 QMetaProperty::revision 에 노출된 metaobject's 리비전과 밀접하게 연동된 버전만 선언할 수 있습니다. 즉, 유형 계층 구조 내의 모든 유형은 동일한 버전 관리 체계를 따라야 합니다. 여기에는 상속받는 Qt 자체에서 제공하는 모든 유형도 포함됩니다.

qmlRegisterType 및 관련 함수를 사용하면 메타객체 리비전과 타입 버전 간의 임의의 매핑을 등록할 수 있습니다. 이 경우 Q_REVISION 의 단일 인자 형식과 Q_PROPERTY 의 REVISION 속성을 사용해야 합니다. 그러나 이 방법은 상당히 복잡하고 혼란스러울 수 있으므로 권장하지 않습니다.

동일한 모듈에서 여러 주요 버전을 내보내기

qt_add_qml_module은 기본적으로 VERSION 인자에 지정된 메이저 버전을 고려합니다. 개별 타입이 QT_QML_SOURCE_VERSIONS 또는 Q_REVISION 을 통해 추가된 특정 버전에서 다른 버전을 선언하더라도 마찬가지입니다. 모듈이 두 개 이상의 버전으로 제공되는 경우, 개별 QML 파일이 어떤 버전으로 제공될지 결정해야 합니다. 추가 주요 버전을 선언하려면 ` qt_add_qml_module `에 ` PAST_MAJOR_VERSIONS ` 옵션을 사용하거나, 개별 QML 파일의 ` QT_QML_SOURCE_VERSIONS ` 속성을 사용할 수 있습니다.

set_source_files_properties(Thing.qml
    PROPERTIES
        QT_QML_SOURCE_VERSIONS "1.4;2.0;3.0"
)

set_source_files_properties(OtherThing.qml
    PROPERTIES
        QT_QML_SOURCE_VERSIONS "2.2;3.0"
)

qt_add_qml_module(my_module
    URI MyModule
    VERSION 3.2
    PAST_MAJOR_VERSIONS
        1 2
    QML_FILES
        Thing.qml
        OtherThing.qml
        OneMoreThing.qml
    SOURCES
        everything.cpp everything.h
)

MyModule 는 메이저 버전 1, 2, 3에서 사용할 수 있습니다. 사용 가능한 최대 버전은 3.2입니다. x가 양수인 경우 1.x 또는 2.x 버전을 임의로 가져올 수 있습니다. Thing.qml 및 OtherThing.qml에는 명시적인 버전 정보를 추가했습니다. Thing.qml은 버전 1.4부터, OtherThing.qml은 버전 2.2부터 사용할 수 있습니다. 메이저 버전을 올릴 때 모듈에서 QML 파일을 제거할 수도 있으므로, 각 set_source_files_properties() 파일에서도 최신 버전을 명시해야 합니다. OneMoreThing.qml에는 명시적인 버전 정보가 없습니다. 즉, OneMoreThing.qml은 마이너 버전 0부터 모든 메이저 버전에서 사용할 수 있습니다.

이러한 설정으로 생성된 등록 코드는 각 메이저 버전에 대해 ` qmlRegisterModule()`를 사용하여 ` versions ` 모듈을 등록합니다. 이렇게 하면 모든 버전을 임포트할 수 있습니다.

사용자 정의 디렉터리 구조

QML 모듈을 구성하는 가장 쉬운 방법은 각 모듈의 URI를 이름으로 한 디렉터리에 모듈을 보관하는 것입니다. 예를 들어, My.Extra.Module 모듈은 이를 사용하는 애플리케이션에 상대적인 My/Extra/Module 디렉터리에 위치하게 됩니다. 이렇게 하면 런타임이나 어떤 도구로도 모듈을 쉽게 찾을 수 있습니다.

더 복잡한 프로젝트에서는 이 규칙이 너무 제한적일 수 있습니다. 예를 들어, 프로젝트의 루트 디렉터리가 복잡해지는 것을 방지하기 위해 모든 QML 모듈을 한 곳에 그룹화하고 싶을 수 있습니다. 또는 단일 모듈을 여러 애플리케이션에서 재사용하고 싶을 수도 있습니다. 이러한 경우에는 QT_QML_OUTPUT_DIRECTORY 를 RESOURCE_PREFIX 및 IMPORT_PATH와 함께 사용할 수 있습니다.

QML 모듈을 특정 출력 디렉터리에 모으려면(예: 빌드 디렉터리 QT_QML_OUTPUT_DIRECTORY 내의 "qml" 하위 디렉터리), 최상위 CMakeLists.txt에 다음을 설정하십시오:

set(QT_QML_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/qml)

QML 모듈의 출력 디렉터리가 새로운 위치로 이동합니다. 마찬가지로, qmllint 및 qmlcachegen 호출도 새로운 출력 디렉터리를 임포트 경로로 사용하도록 자동으로 조정됩니다. 새로운 출력 디렉터리는 기본 QML 임포트 경로의 일부가 아니므로, QML 모듈을 찾을 수 있도록 런타임에 명시적으로 추가해야 합니다.

물리적 파일 시스템에 대한 처리가 완료되었더라도, 리소스 파일 시스템 내에서 QML 모듈을 다른 위치로 이동하고 싶을 수 있습니다. 바로 이를 위해 RESOURCE_PREFIX 옵션이 제공됩니다. 각 qt_add_qml_module에서 이 옵션을 별도로 지정해야 합니다. 그러면 QML 모듈은 지정된 접두사 아래에 배치되며, URI에서 생성된 대상 경로가 그 뒤에 추가됩니다. 예를 들어, 다음 모듈을 살펴보겠습니다.

qt_add_qml_module(
    URI My.Great.Module
    VERSION 1.0
    RESOURCE_PREFIX /example.com/qml
    QML_FILES
        A.qml
        B.qml
)

이렇게 하면 리소스 파일 시스템에 example.com/qml/My/Great/Module 디렉터리가 추가되고, 위에 정의된 QML 모듈이 그 안에 배치됩니다. 모듈은 물리적 파일 시스템에서도 찾을 수 있으므로, QML 임포트 경로에 리소스 접두사를 반드시 추가해야 하는 것은 아닙니다. 하지만 대부분의 모듈에서 리소스 파일 시스템에서 로드하는 것이 물리적 파일 시스템에서 로드하는 것보다 빠르기 때문에, 일반적으로 QML 임포트 경로에 리소스 접두사를 추가하는 것이 좋습니다.

QML 모듈을 여러 임포트 경로가 있는 대규모 프로젝트에서 사용할 예정이라면, 추가 단계를 수행해야 합니다. 실행 시점에 임포트 경로를 추가하더라도 qmllint 와 같은 툴은 해당 경로에 접근할 수 없어 올바른 종속성을 찾지 못할 수 있습니다. IMPORT_PATH 를 사용하여 툴이 고려해야 할 추가 경로를 지정하십시오. 예를 들어:

qt_add_qml_module(
    URI My.Dependent.Module
    VERSION 1.0
    QML_FILES
        C.qml
    IMPORT_PATH "/some/where/else"
)

실행 시 파일 시스템 액세스 제거

모든 QML 모듈이 항상 리소스 파일 시스템에서 로드되는 경우, 애플리케이션을 단일 바이너리로 배포할 수 있습니다.

QTP0001 정책이 ` NEW`로 설정된 경우, ` qt_add_qml_module() `의 ` RESOURCE_PREFIX ` 인수는 기본적으로 ` /qt/qml/`로 설정되므로, 모듈은 리소스 파일 시스템의 ` :/qt/qml/ `에 배치됩니다. 이는 기본 QML 가져오기 경로의 일부이지만 Qt 자체에서는 사용되지 않습니다. 애플리케이션 내에서 사용할 모듈의 경우, 이곳이 올바른 위치입니다.

대신 사용자 정의 RESOURCE_PREFIX 를 지정한 경우, QML 임포트 경로에 해당 사용자 정의 리소스 접두사를 추가해야 합니다. 여러 개의 리소스 접두사를 추가할 수도 있습니다:

QQmlEngine qmlEngine;
qmlEngine.addImportPath(QStringLiteral(":/my/resource/prefix"));
qmlEngine.addImportPath(QStringLiteral(":/other/resource/prefix"));
// Use qmlEngine to load the main.qml file.

이는 타사 라이브러리를 사용할 때 모듈 이름 충돌을 방지하기 위해 필요할 수 있습니다. 그 외의 모든 경우에는 사용자 정의 리소스 접두사 사용을 권장하지 않습니다.

:/qt-project.org/imports/ 경로도 기본 QML 임포트 경로의 일부입니다. 서로 다른 프로젝트나 Qt 버전 간에 광범위하게 재사용되는 모듈의 경우, :/qt-project.org/imports/ 를 리소스 접두사로 사용하는 것이 허용됩니다. 다만, Qt 자체의 QML 모듈도 이 위치에 배치되어 있으므로, 이를 덮어쓰지 않도록 주의해야 합니다.

불필요한 임포트 경로는 추가하지 마십시오. 그렇지 않으면 QML 엔진이 모듈을 잘못된 위치에서 찾을 수 있습니다. 이로 인해 특정 환경에서만 재현되는 문제가 발생할 수 있습니다.

사용자 정의 QML 플러그인 통합

QML 모듈에 image provider 를 포함시키는 경우, QQmlEngineExtensionPlugin::initializeEngine() 메서드를 구현해야 합니다. 이는 결과적으로 자체 플러그인을 작성해야 함을 의미합니다. 이러한 사용 사례를 지원하기 위해 NO_GENERATE_PLUGIN_SOURCE를 사용할 수 있습니다.

자체 플러그인 소스를 제공하는 모듈을 예로 들어 보겠습니다:

qt_add_qml_module(imageproviderplugin
    VERSION 1.0
    URI "ImageProvider"
    PLUGIN_TARGET imageproviderplugin
    NO_PLUGIN_OPTIONAL
    NO_GENERATE_PLUGIN_SOURCE
    CLASS_NAME ImageProviderExtensionPlugin
    QML_FILES
        AAA.qml
        BBB.qml
    SOURCES
        moretypes.cpp moretypes.h
        myimageprovider.cpp myimageprovider.h
        plugin.cpp
)

myimageprovider.h에서 다음과 같이 이미지 제공자를 선언할 수 있습니다:

class MyImageProvider : public QQuickImageProvider
{
    [...]
};

그런 다음 plugin.cpp에서 ` QQmlEngineExtensionPlugin`를 다음과 같이 정의할 수 있습니다:

#include <myimageprovider.h>
#include <QtQml/qqmlextensionplugin.h>

class ImageProviderExtensionPlugin : public QQmlEngineExtensionPlugin
{
    Q_OBJECT
    Q_PLUGIN_METADATA(IID QQmlEngineExtensionInterface_iid)
public:
    void initializeEngine(QQmlEngine *engine, const char *uri) final
    {
        Q_UNUSED(uri);
        engine->addImageProvider("myimg", new MyImageProvider);
    }
};

이렇게 하면 이미지 제공자를 사용할 수 있게 됩니다. 플러그인과 백엔드 라이브러리는 모두 동일한 CMake 타깃인 `imageproviderplugin`에 포함됩니다. 이는 다양한 상황에서 링커가 모듈의 일부를 생략하지 않도록 하기 위함입니다.

플러그인이 이미지 제공자를 생성하므로, 더 이상 단순한 ` initializeEngine ` 함수를 갖지 않게 됩니다. 따라서 이 플러그인은 더 이상 선택 사항이 아닙니다.

Qt Qml 의 변경 사항, QML 모듈 현대화, QML 모듈을 CMake로 이식하기를참조하십시오 .

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