single.php

VC++で接続しているモニターのオンとオフを行う(改良版)

少し前に、VC++で SetDisplayConfig関数を使って、接続しているモニター(ディスプレイ)の切断と再接続をするコンソールアプリを制作しましたが、何かの更新で特定のモニターだけエラーになってしまうので原因と調査と改良を行いました。その時の対象法を備忘録的に投稿しておきます。

NVIDIA Surround 設定

今回のエラーになる原因は、これでした。

アプリ自体は[QueryDisplayConfig]関数でディスプレイ情報を取得して、有効と無効を[SetDisplayConfig]関数で切り替える単純な仕様でしたが、私の環境は4つのモニターで構成されていますが[Get-PnpDevice]コマンドでは6つも取得されます。

表示されているキーワード[NV Surround]で分かる通り、おそらく原因になったのは、NVIDIAのアプリから設定可能な[Surround]です。

何かの調査で一度設定して、その時に調べている用途では使えなかったので設定を削除したつもり(NVIDIAの設定画面では[無効]になっている)でしたが、モニター情報としては残っている状態でした。

調べてみると、この追加されたモニター情報を完全に削除するのは難しいという意見が多かったので、依存しない方法に修正しました。

アクティブなモニターだけ取得

以前は「接続されているディスプレイ=アクティブなディスプレイ」なので、問題なく動作していましたが、NVIDIA Surroundの残留物の影響で明示的にアクティブなモニターのみを取得する必要があります。

アクティブなディスプレイのみを取得したい場合には[QueryDisplayConfig]関数のフラグに[QDC_ONLY_ACTIVE_PATHS]を使います。

// -------------------------------
// Display取得(ACTIVEのみ)
// -------------------------------
UINT32 PathCount = 0;
UINT32 ModeCount = 0;

GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &PathCount, &ModeCount);

std::vector<DISPLAYCONFIG_PATH_INFO> pathArray(PathCount);
std::vector<DISPLAYCONFIG_MODE_INFO> modeArray(ModeCount);

QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &PathCount, pathArray.data(), &ModeCount, modeArray.data(), NULL);

取得されるディスプレイ情報から[NVIDIA Surround]の残留物が除外されたので、エラーを回避できました。

VC++のコンソールアプリで、ディスプレイの接続と切断を制御するのコードを、全部載せておきます

#include <stdio.h>
#include <tchar.h>
#include <vector>
#include <iostream>
#include <windows.h>
#include <WinUser.h>
#include <set>

void printActiveDisplays(std::vector<DISPLAYCONFIG_PATH_INFO>& pathArray)
{
    for (int i = 0; i < pathArray.size(); i++)
    {
        DISPLAYCONFIG_TARGET_DEVICE_NAME dn;
        dn.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
        dn.header.size = sizeof(dn);
        dn.header.adapterId = pathArray[i].targetInfo.adapterId;
        dn.header.id = pathArray[i].targetInfo.id;

        if (DisplayConfigGetDeviceInfo((DISPLAYCONFIG_DEVICE_INFO_HEADER*)&dn) == ERROR_SUCCESS)
        {
            wprintf(L"%d: %s\n", i, dn.monitorDevicePath);
        }
    }
}


int main(int argc, char* argv[])
{
    // -------------------------------
    // Display取得(ACTIVEのみ)
    // -------------------------------
    UINT32 PathCount = 0;
    UINT32 ModeCount = 0;

    GetDisplayConfigBufferSizes(QDC_ONLY_ACTIVE_PATHS, &PathCount, &ModeCount);

    std::vector<DISPLAYCONFIG_PATH_INFO> pathArray(PathCount);
    std::vector<DISPLAYCONFIG_MODE_INFO> modeArray(ModeCount);

    QueryDisplayConfig(QDC_ONLY_ACTIVE_PATHS, &PathCount, pathArray.data(), &ModeCount, modeArray.data(), NULL);

    // -------------------------------
    // 引数なし → 一覧表示
    // -------------------------------
    if (argc == 1)
    {
        printActiveDisplays(pathArray);
        return 0;
    }


    if (argc < 2) {
        printf("使い方: MonitorSwitcher on | off [切断するモニターの番号...(半角スペースで区切って複数指定可能)]\n");
        return 0;
    }

    // -------------------------------
    // ON
    // -------------------------------
    if (strcmp(argv[1], "on") == 0)
    {
        LONG hr = SetDisplayConfig(
            0, NULL,
            0, NULL,
            SDC_APPLY | SDC_TOPOLOGY_EXTEND
        );

        printf("接続結果: %ld\n", hr);
        return 0;
    }

    // -------------------------------
    // OFF
    // -------------------------------
    if (strcmp(argv[1], "off") != 0)
    {
        printf("使い方:\n");
        printf("  MonitorSwitcher            (モニターの番号が一覧で表示される)\n");
        printf("  MonitorSwitcher on\n");
        printf("  MonitorSwitcher off [切断するモニターの番号...(半角スペースで区切って複数指定可能)]\n");
        return -1;
    }

    // インデックスを取得
    std::set<int> targets;
    for (int i = 2; i < argc; i++) {
        targets.insert(atoi(argv[i]));
    }

    if (targets.empty()) {
        printf("切断するモニターの番号が不正です\n");
        return 0;
    }

    // -------------------------------
    // OFF適用
    // -------------------------------
    int activeCount = 0;

    for (int i = 0; i < pathArray.size(); i++)
    {
        if (targets.count(i) > 0)
        {
            pathArray[i].flags = 0;
        }

        if (pathArray[i].flags & DISPLAYCONFIG_PATH_ACTIVE)
        {
            activeCount++;
        }
    }

    if (activeCount == 0)
    {
        printf("エラー: アクティブなモニターが設定されていませんe\n");
        return -1;
    }

    LONG hr = SetDisplayConfig(
        (UINT32)pathArray.size(),
        pathArray.data(),
        ModeCount,
        modeArray.data(),
        SDC_APPLY | SDC_USE_SUPPLIED_DISPLAY_CONFIG | SDC_ALLOW_CHANGES
    );

    printf("切断結果: %ld\n", hr);

    return 0;
}

まとめ

今回は、短い記事になりますが、VC++でディスプレイの接続と切断を行うアプリでエラーが発生する原因と対処法について書きました。

エラーになった原因は[NVIDIA Surround]を設定した際に追加されたディスプレイ情報の残留でした。

[NVIDIA Surround]で追加されたディスプレイ情報は設定を無効にしても削除されない可能性があるので[QueryDisplayConfig]関数でモニター情報を取得する際に、アクティブな接続のみを取得するための[QDC_ONLY_ACTIVE_PATHS]フラグを使うことで改善できました。

VC++で[SetDisplayConfig]関数でエラーになる人の参考になれば幸いです。

スポンサーリンク

最後までご覧いただきありがとうございます。

コメントを残す

メールアドレスが公開されることはありません。 が付いている欄は必須項目です