You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何枚举Windows 11中通过WinKey+TAB创建的虚拟桌面?

枚举Windows 11(及Windows 10)的WinKey+TAB虚拟桌面

你说得完全正确——通过WinKey+TAB创建的虚拟桌面和Win32 API里EnumDesktops()枚举的传统「桌面」根本不是一个概念:

  • 传统桌面(比如Default、DummyDesktopForInstantResume)是Windows窗口站(Window Station)层面的系统对象,负责管理窗口的底层显示上下文;
  • 而WinKey+TAB的虚拟桌面是Windows Shell提供的逻辑分组功能,所有虚拟桌面共享同一个传统Default桌面,所以你会看到窗口句柄始终不变、Spy++始终显示同一套窗口列表。

要枚举这类虚拟桌面,你需要使用Windows Shell提供的虚拟桌面COM接口,而非传统的窗口站API。下面是具体实现方法:


核心概念与所需接口

虚拟桌面的管理由User32.dll中的COM组件负责,主要用到两个接口:

  • IVirtualDesktopManager:公开API,仅用于查询窗口所属的虚拟桌面,没有枚举功能;
  • IVirtualDesktopManagerInternal:内部接口(未正式公开,但在Win10/Win11中稳定可用),提供枚举、创建、切换虚拟桌面的完整能力。

C++实现示例

以下代码展示如何枚举所有虚拟桌面并获取它们的GUID和名称:

#include <windows.h>
#include <shlobj.h>
#include <atlbase.h>
#include <vector>
#include <iostream>

// 声明未公开的IVirtualDesktopManagerInternal接口
MIDL_INTERFACE("a5cd92ff-29be-454c-8d04-d82879fb3f1b")
IVirtualDesktopManagerInternal : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetCount(UINT *count) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetDesktops(IObjectArray **desktops) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetCurrentDesktop(IVirtualDesktop **desktop) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetDesktopById(REFGUID desktopId, IVirtualDesktop **desktop) = 0;
    virtual HRESULT STDMETHODCALLTYPE CreateDesktop(IVirtualDesktop **desktop) = 0;
    virtual HRESULT STDMETHODCALLTYPE RemoveDesktop(IVirtualDesktop *desktop, IVirtualDesktop *fallbackDesktop) = 0;
    virtual HRESULT STDMETHODCALLTYPE SwitchDesktop(IVirtualDesktop *desktop) = 0;
};

// 声明IVirtualDesktop接口
MIDL_INTERFACE("a1841308-3541-4fab-bc81-f71556f20b4a")
IVirtualDesktop : public IUnknown
{
public:
    virtual HRESULT STDMETHODCALLTYPE GetID(GUID *desktopId) = 0;
    virtual HRESULT STDMETHODCALLTYPE GetName(LPWSTR *name) = 0;
};

// 定义VirtualDesktopManager组件的CLSID
const CLSID CLSID_VirtualDesktopManager = { 0xAA509086, 0x5CA9, 0x4C25, { 0x8F, 0x95, 0x58, 0x9D, 0x3C, 0x07, 0xB4, 0x8A } };

int main()
{
    // 初始化COM,必须使用单线程单元(STA)模型
    HRESULT hr = CoInitializeEx(NULL, COINIT_APARTMENTTHREADED | COINIT_DISABLE_OLE1DDE);
    if (FAILED(hr))
    {
        std::cerr << "COM初始化失败,错误码:" << hr << std::endl;
        return 1;
    }

    // 获取IVirtualDesktopManagerInternal接口
    CComPtr<IVirtualDesktopManagerInternal> pVDMInternal;
    hr = CoCreateInstance(CLSID_VirtualDesktopManager, NULL, CLSCTX_ALL, IID_PPV_ARGS(&pVDMInternal));
    if (FAILED(hr))
    {
        std::cerr << "获取IVirtualDesktopManagerInternal失败,错误码:" << hr << std::endl;
        CoUninitialize();
        return 1;
    }

    // 获取所有虚拟桌面的对象数组
    CComPtr<IObjectArray> pDesktopArray;
    hr = pVDMInternal->GetDesktops(&pDesktopArray);
    if (FAILED(hr))
    {
        std::cerr << "获取虚拟桌面数组失败,错误码:" << hr << std::endl;
        CoUninitialize();
        return 1;
    }

    // 获取虚拟桌面数量
    UINT desktopCount = 0;
    hr = pDesktopArray->GetCount(&desktopCount);
    if (FAILED(hr))
    {
        std::cerr << "获取虚拟桌面数量失败,错误码:" << hr << std::endl;
        CoUninitialize();
        return 1;
    }

    std::cout << "找到" << desktopCount << "个虚拟桌面:" << std::endl;

    // 遍历每个虚拟桌面,获取信息
    for (UINT i = 0; i < desktopCount; ++i)
    {
        CComPtr<IVirtualDesktop> pDesktop;
        hr = pDesktopArray->GetAt(i, IID_PPV_ARGS(&pDesktop));
        if (FAILED(hr))
        {
            std::cerr << "获取第" << i+1 << "个虚拟桌面失败,错误码:" << hr << std::endl;
            continue;
        }

        // 获取虚拟桌面唯一GUID
        GUID desktopId;
        hr = pDesktop->GetID(&desktopId);
        if (SUCCEEDED(hr))
        {
            wchar_t guidStr[39];
            StringFromGUID2(desktopId, guidStr, sizeof(guidStr)/sizeof(wchar_t));
            std::wcout << L"桌面" << i+1 << L" GUID: " << guidStr << std::endl;
        }

        // 获取虚拟桌面名称
        LPWSTR pName = nullptr;
        hr = pDesktop->GetName(&pName);
        if (SUCCEEDED(hr) && pName)
        {
            std::wcout << L"桌面" << i+1 << L" 名称: " << pName << std::endl;
            CoTaskMemFree(pName); // 必须手动释放接口分配的内存
        }

        std::cout << "------------------------" << std::endl;
    }

    CoUninitialize();
    return 0;
}

关键注意事项

  1. 接口兼容性:IVirtualDesktopManagerInternal是内部接口,微软未承诺长期兼容,但从Windows 10 1607到Windows 11的所有版本中都能正常使用;
  2. COM线程模型:必须用COINIT_APARTMENTTHREADED初始化COM,否则接口调用会失败;
  3. 内存管理:GetName返回的字符串需要用CoTaskMemFree释放,避免内存泄漏;
  4. 权限要求:普通用户权限即可运行,无需管理员权限。

补充说明

如果仅需查询某个窗口所属的虚拟桌面,可以使用公开的IVirtualDesktopManager接口,它的GetWindowDesktopId方法能直接获取窗口对应的虚拟桌面GUID,无需依赖内部接口。

内容的提问来源于stack exchange,提问作者Dimitrios Staikos

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.28 15:32:42