web-dev-qa-db-ja.com

C#を使用してプログラムでWindows10の表示スケーリングを変更するにはどうすればよいですか

C#を使用してプログラムでWindows10の表示スケーリングを変更する方法を見つけようとしています。

また、ユーザーの画面に解像度/スケーリングを自動的に変更させるアプリケーションを作成しようとしているのではありません。それは私がテストのためにしばしばしなければならないこととして、トレイからスケールを切り替えることができるようにするための単なるツールです。したがって、このアクションのために意図的に設計されています。

そのため、ユーザーが表示されている公式ダイアログを介して手動でこれを行ったときに、どのレジストリエントリ(HKEY_CURRENT_USER\Control Panel\Desktop)が設定されているかを追跡できました。未満:

Windows 10 Display Scaling Dialog

ただし、明らかにレジストリを直接操作するということは、有効にするにはマシンを再起動する必要があることを意味します。

Pinvokeを使用して画面の解像度を変更できることを認識しています: ディスプレイの解像度の設定

特定の画面でこの「%」を変更する方法もあるのでしょうか。つまり、上の画面に150%と表示されているので、プログラムで100〜500%の全範囲で変更できるようにしたいと思います。

11
Futile32

これは、システム設定アプリ(没入型コントロールパネル)で行ったRnDからの私の学習です。 (この学習から作成した単純なC++ APIについては、他の回答を参照してください https://stackoverflow.com/a/58066736/981766

  1. システム設定アプリ(Windows 10に付属する新しい没入型コントロールパネル)はそれを行うことができます。これは確かにAPIがあることを意味しますが、Microsoftがそれを公開していないということだけです。
  2. システム設定アプリはUWPアプリですが、デバッガー(WinDbg)に接続できます。

WinDbgを使用して、このアプリからの呼び出しを実行しました。特定の機能が実行されるとすぐに--_user32!_imp_NtUserDisplayConfigSetDeviceInfo_新しいDPI設定が私のマシンで有効になることがわかりました。

この関数にブレークポイントを設定することはできませんでしたが、DisplayConfigSetDeviceInfo() _(bp user32!DisplayConfigSetDeviceInfo)_にブレークポイントを設定することはできました。

DisplayConfigSetDeviceInfo( msdn link )はパブリック関数ですが、設定アプリが文書化されていないパラメーターを送信しているようです。デバッグセッション中に見つけたパラメータは次のとおりです。

_((user32!DISPLAYCONFIG_DEVICE_INFO_HEADER *)0x55df8fba30)                 : 0x55df8fba30 [Type: DISPLAYCONFIG_DEVICE_INFO_HEADER *]
    [+0x000] type             : -4 [Type: DISPLAYCONFIG_DEVICE_INFO_TYPE]
    [+0x004] size             : 0x18 [Type: unsigned int]
    [+0x008] adapterId        [Type: _LUID]
    [+0x010] id               : 0x0 [Type: unsigned int]
0:003> dx -r1 (*((user32!_LUID *)0x55df8fba38))
(*((user32!_LUID *)0x55df8fba38))                 [Type: _LUID]
    [+0x000] LowPart          : 0xcbae [Type: unsigned long]
    [+0x004] HighPart         : 0 [Type: long]
_

基本的に、DisplayConfigSetDeviceInfo()に渡される_DISPLAYCONFIG_DEVICE_INFO_HEADER_構造体のメンバーの値は次のとおりです。

_type : -4
size : 0x18
adapterId : LowPart : 0xcbae HighPart :0
_

Wingdi.hで定義されている列挙型は次のとおりです。

_typedef enum
{
      DISPLAYCONFIG_DEVICE_INFO_GET_SOURCE_NAME                 = 1,
      DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME                 = 2,
      DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_PREFERRED_MODE       = 3,
      DISPLAYCONFIG_DEVICE_INFO_GET_ADAPTER_NAME                = 4,
      DISPLAYCONFIG_DEVICE_INFO_SET_TARGET_PERSISTENCE          = 5,
      DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_BASE_TYPE            = 6,
      DISPLAYCONFIG_DEVICE_INFO_GET_SUPPORT_VIRTUAL_RESOLUTION  = 7,
      DISPLAYCONFIG_DEVICE_INFO_SET_SUPPORT_VIRTUAL_RESOLUTION  = 8,
      DISPLAYCONFIG_DEVICE_INFO_GET_ADVANCED_COLOR_INFO         = 9,
      DISPLAYCONFIG_DEVICE_INFO_SET_ADVANCED_COLOR_STATE        = 10,
      DISPLAYCONFIG_DEVICE_INFO_FORCE_UINT32                = 0xFFFFFFFF
} DISPLAYCONFIG_DEVICE_INFO_TYPE;
_

設定アプリがタイプに-4を送信しようとしている間、列挙型に負の値がないことがわかります。

これを完全にリバースエンジニアリングできれば、モニターのDPIを設定するためのAPIが機能します。

Microsoftが独自のアプリ用に他の人が使用できない特別なAPIを持っていることは、信じられないほど不公平に思えます。

UPDATE 1

私の理論を検証するために、パラメーターとしてDisplayConfigSetDeviceInfo()に送信される_DISPLAYCONFIG_DEVICE_INFO_HEADER_構造体のバイトを(WinDbgを使用して)コピーしました。システム設定アプリからDPIスケーリングを変更した場合(150%DPIスケーリングを設定してみました)。

次に、これらのバイト(24バイト-0x18バイト)をDisplayConfigSetDeviceInfo()に送信する簡単なCプログラムを作成しました。
次に、DPIスケーリングを100%に戻し、コードを実行しました。案の定、DPIスケーリングはコードの実行時に変更されました!!!

_BYTE buf[] = { 0xFC,0xFF,0xFF,0xFF,0x18,0x00,0x00,0x00,0xAE,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x01,0x00,0x00,0x00 };
DISPLAYCONFIG_DEVICE_INFO_HEADER* packet = (DISPLAYCONFIG_DEVICE_INFO_HEADER*)buf;
    DisplayConfigSetDeviceInfo(packet);
_

LUIDと同じコードが機能しない場合があり、システム上の表示を指すidパラメーターが異なることに注意してください(LUIDは通常GPUに使用され、idはソースID、ターゲットID、またはその他のIDである可能性があります) 、このパラメーターはDISPLAYCONFIG_DEVICE_INFO_HEADER :: typeに依存します。

ここで、これらの24バイトの意味を理解する必要があります。

UPDATE 2

175%dpiのスケーリングを設定しようとしたときに取得したバイトは次のとおりです。

_BYTE buf[] = { 0xFC,0xFF,0xFF,0xFF,0x18,0x00,0x00,0x00,0xAE,0xCB,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x00,0x02,0x00,0x00,0x00 };
_

2バイトのバッファを比較すると、次の結論を導き出すことができます。

  1. 他のすべてのバイトは150%から175%の間で同じであるため、バイト番号21はDPIスケーリングを指定するために使用されています。
  2. 150%のスケーリングの場合、バイト21の値は1ですが、175%の場合は2です。このモニターのデフォルト(推奨)のDPIスケーリングは125%です。
  3. @Dodgeが言及した technetの記事 から、Windowsの用語では、0は推奨されるDPIスケーリング値に対応します。他の整数は、この推奨値に関する相対的なdpiスケーリングに対応します。 1はスケーリングが一歩進むことを意味し、-1は一歩下がることを意味します。例えば。推奨が125%の場合、値1は150%のスケーリングを意味します。これは確かに私たちが見たものです。

残っているのは、ディスプレイの推奨DPIスケーリング値を取得する方法を理解することだけです。これにより、次の形式のAPIを記述できるようになります-SetDPIScaling(monitor_LUID, DPIScale_percent)

UPDATE 3

@Dodgeの回答に記載されているレジストリエントリを確認すると、これらの整数がDWORDとして格納されていることがわかります。私のコンピュータはリトルエンディアンであるため、最後の4バイト(バイト21〜24)が使用されていることを意味します。したがって、負の数を送信するには、DWORDの2の補数を使用し、バイトをリトルエンディアンとして書き込む必要があります。

UPDATE 4

また、WindowsがDPIスケーリング値を格納するためのモニターIDを生成する方法についても調査しています。どのモニターでも、ユーザーが選択したDPIスケーリング値は次の場所に保存されます。

_HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\
*MonitorID*
_

私のマシンに接続されているDellディスプレイの場合、モニターIDは_DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297_でした。モニターIDの構造がわかりました。私は4つの異なるモニターで私の理論を検証しました。

デルのディスプレイ(_HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\ DELA0BC9DRXV68A0LWL_21_07E0_33^7457214C9330EFC0300669BF736A5297_に保存されているdpiスケーリング)の場合、次のようになります(画像を追加して申し訳ありませんが、情報を簡潔に表現する方法がわかりませんでした)。

Monitor ID image windows 10 x64 17763, 18362

基本的に、モニターIDを作成するためにEDIDから必要なデータは次のとおりです。

  1. メーカーID
    • EDID(ビッグエンディアン)のバイト8、9。
    • 例えば。 Dellディスプレイの場合、EDIDにはこれらのバイト用の10ACがあります。ビット15を除いて、残りの15ビット(ビット0〜14)を一度に5つずつ使用します。 (10AC)16 等しい(0001-0000-1010-1100)2。このバイナリをLSBから始めて5ビットのチャンクに分割すると、(0-00100-00101-01100)が得られます。2。各チャンクを10進数に変換する(0-4-5-12)10、現在「D」は4ですth アルファベット、「E」は5th、および「L」は12ですth
    • フォールバック:_@@@_
  2. 製品ID
    • EDIDのバイト10、11(リトルエンディアン)
    • 例えば。 Dellディスプレイの場合、EDIDにはBCA0があります。これはリトルエンディアンなので、A0BCに変換するだけで製品IDが得られます。
    • フォールバック:_000_
  3. シリアル番号
    • DTDシリアル番号が使用されます。 EDIDのベースブロック(最初の128バイト)には、DTDと呼ばれる4つのデータブロックがあります。これらは、タイミング情報または任意のデータを格納するために使用できます。 4つのDTDブロックは、バイト54、72、90、および108にあります。シリアル番号を持つDTDブロックは、最初の2バイト(バイト0、および1)をゼロ、2として持ちます。nd バイトもゼロ、および3rd 0xFFとしてのバイト。 4th 再びゼロです。バイト5以降のシリアル番号はASCIIです。シリアル番号は最大13バイト(DTDブロックのバイト5から17)を占めることができます。シリアル番号が13文字(13バイト)未満の場合、改行(_0x0A_)で終了します。
    • デルのディスプレイの場合、それは_00-00-00-FF-00-39-44-52-58-56-36-38-41-30-4C-57-4C-0A_でした。シリアル番号は12バイトで、改行(_0x0A_)で終了することに注意してください。 _39-44-52-58-56-36-38-41-30-4C-57-4C_をASCIIに変換すると、_9DRXV68A0LWL_が得られます。
    • フォールバック:EDIDのバイト12のシリアル番号。 EDIDはシリアル番号を2箇所に格納でき、DTDブロックEDIDが見つからない場合、OSはバイト12〜15(32ビットリトルエンディアン)に存在するシリアル番号を使用します。デルのディスプレイの場合は(4C-57-4C-30)16、リトルエンディアンなので、シリアル番号は(304C574C)です。16、つまり(810309452)10。 OSはこの値を使用します(フォールバックとして基数10)。これが存在しない場合でも、_0_が使用されます。
  4. 製造週
    • EDIDのバイト16(いくつかのバリエーションがあります。 ウィキペディアの記事 を参照してください)
    • デルのディスプレイの場合は(21)16
    • フォールバック:_00_
  5. 製造年
    • EDIDのバイト17
    • 1990年以降の製造年。バイト17の値に1990を追加します。
    • デルのディスプレイの場合は(1A)16。 (1A)16 +(1990)10 =(07C6)16
    • フォールバック:_0000_
  6. Edidベースブロックチェックサム
    • EDIDのバイト127
    • から ウィキペディア -チェックサム。 128バイトすべての合計は0(mod 256)に等しくなります。
    • フォールバックはありません。有効なEDIDにはこの値が必要です。

EDIDの最初の128バイトのみが必要であることに注意してください。

フォールバックに関する注記

モニターIDの作成に必要なデータの一部が存在しない場合、OSはフォールバックを使用します。 Windows 10マシンで観察したように、モニターIDの作成に必要な各データのフォールバックは上記のリストに示されています。デルのディスプレイのEDIDを手動で編集しました( link1link2link -注意してください-リンク3で提案されている方法は、システムに損傷を与える可能性があります。続行してください。確かな場合のみ; Link1が最も推奨されます)上記の6つの項目すべてを削除するには、OSが作成したモニターID(MD5サフィックスなし)は_@@@0000810309452_00_0000_85_でしたが、バイト12のシリアル番号も削除しました。構築されたIDは_@@@00000_00_0000_A4_でした。

UPDATE 4

DPIスケーリングはソースのプロパティであり、ターゲットのプロパティではないため、DisplayConfigGetDeviceInfo()およびDisplayConfigSetDeviceInfo()で使用されるidパラメーターはソースIDであり、ターゲットIDではありません。

上記で提案されたレジストリ方法は、ほとんどの場合正常に機能するはずですが、2つの欠点があります。 1つは、システム設定アプリと同等ではないということです(設定が行われる時間に関して)。次に、まれに(これ以上再現できない)、OSによって生成されたモニターID文字列がわずかに異なることがわかりました。上の写真に示されているコンポーネントが多く含まれています。

システム設定アプリとまったく同じ方法でDPIスケーリングを取得/設定するために使用できるAPIを正常に作成しました。これは私が解決策を見つけるために取ったアプローチに関するものなので、新しい回答を投稿します。

16
Sahil Singh

まったく同じものを検索しているときに、私はあなたの質問を見つけ、可能な解決策を見つけました。

この%値のモニターごとの切り替えは、レジストリのComputer\HKEY_CURRENT_USER\Control Panel\Desktop\PerMonitorSettings\*monitorId*\DpiValueにあることがわかりました。値の意味は画面(サイズとdpi)によって異なるようです。詳細については、 このredditの投稿 を参照してください。

私の24 "1080p画面の場合、0は100%を意味し、1は125%を意味します。 このTechnetの記事 は値を少し説明しているようです。

残念ながら、レジストリ値を変更するだけでは不十分です。ただし、レジストリへの書き込み後に解像度を変更することで、dpiを更新できます。

次のコードは、dpiを設定してから、解像度を低くしたり高くしたりして、dpiの更新をトリガーします。

using System;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using Microsoft.Win32;

namespace SetDpiScale
{
    public partial class Form1 : Form
    {
        public enum DMDO
        {
            DEFAULT = 0,
            D90 = 1,
            D180 = 2,
            D270 = 3
        }

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Auto)]
        struct DEVMODE
        {
            public const int DM_PELSWIDTH = 0x80000;
            public const int DM_PELSHEIGHT = 0x100000;
            private const int CCHDEVICENAME = 32;
            private const int CCHFORMNAME = 32;

            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHDEVICENAME)]
            public string dmDeviceName;
            public short dmSpecVersion;
            public short dmDriverVersion;
            public short dmSize;
            public short dmDriverExtra;
            public int dmFields;

            public int dmPositionX;
            public int dmPositionY;
            public DMDO dmDisplayOrientation;
            public int dmDisplayFixedOutput;

            public short dmColor;
            public short dmDuplex;
            public short dmYResolution;
            public short dmTTOption;
            public short dmCollate;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = CCHFORMNAME)]
            public string dmFormName;
            public short dmLogPixels;
            public int dmBitsPerPel;
            public int dmPelsWidth;
            public int dmPelsHeight;
            public int dmDisplayFlags;
            public int dmDisplayFrequency;
            public int dmICMMethod;
            public int dmICMIntent;
            public int dmMediaType;
            public int dmDitherType;
            public int dmReserved1;
            public int dmReserved2;
            public int dmPanningWidth;
            public int dmPanningHeight;
        }

        [DllImport("user32.dll", CharSet = CharSet.Auto)]
        static extern int ChangeDisplaySettings([In] ref DEVMODE lpDevMode, int dwFlags);

        public Form1()
        {
            InitializeComponent();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            ChangeDPI(0); // 100%
        }
        private void button2_Click(object sender, EventArgs e)
        {
            ChangeDPI(1); // 125%
        }

        void ChangeDPI(int dpi)
        {
            RegistryKey key = Registry.CurrentUser.OpenSubKey("Control Panel", true);

            key = key.OpenSubKey("Desktop", true);
            key = key.OpenSubKey("PerMonitorSettings", true);
            key = key.OpenSubKey("*monitor id where to change the dpi*", true); // my second monitor here

            key.SetValue("DpiValue", dpi);

            SetResolution(1920, 1080); // this sets the resolution on primary screen
            SetResolution(2560, 1440); // returning back to my primary screens default resolution
        }

        private static void SetResolution(int w, int h)
        {
            long RetVal = 0;

            DEVMODE dm = new DEVMODE();

            dm.dmSize = (short)Marshal.SizeOf(typeof(DEVMODE));

            dm.dmPelsWidth = w;
            dm.dmPelsHeight = h;

            dm.dmFields = DEVMODE.DM_PELSWIDTH | DEVMODE.DM_PELSHEIGHT;


            RetVal = ChangeDisplaySettings(ref dm, 0);
        }
    }
}
4
Dodge

