Jim*_*ola 7 c++ winapi multiple-monitors gdi
我正在为 Windows 10 开发一个模块,用于管理配置并将其应用到连接的显示器。使用下面的代码,我可以克隆和扩展通过 HDMI、VGA、DP 和 DVI 连接的显示器,但在克隆使用 displaylink 驱动程序 ( https://www.displaylink.com/downloads )的 USB 连接显示器时遇到问题。当然,如果我使用Windows 显示设置窗口,这将有效。问题是我不太确定如何在 Windows 上正确克隆和扩展监视器。
代码很简单,基本上就是这样做的:
GetDisplayConfigBufferSizes函数检索调用QueryDisplayConfig所需的缓冲区大小。
它确定保存所有有效路径所需的路径和模式信息数组的大小。有关这些路径的更多详细信息,请参阅
下面的文档以获取更多信息。
#include <Windows.h>
#include <iostream>
#include <vector>
int main()
{
UINT32 pathSize, modeSize;
GetDisplayConfigBufferSizes(QDC_ALL_PATHS, &pathSize, &modeSize);
std::vector<DISPLAYCONFIG_PATH_INFO> pathArray(pathSize);
std::vector<DISPLAYCONFIG_MODE_INFO> modeArray(modeSize);
// Fills a block of memory with zeros.
SecureZeroMemory(&pathArray[0],
sizeof(DISPLAYCONFIG_PATH_INFO) *
pathArray.size());
SecureZeroMemory(&modeArray[0],
sizeof(DISPLAYCONFIG_MODE_INFO) *
modeArray.size());
//...
}
Run Code Online (Sandbox Code Playgroud)
QDC_ALL_PATHS :源到目标的所有可能的路径组合。
QueryDisplayConfig函数检索有关当前设置中所有显示设备或视图的所有可能显示路径的信息。
QueryDisplayConfig(QDC_ALL_PATHS, &pathSize, &pathArray[0], &modeSize, &modeArray[0], NULL);
Run Code Online (Sandbox Code Playgroud)
通过这两个调用,我可以克隆和扩展两个连接的监视器,但我无法选择要克隆或扩展的确切监视器对。
// Clone two monitors at **random**. If we have more than two we can't choose
// which ones to clone. This call can also clone a monitor connected via usb.
SetDisplayConfig(0, NULL, 0, NULL, SDC_TOPOLOGY_CLONE | SDC_APPLY);
// Extend all connected monitors. If we have more than two,
// we cannot choose which ones to extend.
SetDisplayConfig(0, NULL, 0, NULL, SDC_TOPOLOGY_EXTEND | SDC_APPLY);
Run Code Online (Sandbox Code Playgroud)
例如,如果我们有 3 个连接的监视器,我们可以选择要克隆的监视器。
// To clone two monitors of your choice just copy the
// DISPLAYCONFIG_PATH_SOURCE_INFO id and modeInfoIdx.
pathArray[1].sourceInfo.id = pathArray[0].sourceInfo.id;
pathArray[1].sourceInfo.modeInfoIdx = pathArray[0].sourceInfo.modeInfoIdx;
// Note: The sourceInfo of the primary monitor cannot be overwritten.
// If we have to clone the primary monitor we have to put it on the right side.
constexpr UINT flags = SDC_APPLY |
SDC_SAVE_TO_DATABASE |
SDC_ALLOW_CHANGES |
SDC_USE_SUPPLIED_DISPLAY_CONFIG;
const LONG RESULT = SetDisplayConfig(static_cast<UINT32>(pathArray.size()),
&pathArray[0],
static_cast<UINT32>(modeArray.size()),
&modeArray[0],
flags);
// SDC_APPLY
// The resulting topology, source, and target mode is set.
// SDC_USE_SUPPLIED_DISPLAY_CONFIG
// The topology, source, and target mode information that are supplied in the pathArray and the
// modeInfoArray parameters are used, rather than looking up the configuration in the database.
// SDC_SAVE_TO_DATABASE
// The resulting topology, source, and target mode are saved to the database.
// SDC_ALLOW_CHANGES
// If required, the function can modify the specified source and target mode information in order
// to create a functional display path set.
Run Code Online (Sandbox Code Playgroud)
让我们分析一下这样的情况:我们已经通过应用上面的示例克隆了两个监视器,现在我们需要再次扩展它们。
// We've already cloned two monitors
pathArray[1].sourceInfo.id = pathArray[0].sourceInfo.id;
pathArray[1].sourceInfo.modeInfoIdx = pathArray[0].sourceInfo.modeInfoIdx;
// Remember that pathArray[1] couldn't be the primary monitor.
Run Code Online (Sandbox Code Playgroud)
让我们再次扩展它们
// A random id that is not already used by other display path.
// I don't know how correct this is but if you don't set this id
// it will don't work.
pathArray[0].sourceInfo.id = 3;
// modeInfoIdx no longer has to refer to the original cloned configuration.
pathArray[0].sourceInfo.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
pathArray[0].targetInfo.modeInfoIdx = DISPLAYCONFIG_PATH_MODE_IDX_INVALID;
constexpr UINT flags = SDC_APPLY |
SDC_SAVE_TO_DATABASE |
SDC_ALLOW_CHANGES |
SDC_USE_SUPPLIED_DISPLAY_CONFIG;
// SetDisplayConfig will look for a suitable configuration in the database
// for the monitor without its DISPLAYCONFIG_MODE_INFO.
const LONG RESULT = SetDisplayConfig(static_cast<UINT32>(pathArray.size()),
&pathArray[0],
static_cast<UINT32>(modeArray.size()),
&modeArray[0],
flags);
Run Code Online (Sandbox Code Playgroud)
通过这样做,我无法选择扩展显示器的位置,但我可以稍后进行如下设置。
// We need to update the displays information because what we have now is no longer valid.
QueryDisplayConfig(QDC_ALL_PATHS, &pathSize, &pathArray[0], &modeSize, &modeArray[0], NULL);
// modeInfoIdx is an index of a DISPLAYCONFIG MODEINFO array (modeArray)
const UINT32 modeIndex = pathArray[0].sourceInfo.modeInfoIdx;
// On Windows the primary display always has the position (0, 0).
// Then we want to extend it to the left side of the primary.
// It's a common Full HD monitor.
modeArray[modeIndex].sourceMode.position = POINTL{-1920, 0};
constexpr UINT flags = SDC_APPLY |
SDC_SAVE_TO_DATABASE |
SDC_ALLOW_CHANGES |
SDC_USE_SUPPLIED_DISPLAY_CONFIG;
const LONG RESULT = SetDisplayConfig(static_cast<UINT32>(pathArray.size()),
&pathArray[0],
static_cast<UINT32>(modeArray.size()),
&modeArray[0],
flags);
Run Code Online (Sandbox Code Playgroud)
我还没有找到可靠的文档来确认这是正确的操作方式,但是这在 HDMI、VGA、DP、DVI 显示器上运行良好,不幸的是它在 USB 显示器上不起作用,这表明我有问题。我必须使其在通过 USB 连接的 Wacom 交互式笔写板上工作,但SetDisplayConfig始终返回ERROR_INVALID_PARAMETER。我尝试了不同的参数组合,但无法使其工作。
感谢您提供任何有用的反馈。
GetDisplayConfigBufferSizes 函数
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-getdisplayconfigbuffersizes
DISPLAYCONFIG_PATH_INFO 结构
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-displayconfig_path_info
QueryDisplayConfig 函数
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-querydisplayconfig
SetDisplayConfig 函数
https://learn.microsoft.com/en-us/windows/win32/api/winuser/nf-winuser-setdisplayconfig
CDD API
https://learn.microsoft.com/en-us/windows-hardware/drivers/display/ccd-apis