Debugging QML Applications

2025-05-17

以下に主なデバッグ手法を説明します。

Qt Creatorのデバッグモード

Qt CreatorはQMLのデバッグに特化した機能を提供しています。

  • 呼び出しスタック (Call Stack)

    • 現在の実行パス(どの関数がどの関数を呼び出したか)を確認できます。
  • ローカル変数とプロパティの表示

    • デバッグ中に、現在のスコープにあるQMLプロパティやJavaScript変数の値を確認できます。Qt Creatorの「ローカル」ビューや「コンソール」ビューで確認できます。
  • ステップ実行

    • 実行が一時停止した状態で、以下の操作が可能です。
      • ステップオーバー (F10)
        現在の行を実行し、次の行に進みます。関数呼び出しがあっても関数の中には入りません。
      • ステップイン (F11)
        現在の行を実行し、関数呼び出しがある場合はその関数の中に入ります。
      • ステップアウト (Shift+F11)
        現在の関数から抜け出し、呼び出し元に戻ります。
      • カーソル行まで実行 (Ctrl+F10)
        カーソルがある行まで実行を再開します。
  • ブレークポイント

    • QMLファイルやJavaScriptファイル内の特定の行にブレークポイントを設定できます。ブレークポイントを設定するには、行番号の左側をクリックするか、F9キー(macOSではF8キー)を押します。
    • アプリケーションがその行に到達すると実行が一時停止し、現在の変数の値などを確認できます。
    • Qt Creatorの左下にある「デバッグ開始」ボタン(通常は緑色の再生ボタンの横にある虫のアイコン)をクリックするか、F5キーを押すと、アプリケーションがデバッグモードで起動します。
    • アプリケーションの実行設定で「QML debugging and profiling」を有効にする必要があります。

QMLコンソールへの出力 (qmlRegisterType)

QMLコード内でconsole.log()関数を使用すると、デバッグ情報をQt Creatorのアプリケーション出力ペインやシステムのコンソールに出力できます。これは、特定の変数の値やコードの実行フローを確認するのに非常に便利です。

// 例: QMLファイル内でconsole.log()を使用
import QtQuick 2.0
import QtQuick.Window 2.0

Window {
    width: 640
    height: 480
    visible: true
    title: "QML Debug Example"

    Rectangle {
        width: 100
        height: 100
        color: "red"

        MouseArea {
            anchors.fill: parent
            onClicked: {
                console.log("Rectangle clicked!"); // ここでログ出力
                console.log("Current width:", parent.width); // プロパティの値も出力可能
            }
        }
    }
}

QMLプロファイラー

Qt CreatorにはQMLプロファイラーが搭載されており、アプリケーションのパフォーマンスボトルネックを特定するのに役立ちます。

  • グラフィックビュー
    シーングラフの構造や、各要素のサイズ、位置などを視覚的に確認できます。
  • QMLトレース
    QMLプロファイラーは、QMLオブジェクトの作成、プロパティの変更、バインディングの評価、関数呼び出しなどのイベントを記録し、タイムラインで視覚化します。これにより、UIのどこでパフォーマンスの問題が発生しているかを把握できます。

QMLホットリロード (Live Reload)

開発中のQMLファイルを保存すると、アプリケーションを再起動することなく、UIの変更がすぐに反映される機能です。これは、UIのレイアウトやスタイルの調整を素早く繰り返す際に非常に効率的です。ただし、この機能は厳密にはデバッグツールではありませんが、開発サイクルを高速化し、問題の早期発見に役立ちます。

QMLとC++を組み合わせてアプリケーションを開発する場合、両方のレイヤーを同時にデバッグすることが可能です。

  • ブレークポイントの共有
    Qt Creatorでは、C++コードとQMLコードの両方にブレークポイントを設定し、シームレスにデバッグを行うことができます。
  • C++側でQMLエンジンを準備する際の設定
    QML_DEBUG環境変数を設定するか、Qtのビルド時にqml_debug_buildオプションを有効にする必要があります。