SahilSinghsの答えに加えて。 MonitorIDは、次の場所にsubkeysとして配置できます。HKEY_LOCAL_MACHINE\SYSTEM\CurrentControlSet\Control\GraphicsDrivers\ScaleFactors

0
Peter Søholm

これが@SahilSinghに基づく私のコードです:

C++ APIをラップするDLLプロジェクト:

stdafx.h:

#pragma once

#include "targetver.h"

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
// Windows Header Files
#include <windows.h>

// reference additional headers your program requires here
#ifdef __cplusplus
extern "C" {
#endif
    extern __declspec(dllexport) void PrintDpiInfo();
    extern __declspec(dllexport) void SetDPIScaling(INT32 adapterIDHigh, UINT32 adapterIDlow, UINT32 sourceID, UINT32 dpiPercentToSet);
    extern __declspec(dllexport) void RestoreDPIScaling();
#ifdef __cplusplus
}
#endif

DpiHelper.cpp:

// DpiHelper.cpp : Defines the exported functions for the DLL application.
//

#include "stdafx.h"
#include "DpiHelper.h"
#include <memory>
#include <cassert>
#include <string>
#include <map>

bool DpiHelper::GetPathsAndModes(std::vector<DISPLAYCONFIG_PATH_INFO>& pathsV, std::vector<DISPLAYCONFIG_MODE_INFO>& modesV, int flags)
{
    UINT32 numPaths = 0, numModes = 0;
    auto status = GetDisplayConfigBufferSizes(flags, &numPaths, &numModes);
    if (ERROR_SUCCESS != status)
    {
        return false;
    }

    std::unique_ptr<DISPLAYCONFIG_PATH_INFO[]> paths(new DISPLAYCONFIG_PATH_INFO[numPaths]);
    std::unique_ptr<DISPLAYCONFIG_MODE_INFO[]> modes(new DISPLAYCONFIG_MODE_INFO[numModes]);
    status = QueryDisplayConfig(flags, &numPaths, paths.get(), &numModes, modes.get(), nullptr);
    if (ERROR_SUCCESS != status)
    {
        return false;
    }

    for (unsigned int i = 0; i < numPaths; i++)
    {
        pathsV.Push_back(paths[i]);
    }

    for (unsigned int i = 0; i < numModes; i++)
    {
        modesV.Push_back(modes[i]);
    }

    return true;
}


DpiHelper::DpiHelper()
{
}


DpiHelper::~DpiHelper()
{
}


DpiHelper::DPIScalingInfo DpiHelper::GetDPIScalingInfo(LUID adapterID, UINT32 sourceID)
{
    DPIScalingInfo dpiInfo = {};

    DpiHelper::DISPLAYCONFIG_SOURCE_DPI_SCALE_GET requestPacket = {};
    requestPacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DpiHelper::DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM::DISPLAYCONFIG_DEVICE_INFO_GET_DPI_SCALE;
    requestPacket.header.size = sizeof(requestPacket);
    assert(0x20 == sizeof(requestPacket));//if this fails => OS has changed somthing, and our reverse enginnering knowledge about the API is outdated
    requestPacket.header.adapterId = adapterID;
    requestPacket.header.id = sourceID;

    auto res = ::DisplayConfigGetDeviceInfo(&requestPacket.header);
    if (ERROR_SUCCESS == res)
    {//success
        if (requestPacket.curScaleRel < requestPacket.minScaleRel)
        {
            requestPacket.curScaleRel = requestPacket.minScaleRel;
        }
        else if (requestPacket.curScaleRel > requestPacket.maxScaleRel)
        {
            requestPacket.curScaleRel = requestPacket.maxScaleRel;
        }

        std::int32_t minAbs = abs((int)requestPacket.minScaleRel);
        if (DpiHelper::CountOf(DpiVals) >= (size_t)(minAbs + requestPacket.maxScaleRel + 1))
        {//all ok
            dpiInfo.current = DpiVals[minAbs + requestPacket.curScaleRel];
            dpiInfo.recommended = DpiVals[minAbs];
            dpiInfo.maximum = DpiVals[minAbs + requestPacket.maxScaleRel];
            dpiInfo.bInitDone = true;
        }
        else
        {
            //Error! Probably DpiVals array is outdated
            return dpiInfo;
        }
    }
    else
    {
        //DisplayConfigGetDeviceInfo() failed
        return dpiInfo;
    }

    return dpiInfo;
}