デバッグが開始されない/ブレークポイントが当たらない

これはQMLデバッグで最もよくある問題の一つです。

  • トラブルシューティング

    1. Qt Creatorの設定確認
      • プロジェクト設定 (Projects タブ) に移動します。
      • 使用しているビルドキット (通常は「Desktop Qt x.x.x」のようなもの) を選択します。
      • Build > QML debugging and profiling のチェックボックスがオンになっていることを確認します。
      • Run > Debugger Settings > QML debuggerAutomatic または Enabled になっていることを確認します。
    2. プロジェクトの再ビルド
      • 設定を変更した後、プロジェクトを「クリーン」してから「再ビルド」してください (Build > Clean AllBuild > Rebuild Project)。これによってデバッグシンボルが確実に生成されます。
    3. QML_DEBUG 環境変数
      • C++でQMLエンジンを初期化している場合、main.cppQQmlDebuggingEnabler enabler; の行を追加するか、QT_QML_DEBUG マクロを定義する必要があります。
      • CMakeLists.txtの場合:
        target_compile_definitions(${PROJECT_NAME} PRIVATE $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>)
        
      • .proファイルの場合:
        CONFIG += qml_debug
        
    4. 既存のアプリケーションインスタンスの終了
      • デバッグ対象のアプリケーションがすでにバックグラウンドで実行されていないことを確認します。タスクマネージャー(Windows)やアクティビティモニタ(macOS)でプロセスを終了させてください。
    5. Qt Creatorのアップデート
      • Qt Creatorのバージョンが古い場合、QMLデバッグ機能が不安定なことがあります。最新版にアップデートを検討してください。
    • QMLデバッグが有効になっていない。
    • ビルド設定や実行設定が正しくない。
    • QMLファイルやJavaScriptファイルにシンボル情報が適切に含まれていない。
    • アプリケーションが複数のインスタンスで実行されている(デバッガーが正しいポートに接続できない)。
    • Qt CreatorのバージョンやQtのバージョンが古い、または互換性がない。
    • CMakeLists.txtまたは.proファイルにQMLデバッグ用の定義が欠けている。

"QML package not found" / "XXX is not a type" エラー

QMLモジュールやカスタムコンポーネントがQMLエンジンに見つけられない場合に発生します。

  • トラブルシューティング

    1. import パスの確認
      • QMLファイル内で import 文が正しいパスを指しているか確認します。相対パスの場合は、そのQMLファイルの親ディレクトリからの相対パスになります。
      • 例: import "MyCustomComponents" のようにモジュール名でインポートする場合、そのモジュールがQMLエンジンに登録されている必要があります。
    2. リソースファイル (.qrc) の確認
      • プロジェクトのリソースファイル (.qrc) にQMLファイルが正しく含まれているか確認します。Qt Creatorで qml.qrc などのリソースファイルを右クリックし、「Add Existing Files...」で追加します。
    3. QMLインポートパスの追加
      • C++コードでQMLエンジン (例: QQmlApplicationEngine) を使用している場合、engine.addImportPath("path/to/your/qml/modules"); のように明示的にインポートパスを追加できます。
    4. qmldir ファイルの利用
      • カスタムQMLコンポーネントをモジュールとして提供する場合、そのディレクトリ内に qmldir ファイルを作成し、コンポーネントを定義します。
        // MyCustomComponents/qmldir
        module MyCustomComponents
        MyButton 1.0 MyButton.qml
        
      • その後、QMLファイルで import MyCustomComponents 1.0 のようにインポートします。
    5. QML_IMPORT_TRACE 環境変数
      • この環境変数を設定すると、QMLエンジンがモジュールを解決する際のデバッグ情報がコンソールに出力されます。どのパスを検索し、どこで失敗したかがわかります。
      • Qt Creatorの「Run」設定で環境変数として追加できます。
  • 原因

    • import パスが正しくない。
    • カスタムQMLファイルのパスがQMLエンジンに登録されていない。
    • QMLモジュールが正しくインストールされていない、または利用できない。
    • qmldir ファイルが適切に記述されていない。