std::wstring GetTargetName(LUID adapterLUID, UINT32 sourceId)
{
    std::vector<DISPLAYCONFIG_PATH_INFO> pathsV;
    std::vector<DISPLAYCONFIG_MODE_INFO> modesV;
    int flags = QDC_ONLY_ACTIVE_PATHS;
    if (false == DpiHelper::GetPathsAndModes(pathsV, modesV, flags))
    {
        wprintf(L"DpiHelper::GetPathsAndModes() failed\r\n");
    }

    for (const auto& path : pathsV)
    {

        if (adapterLUID.LowPart == path.targetInfo.adapterId.LowPart
            && adapterLUID.HighPart == path.targetInfo.adapterId.HighPart
            && sourceId == path.sourceInfo.id)
        {
            DISPLAYCONFIG_TARGET_DEVICE_NAME deviceName;
            deviceName.header.size = sizeof(deviceName);
            deviceName.header.type = DISPLAYCONFIG_DEVICE_INFO_GET_TARGET_NAME;
            deviceName.header.adapterId = adapterLUID;
            deviceName.header.id = path.targetInfo.id;
            if (ERROR_SUCCESS != DisplayConfigGetDeviceInfo(&deviceName.header))
            {
                wprintf(L"DisplayConfigGetDeviceInfo() failed\r\n");
            }
            else
            {

                std::wstring nameString = deviceName.monitorFriendlyDeviceName;
                if (DISPLAYCONFIG_OUTPUT_TECHNOLOGY_INTERNAL == deviceName.outputTechnology)
                {
                    nameString += L"(internal display)";
                }
                return nameString;
            }
        }

    }
    return L"N/A";

}