バインディングエラー/プロパティ変更の通知漏れ

QMLのバインディング(プロパティ間の自動同期)が期待通りに動作しない、またはプロパティの変更がUIに反映されない。

  • トラブルシューティング

    1. console.log() を活用
      • バインディングの前後で関係するプロパティの値を console.log() で出力し、変化を追跡します。
      • JavaScript関数内でプロパティを変更する際も、変更前と変更後の値を出力すると原因特定に役立ちます。
    2. Qt Creatorの「Application Output」
      • QMLエンジンはバインディングのエラーや警告を「Application Output」に出力します。ここを注意深く確認してください。例えば、「QML Binding: Property 'xxx' is not defined on type 'yyy'」のようなメッセージが表示されます。
    3. C++ Q_PROPERTY の確認
      • C++オブジェクトをQMLに公開している場合、Q_PROPERTYマクロに NOTIFY シグナルが定義されていることを確認します。これが無いと、C++側でプロパティが変更されてもQML側に通知されず、UIが更新されません。
      • 例: Q_PROPERTY(QString myString READ myString WRITE setMyString NOTIFY myStringChanged)
    4. QMLデバッガーの「プロパティ」ビュー
      • デバッグ中に、Qt Creatorの「プロパティ」ビューでQMLオブジェクトの現在のプロパティ値をリアルタイムで確認できます。
  • 原因

    • バインディング式が構文的に誤っている。
    • 存在しないプロパティを参照している。
    • C++側でQ_PROPERTYが正しく定義されていない(NOTIFYシグナルがないなど)。
    • JavaScriptの変数がQMLのプロパティと正しくバインドされていない。

パフォーマンスの問題/UIのちらつき

アプリケーションの動作が遅い、またはUIの更新が滑らかでない。

  • トラブルシューティング

    1. QMLプロファイラーの使用
      • Qt CreatorのQMLプロファイラー (Analyze > QML Profiler) を使用して、ボトルネックを特定します。
      • 「QML Trace」ビューで、プロパティの変更、バインディングの評価、描画イベントなどの発生頻度と時間を視覚的に確認できます。
      • 「Call Tree」ビューで、時間がかかっているJavaScript関数やバインディングを特定します。
    2. バインディングの最適化
      • 不必要なバインディングを避ける。
      • 複雑なバインディングはJavaScript関数に分割し、キャッシュ可能な部分を考慮する。
      • Binding 要素を使用して、条件付きでバインディングを有効/無効にする。
    3. LoaderやRepeaterの最適化
      • Loader でコンポーネントを遅延ロードする。
      • Repeater で大量のアイテムを表示する場合、model のデータ構造やアイテムの複雑さを簡素化する。
    4. 非同期処理
      • 重い処理はC++で実装し、QMLからは非同期で呼び出すようにする。
      • Qt.callLater()WorkerScript を使用して、UIスレッドをブロックしないようにする。
    5. QMLキャッシュの利用
      • QMLコンポーネントのキャッシュを有効にする (QQmlApplicationEngine::setBaseUrl()など)。
  • 原因

    • 過剰なバインディングや複雑なバインディング式。
    • QMLオブジェクトの不必要な再生成。
    • C++とQML間の頻繁なデータのやり取り(特に同期呼び出し)。
    • 重い計算やネットワーク操作をUIスレッドで行っている。
    • 大量の要素を一度に生成・描画している。

クラッシュ/フリーズ

アプリケーションが予期せず終了したり、応答しなくなる。

  • トラブルシューティング

    1. C++デバッガーの活用
      • QMLアプリケーションでも、根本的な原因がC++側にある場合が多いです。Qt CreatorでC++デバッガーも同時にアタッチし、C++のコードにブレークポイントを設定してステップ実行します。
    2. Qt Creatorの「Issues」タブ
      • クラッシュ時にQt Creatorの「Issues」タブにクラッシュレポートやバックトレースが表示されることがあります。ここからC++側のエラーを特定できる場合があります。
    3. 無限ループ/再帰バインディングの確認
      • プロパティAがプロパティBに依存し、プロパティBがプロパティAに依存するような循環参照がないか確認します。QMLプロファイラーでも検出可能です。
    4. console.exception()
      • JavaScriptの例外をキャッチし、スタックトレースをコンソールに出力する console.exception() を利用することもできます。
  • 原因

    • C++コードでのメモリリークやポインタの問題がQMLに影響している。
    • 無限ループや再帰的なバインディング。
    • QMLからのC++オブジェクトへの無効なアクセス。
    • Qt Creatorのデバッガー自体が不安定な場合。
  • ドキュメントの参照
    Qtの公式ドキュメント (qt.io/documentation/) は非常に充実しています。QMLデバッグに関するセクションや、関連するクラスのドキュメントを定期的に参照しましょう。
  • シンプルなケースで再現
    問題が発生した場合は、その問題を再現する最小限のQMLコードとC++コードを作成し、切り分けを行います。
  • 詳細なログ出力
    積極的に console.log() を使用し、プログラムの実行フロー、変数の値、イベントの発生などを詳細に記録します。


console.log() を使ったデバッグ

最も基本的なデバッグ手法です。QMLコードの特定の場所で変数の値や実行フローを確認するのに使います。

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML Debug Console Log Example")

    // ユーザーが入力するテキスト
    property string inputText: ""

    // クリック回数をカウントするプロパティ
    property int clickCount: 0

    Rectangle {
        id: myRect
        width: 200
        height: 100
        color: "lightblue"
        anchors.centerIn: parent

        Text {
            anchors.centerIn: parent
            text: "Click Me!"
        }

        MouseArea {
            anchors.fill: parent
            onClicked: {
                clickCount++;
                console.log("Rectangleがクリックされました。現在のクリック数:", clickCount); // デバッグ出力
                console.debug("x座標:", mouseX, "y座標:", mouseY); // より詳細なデバッグ出力
                // 条件付きのデバッグ出力
                if (clickCount % 5 === 0) {
                    console.warn("5回クリックされました!"); // 警告メッセージとして出力
                }
            }
        }
    }

    TextInput {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: myRect.bottom
        anchors.topMargin: 20
        placeholderText: "何か入力してください..."
        onTextChanged: {
            inputText = text;
            console.info("入力テキストが変更されました:", inputText); // 情報メッセージとして出力
        }
    }

    Button { // QtQuick.Controls 2.15 が必要
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: textInput.bottom
        anchors.topMargin: 20
        text: "エラーをシミュレート"
        onClicked: {
            // 意図的にエラーを発生させる
            try {
                var nonExistentObject.property = "value"; // 未定義のオブジェクトへのアクセス
            } catch (e) {
                console.error("エラーが発生しました:", e.message); // エラーメッセージとして出力
                console.exception(e); // スタックトレースを含む例外情報を出力
            }
        }
    }
}

このコードを実行し、Qt Creatorの「Application Output」ウィンドウを見ると、console.logconsole.debugconsole.infoconsole.warnconsole.errorconsole.exception の出力が表示されます。

C++からQMLデバッグを有効にする設定

Qt Creatorのプロジェクト設定で「QML debugging and profiling」を有効にするのが一般的ですが、C++コード側で明示的にデバッグを有効にする方法もあります。これは特にコマンドラインから実行する場合や、Qt Creator以外のIDEを使用する場合に便利です。

CMakeLists.txt (CMakeを使用する場合)

# CMakeLists.txt
cmake_minimum_required(VERSION 3.16)
project(QmlDebugExample LANGUAGES CXX QML)

set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)

find_package(Qt6 6.5 COMPONENTS Core Gui Qml Quick REQUIRED)

# QMLデバッグを有効にするためのコンパイル定義
target_compile_definitions(${PROJECT_NAME} PRIVATE
    $<TARGET_EXISTS:Qt6::Qml::QmlDiagnostics>
        ? $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>
        : ""
)