void printOne(LUID adapterLUID, UINT32 sourceID) {
    wprintf(L"GPU=%ld.%u,Desktop_Index_In_GPU=%d,Monitor=%ls\r\n"
        ,adapterLUID.HighPart
        , adapterLUID.LowPart
        , sourceID
        , GetTargetName(adapterLUID, sourceID).data());
}



bool DpiHelper::SetDPIScaling(LUID adapterID, UINT32 sourceID, UINT32 dpiPercentToSet)
{

    wprintf(L"setting dpi scale to %d: ", dpiPercentToSet);
    printOne(adapterID, sourceID);
    DPIScalingInfo dPIScalingInfo = GetDPIScalingInfo(adapterID, sourceID);

    if (dpiPercentToSet == dPIScalingInfo.current)
    {
        return true;
    }

    if (dpiPercentToSet < dPIScalingInfo.mininum)
    {
        dpiPercentToSet = dPIScalingInfo.mininum;
    }
    else if (dpiPercentToSet > dPIScalingInfo.maximum)
    {
        dpiPercentToSet = dPIScalingInfo.maximum;
    }

    int idx1 = -1, idx2 = -1;

    int i = 0;
    for (const auto& val : DpiVals)
    {
        if (val == dpiPercentToSet)
        {
            idx1 = i;
        }

        if (val == dPIScalingInfo.recommended)
        {
            idx2 = i;
        }
        i++;
    }

    if ((idx1 == -1) || (idx2 == -1))
    {
        //Error cannot find dpi value
        return false;
    }

    int dpiRelativeVal = idx1 - idx2;

    DpiHelper::DISPLAYCONFIG_SOURCE_DPI_SCALE_SET setPacket = {};
    setPacket.header.adapterId = adapterID;
    setPacket.header.id = sourceID;
    setPacket.header.size = sizeof(setPacket);
    assert(0x18 == sizeof(setPacket));//if this fails => OS has changed somthing, and our reverse enginnering knowledge about the API is outdated
    setPacket.header.type = (DISPLAYCONFIG_DEVICE_INFO_TYPE)DpiHelper::DISPLAYCONFIG_DEVICE_INFO_TYPE_CUSTOM::DISPLAYCONFIG_DEVICE_INFO_SET_DPI_SCALE;
    setPacket.scaleRel = (UINT32)dpiRelativeVal;

    auto res = ::DisplayConfigSetDeviceInfo(&setPacket.header);
    if (ERROR_SUCCESS == res)
    {
        return true;
    }
    else
    {
        return false;
    }
    return true;
}