qt_add_executable(${PROJECT_NAME}
    main.cpp
    main.qml
)

qt_add_qml_module(${PROJECT_NAME}
    URI QmlDebugExample
    VERSION 1.0
    QML_FILES
        main.qml
)

target_link_libraries(${PROJECT_NAME} PRIVATE
    Qt6::Core
    Qt6::Gui
    Qt6::Qml
    Qt6::Quick
    # QMLデバッグモジュールをリンク(オプション、診断サービスのため)
    $<TARGET_EXISTS:Qt6::Qml::QmlDiagnostics> ? Qt6::Qml::QmlDiagnostics : ""
)

myproject.pro (qmakeを使用する場合)

# myproject.pro
QT += quick

CONFIG += c++17
CONFIG += qml_debug # QMLデバッグを有効にする行

SOURCES += main.cpp
RESOURCES += qml.qrc # QMLファイルをリソースとして追加する場合

# QMLファイルを直接指定する場合
QML_FILES += \
    main.qml

main.cpp (C++側でQMLデバッグを明示的に有効にする)

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlDebuggingEnabler> // このヘッダーが必要

int main(int argc, char *argv[])
{
    // QMLデバッグを有効にするためのオブジェクト (Qt 5以降)
    // デバッグビルドでのみ有効にするのが一般的です
#ifdef QT_QML_DEBUG
    QQmlDebuggingEnabler enabler;
#endif

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    const QUrl url(u"qrc:/QmlDebugExample/main.qml"_qs); // qrc:で始まるパスはリソースファイルからロードされる
    // QMLファイルがリソースに含まれていない場合は、以下のようになります
    // const QUrl url(u"main.qml"_qs);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
        &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

このmain.cppでは、QT_QML_DEBUGマクロが定義されている場合(通常はDebugビルドで自動的に定義されます)、QQmlDebuggingEnablerオブジェクトが作成され、QMLデバッグが有効になります。

QMLカスタムコンポーネントのインポートパスとデバッグ

カスタムQMLファイルをモジュールとして整理し、それをデバッグする際の例です。

プロジェクト構造

myproject/
├── CMakeLists.txt
├── main.cpp
├── main.qml
└── MyComponents/
    ├── qmldir
    └── CustomButton.qml

MyComponents/qmldir

module MyComponents
CustomButton 1.0 CustomButton.qml

MyComponents/CustomButton.qml

import QtQuick 2.15
import QtQuick.Controls 2.15 // Buttonを使用するため

Button {
    id: root
    property string customText: "Default Button"
    text: customText
    width: 150
    height: 40

    onClicked: {
        console.log("CustomButton がクリックされました!");
        console.log("カスタムテキスト:", root.customText);
        // ここにブレークポイントを設定できます
    }
}

main.qml

import QtQuick 2.15
import QtQuick.Window 2.15
import MyComponents 1.0 // カスタムモジュールをインポート

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML Custom Component Debug Example")

    CustomButton {
        anchors.centerIn: parent
        customText: "Hello QML Debug!"
        onClicked: {
            console.log("main.qml から CustomButton のクリックイベントを検出");
        }
    }
}

main.cpp (インポートパスの追加)

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlDebuggingEnabler>

int main(int argc, char *argv[])
{
#ifdef QT_QML_DEBUG
    QQmlDebuggingEnabler enabler;
#endif

    QGuiApplication app(argc, argv);

    QQmlApplicationEngine engine;
    // カスタムコンポーネントのパスを追加
    // この例では "MyComponents" ディレクトリが main.cpp と同じ階層にあると仮定
    engine.addImportPath("./MyComponents");
    // もしリソースファイル経由でQMLを読み込む場合は qrc:/MyComponents のように設定

    const QUrl url(u"main.qml"_qs);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
        &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

この例では、engine.addImportPath("./MyComponents"); を使用して、QMLエンジンがカスタムモジュールを見つけられるようにしています。これにより、import MyComponents 1.0 が正しく機能します。

QMLプロファイラー自体はコード例というよりは、Qt Creatorの機能です。しかし、プロファイリングに影響を与えるQMLコードの書き方があります。

パフォーマンスを考慮しないQML (プロファイリング対象)

// このようなコードはプロファイラーでボトルネックとして表示される可能性がある
import QtQuick 2.15
import QtQuick.Window 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML Profiler Example (Bad)")

    // スライダーの値に応じて大量のRectangleを作成し、色を更新
    Slider { // QtQuick.Controls 2.15 が必要
        id: countSlider
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: parent.top
        anchors.topMargin: 20
        width: parent.width * 0.8
        from: 1
        to: 1000
        value: 10 // 初期値は少なめに

        onValueChanged: {
            // スライダーを動かすたびに大量のオブジェクトが生成・破棄される可能性がある
            // これがプロファイラーで「Item Creation」のスパイクとして表示される
            rectRepeater.count = Math.floor(value);
        }
    }

    // 大量のRectangleをRepeaterで生成
    Repeater {
        id: rectRepeater
        model: countSlider.value // ここを動的にすると、モデル変更のたびに再生成
        delegate: Rectangle {
            width: 10
            height: 10
            color: Qt.rgba(Math.random(), Math.random(), Math.random(), 1)
            x: Math.random() * (parent.width - width)
            y: Math.random() * (parent.height - height)
        }
    }
}

この例では、スライダーの値を変更するたびにRepeaterのモデルが変更され、多くのRectangle要素が頻繁に再生成されるため、UIの応答性が低下する可能性があります。Qt CreatorのQMLプロファイラーでこのアプリケーションを実行すると、「Item Creation」や「Binding Evaluation」に高いコストがかかっていることが視覚的に確認できます。

  1. 問題の再現
    まず、何が問題なのか(クラッシュ、フリーズ、動作不良、パフォーマンス低下など)を明確にし、その問題を再現できる最小限のコードスニペットや手順を特定します。
  2. console.log() で絞り込み
    問題が起こる可能性のある場所の前後でconsole.log()を大量に仕込み、変数の値や実行フローを追跡します。
  3. ブレークポイントの活用
    Qt Creatorでブレークポイントを設定し、ステップ実行(F10, F11)でコードを一行ずつ確認します。変数の値は「ローカル」ビューで常に監視します。
  4. C++デバッガーとの連携
    QMLとC++が連携している場合、QMLからの呼び出しがC++側で問題を起こしていることもあります。C++コードにもブレークポイントを設定し、両方のレイヤーを同時にデバッグします。
  5. QMLプロファイラーの利用
    パフォーマンスの問題であれば、プロファイラーでボトルネックを特定し、その部分のコードを最適化します。
  6. エラーメッセージの確認
    Qt Creatorの「Application Output」や「Issues」タブに表示されるエラーメッセージや警告メッセージは、問題解決の重要なヒントです。特にQMLのバインディングエラーやタイプミスはここに表示されます。
  7. 公式ドキュメントとコミュニティ
    Qtの公式ドキュメントやオンラインのフォーラム(Stack Overflowなど)で類似の問題が報告されていないか検索します。


QML_IMPORT_TRACE 環境変数

QMLモジュールが見つからない、または予期せぬモジュールがロードされている場合など、QMLのインポートパスに関する問題をデバッグするのに非常に役立ちます。

  • 出力例
    この変数を設定してアプリケーションを実行すると、QMLエンジンがモジュールを探す過程や解決結果が詳細にコンソールに出力されます。
    QML Import: Finding module "QtQuick.Controls" version 2.15 in "/path/to/qt/qml"
    QML Import: Found module "QtQuick.Controls" version 2.15 in "/path/to/qt/qml/QtQuick/Controls"
    QML Import: ...
    
    これにより、"QML package not found" エラーの原因特定が容易になります。
  • 設定方法
    • Qt Creator
      プロジェクトの「Run」設定 (Projects タブ -> Run セクション) に移動し、EnvironmentDetails をクリックします。 Add をクリックして、変数名に QML_IMPORT_TRACE、値に 1 を設定します。
    • コマンドライン
      アプリケーションを実行する前に、シェルで環境変数を設定します。
      # Linux/macOS
      export QML_IMPORT_TRACE=1
      ./your_qml_application
      
      # Windows (cmd.exe)
      set QML_IMPORT_TRACE=1
      your_qml_application.exe
      
      # Windows (PowerShell)
      $env:QML_IMPORT_TRACE=1
      ./your_qml_application.exe
      