#define MAX_ID  10
LUID GpuId[MAX_ID];
UINT32 DesktopIndexInGpu[MAX_ID];
UINT32 oldDPI[MAX_ID];


void PrintDpiInfo() {



    std::vector<DISPLAYCONFIG_PATH_INFO> pathsV;
    std::vector<DISPLAYCONFIG_MODE_INFO> modesV;
    int flags = QDC_ONLY_ACTIVE_PATHS;
    if (false == DpiHelper::GetPathsAndModes(pathsV, modesV, flags))
    {
        wprintf(L"DpiHelper::GetPathsAndModes() failed");
    }

    int i = 0;
    for (const auto& path : pathsV)
    {
        //get display name
        auto adapterLUID = path.targetInfo.adapterId;       
        auto sourceID = path.sourceInfo.id;
        std::wstring monitor_name = GetTargetName(adapterLUID, sourceID);
        printOne(adapterLUID, sourceID);

        DpiHelper::DPIScalingInfo dpiInfo = DpiHelper::GetDPIScalingInfo(adapterLUID, sourceID);

        GpuId[i] = adapterLUID;
        DesktopIndexInGpu[i] = sourceID;
        oldDPI[i] = dpiInfo.current;


        wprintf(L"Available DPI:\r\n");
        int curdpi = 0;
        for (const auto& dpi : DpiVals)
        {
            if ((dpi >= dpiInfo.mininum) && (dpi <= dpiInfo.maximum))
                wprintf(L"    %d\r\n",dpi);
        }
        wprintf(L"    current DPI: %d\r\n",dpiInfo.current);

        i++;
        if (i >= MAX_ID) {
            wprintf(L"To many desktops\r\n");
            break;
        }
    }


}

void SetDPIScaling(INT32 adapterIDHigh, UINT32 adapterIDlow, UINT32 sourceID, UINT32 dpiPercentToSet) {
    LUID adapterId;
    adapterId.HighPart = adapterIDHigh;
    adapterId.LowPart = adapterIDlow;   
    DpiHelper::SetDPIScaling(adapterId, sourceID, dpiPercentToSet);
}

void RestoreDPIScaling() 
{
    wprintf(L"Now restore DPI settings...\r\n");
    for (int i = 0;i < MAX_ID;i++) {
        if (GpuId[i].LowPart == 0 && GpuId[i].HighPart==0) break;
        DpiHelper::SetDPIScaling(GpuId[i], DesktopIndexInGpu[i], oldDPI[i]);
    }

}

DpiHelper.hは、参照されている回答と同じです。 VisualStudioでC++ Dllプロジェクトを作成し、上記のコードを追加/配置して、次のC#アプリケーションでdllを使用します。

C#コンソールアプリケーションコマンドラインパラメータに従ってDPIを設定し、任意のキーを押すとそれらを復元します。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Runtime.InteropServices;
using System.Text;
using System.Threading.Tasks;

namespace DispSetEx
{
    class Program
    {



        [DllImport("DpiHelper.dll")]
        static public extern void PrintDpiInfo();