QQmlDebuggingEnabler の条件付き有効化とビルド設定

前述のコード例にもありましたが、QQmlDebuggingEnabler を利用してC++側でデバッグ機能を有効にする場合、通常はデバッグビルドでのみ有効にするのが賢明です。

main.cpp

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlDebuggingEnabler> // QMLデバッグを有効にするためのヘッダー

int main(int argc, char *argv[])
{
    // QMLデバッグは通常、デバッグビルドでのみ有効にします。
    // リリースビルドで有効にすると、パフォーマンス低下やセキュリティリスクにつながる可能性があります。
#ifdef QT_QML_DEBUG
    QQmlDebuggingEnabler enabler;
#endif

    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    // ... (QMLファイルのロードなど)

    return app.exec();
}

CMakeLists.txt (CMake)

# QMLデバッグを有効にするためのコンパイル定義
# Debug または RelWithDebInfo (リリースとデバッグ情報) ビルドでのみ有効
target_compile_definitions(${PROJECT_NAME} PRIVATE
    $<TARGET_EXISTS:Qt6::Qml::QmlDiagnostics> # Qt 6.xの場合、診断モジュールが存在するか確認
        ? $<$<OR:$<CONFIG:Debug>,$<CONFIG:RelWithDebInfo>>:QT_QML_DEBUG>
        : ""
)

.pro (qmake)

# デバッグビルドでのみ QML デバッグを有効にする
CONFIG(debug, debug|release): CONFIG += qml_debug

これにより、リリースビルドではデバッグ機能が自動的に無効になり、余計なオーバーヘッドやデバッグインターフェースへのアクセスを防ぐことができます。

C++オブジェクトの公開とC++デバッガーの活用

複雑なロジックやデータ処理がC++側で行われている場合、QMLからのデバッグだけでは不十分なことがあります。C++オブジェクトをQMLに公開し、そのオブジェクトを介してC++デバッガーを利用します。

C++ヘッダーファイル (mybackend.h)

#ifndef MYBACKEND_H
#define MYBACKEND_H

#include <QObject>
#include <QDebug> // デバッグ出力用

class MyBackend : public QObject
{
    Q_OBJECT
    Q_PROPERTY(QString data READ data WRITE setData NOTIFY dataChanged)

public:
    explicit MyBackend(QObject *parent = nullptr);

    QString data() const { return m_data; }
    void setData(const QString &newData) {
        if (m_data == newData) return;
        m_data = newData;
        emit dataChanged();
        qDebug() << "C++: data changed to" << m_data; // C++からのデバッグ出力
    }

    Q_INVOKABLE void doSomethingInCpp(const QString &message); // QMLから呼び出し可能な関数

signals:
    void dataChanged();

private:
    QString m_data;
};

#endif // MYBACKEND_H

C++ソースファイル (mybackend.cpp)

#include "mybackend.h"

MyBackend::MyBackend(QObject *parent)
    : QObject{parent}, m_data("Initial Data")
{
}

void MyBackend::doSomethingInCpp(const QString &message)
{
    qDebug() << "C++: QMLから doSomethingInCpp が呼ばれました。メッセージ:" << message;
    // ここにC++デバッガーのブレークポイントを設定できます
    int result = message.length() * 2;
    qDebug() << "C++: 計算結果:" << result;
    // ...
}

main.cpp (C++オブジェクトをQMLに公開)

#include <QGuiApplication>
#include <QQmlApplicationEngine>
#include <QQmlContext> // コンテキストオブジェクトにアクセスするため
#include <QQmlDebuggingEnabler>
#include "mybackend.h" // 作成したバックエンドクラスをインクルード

int main(int argc, char *argv[])
{
#ifdef QT_QML_DEBUG
    QQmlDebuggingEnabler enabler;
#endif

    QGuiApplication app(argc, argv);
    QQmlApplicationEngine engine;

    MyBackend backend; // バックエンドオブジェクトを作成
    // "backend" という名前でQMLに公開
    engine.rootContext()->setContextProperty("backend", &backend);

    const QUrl url(u"qrc:/QmlDebugExample/main.qml"_qs);
    QObject::connect(&engine, &QQmlApplicationEngine::objectCreationFailed,
        &app, []() { QCoreApplication::exit(-1); }, Qt::QueuedConnection);
    engine.load(url);

    return app.exec();
}

main.qml (C++オブジェクトにアクセス)

import QtQuick 2.15
import QtQuick.Window 2.15
import QtQuick.Controls 2.15

Window {
    width: 640
    height: 480
    visible: true
    title: qsTr("QML C++ Debug Example")

    // C++のMyBackendオブジェクトのデータを表示
    Text {
        anchors.centerIn: parent
        text: "C++データ: " + backend.data // backend.data をバインド
        font.pointSize: 24
        onTextChanged: {
            console.log("QML: C++データが変更されました:", text); // C++の変更がQMLに反映されたことをログ
        }
    }

    Button {
        anchors.horizontalCenter: parent.horizontalCenter
        anchors.top: parent.top
        anchors.topMargin: 20
        text: "C++関数を呼び出す"
        onClicked: {
            backend.doSomethingInCpp("QMLからメッセージです!"); // C++関数を呼び出す
            console.log("QML: C++関数を呼び出しました。");
            backend.data = "新しいデータ " + Math.random().toFixed(2); // C++プロパティを変更
        }
    }
}

この設定により、C++側のMyBackendクラス内の任意の場所にC++デバッガーのブレークポイントを設定し、QMLからの呼び出しやデータ変更時にC++のコード実行を停止して調査することができます。qDebug()もC++側からQt Creatorの「Application Output」に出力されます。

QML_FBO_DEBUG 環境変数 (Qt Quick Render Loop デバッグ)

グラフィックの描画に関する問題をデバッグする場合に役立ちます。Qt QuickがどのようにFBO (Framebuffer Object) を使用してシーンをレンダリングしているかに関する情報を出力します。

  • 用途
    UI要素の描画順序、クリッピングの問題、レンダリングの効率性などを調査する際に、詳細なOpenGL/Vulkan関連のデバッグ情報が得られることがあります。
  • 設定方法
    QML_FBO_DEBUG=1 を環境変数として設定します。

Qt QuickのグラフィックレンダリングエンジンであるScene Graphに関する詳細なデバッグ情報を得るための環境変数です。

  • QSG_VISUALIZE=overdraw / QSG_VISUALIZE=batches など
    これは、描画時に画面上にオーバーレイを表示し、Scene Graphの描画状況を視覚的に確認するための強力なツールです。
    • QSG_VISUALIZE=overdraw: 描画が重ねられている領域を色で視覚化します。赤色の領域はオーバードローが多いことを示し、パフォーマンスの問題につながることがあります。
    • QSG_VISUALIZE=batches: 描画バッチ(GPUへの描画命令のまとまり)を視覚化します。バッチ数が多すぎると、GPUの効率が低下する可能性があります。
    • 設定方法
      QSG_VISUALIZE=overdraw のように環境変数を設定してアプリケーションを実行します。
  • QSG_INFO=1
    Scene Graphの初期化、ノードの作成・削除、テクスチャ管理などに関する非常に詳細な情報を出力します。パフォーマンスチューニングの際に役立つことがあります。