        [DllImport("DpiHelper.dll")]
        static public extern int SetDPIScaling(Int32 adapterIDHigh, UInt32 adapterIDlow, UInt32 sourceID, UInt32 dpiPercentToSet);
        [DllImport("DpiHelper.dll")]
        static public extern void RestoreDPIScaling();

        static void Main(string[] args)
        {
            if ((args.Length % 3) != 0)
            {
                Console.WriteLine("wrong parameters");
                return;
            }

//print the DPI info, you need to set the command line parameters
//according to this
            PrintDpiInfo();

    //commandline parameters should be of groups of three
    //each groups's tree paramters control a desktop's setting
    //in each group:
    //GPUIdhigh.GPUIdlow DesktopIndexInGPU DPIScalingValue
    //for example:
    //    0.1234 0 100 //set the DPI scaling to 100 for desktop 0 on GPU 0.1234
    //    0.4567 0 125 //set the DPI scaling to 125 for desktop 0 on GPU 0.5678
    //    0.4567 1 150 //set the DPI scaling to 150 for desktop 1 on GPU 0.5678
    //in most cases GPUIdhigh is 0.
    //you can use the monitor name to identify which is which easily
    //you need to set the command line parameters according to the result of PrintDpiInfo
    //e.g. you should only set the DPI scaling to a value that is supported by 
    //that desktop. 


            for (int i = 0; i < args.Length / 3; i++)
            {
                string[] sa = args[i * 3].Split(new char[] { '.' });

                Int32 adapterHigh = Int32.Parse(sa[0]);
                UInt32 adapterLow = UInt32.Parse(sa[1]);
                UInt32 source = UInt32.Parse(args[i * 3 + 1]);
                UInt32 dpiscale = UInt32.Parse(args[i * 3 + 2]);

                SetDPIScaling(adapterHigh, adapterLow, source,dpiscale);
            }

            Console.WriteLine("Press any key to resotre the settings...");
            Console.ReadKey();

            RestoreDPIScaling();  
        }
    }
}
0
jw_