ReactOS 图形系统分析(11):映射设备对象 — MDEVOBJ(多显示器)

ReactOS 图形系统分析(11):映射设备对象 --- MDEVOBJ(多显示器)

1. 概述

MDEVOBJ(映射设备对象,Mapping Device Object)位于 win32ss/gdi/eng/mdevobj.c(实现)与 mdevobj.h(定义),是 GDI 引擎中聚合多个物理设备(PDEV)以支持多显示器的顶层对象。

复制代码
MDEVOBJ (映射设备 ← 本分析对象)
   ├── dev[0].ppdev → PDEVOBJ (显示器 0,主显示器)
   ├── dev[1].ppdev → PDEVOBJ (显示器 1)
   ├── dev[2].ppdev → PDEVOBJ (显示器 2)
   └── ppdevGlobal  → PDEVOBJ (虚拟全局桌面设备,cDev>1 时)

全局对象 gpmdev 是 win32k 的当前映射设备,gpmdev->ppdevGlobal 代表虚拟全局桌面(多显示器合成后的总画布),被大量用于:

  • SM_CXSCREEN / SM_CYSCREEN 系统度量

  • GDI 句柄表的 DevCaps(全局设备能力)

  • 显示器关联(UserAttachMonitor

  • 桌面渲染目标

    win32k 初始化 (winsta.c: UserCreateWindowStation)
    ↓ PDEVOBJ_lChangeDisplaySettings → MDEVOBJ_Create
    MDEVOBJ (gpmdev)
    ├── 为每个 GRAPHICS_DEVICE 创建/复用 PDEV
    └── ppdevGlobal = 虚拟全局桌面


2. 核心数据结构(mdevobj.h(file:///d:/reactos/win32ss/gdi/eng/mdevobj.h#L8-L22))

c 复制代码
typedef struct _MDEVDISPLAY
{
    PPDEVOBJ ppdev;              // 单个物理设备
} MDEVDISPLAY, *PMDEVDISPLAY;

typedef struct _MDEVOBJ
{
    ULONG cDev;                  // 设备数量
    PPDEVOBJ ppdevGlobal;        // 全局桌面设备(多显示器时存在)
    MDEVDISPLAY dev[0];          // 设备数组(灵活数组,随数量增长)
} MDEVOBJ, *PMDEVOBJ;

extern PMDEVOBJ gpmdev;          // 全局映射设备(FIXME: 应存入 gpDispInfo->pmdev)

关键设计:

  • dev[0]灵活数组 (C99 flexible array),对象实际大小为 sizeof(MDEVOBJ) + cDev * sizeof(MDEVDISPLAY),随显示器数量动态扩展
  • ppdevGlobal虚拟桌面的 PDEV------多显示器时各物理 PDEV 像素映射到全局桌面的不同区域,绘图统一在全局桌面上进行

3. MDEVOBJ_Create --- 创建设备集合

#mermaid-svg-WROKOrNmloQNxnC7{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-WROKOrNmloQNxnC7 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-WROKOrNmloQNxnC7 .error-icon{fill:#552222;}#mermaid-svg-WROKOrNmloQNxnC7 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-WROKOrNmloQNxnC7 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-WROKOrNmloQNxnC7 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-WROKOrNmloQNxnC7 .marker.cross{stroke:#333333;}#mermaid-svg-WROKOrNmloQNxnC7 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-WROKOrNmloQNxnC7 p{margin:0;}#mermaid-svg-WROKOrNmloQNxnC7 .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-WROKOrNmloQNxnC7 .cluster-label text{fill:#333;}#mermaid-svg-WROKOrNmloQNxnC7 .cluster-label span{color:#333;}#mermaid-svg-WROKOrNmloQNxnC7 .cluster-label span p{background-color:transparent;}#mermaid-svg-WROKOrNmloQNxnC7 .label text,#mermaid-svg-WROKOrNmloQNxnC7 span{fill:#333;color:#333;}#mermaid-svg-WROKOrNmloQNxnC7 .node rect,#mermaid-svg-WROKOrNmloQNxnC7 .node circle,#mermaid-svg-WROKOrNmloQNxnC7 .node ellipse,#mermaid-svg-WROKOrNmloQNxnC7 .node polygon,#mermaid-svg-WROKOrNmloQNxnC7 .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-WROKOrNmloQNxnC7 .rough-node .label text,#mermaid-svg-WROKOrNmloQNxnC7 .node .label text,#mermaid-svg-WROKOrNmloQNxnC7 .image-shape .label,#mermaid-svg-WROKOrNmloQNxnC7 .icon-shape .label{text-anchor:middle;}#mermaid-svg-WROKOrNmloQNxnC7 .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-WROKOrNmloQNxnC7 .rough-node .label,#mermaid-svg-WROKOrNmloQNxnC7 .node .label,#mermaid-svg-WROKOrNmloQNxnC7 .image-shape .label,#mermaid-svg-WROKOrNmloQNxnC7 .icon-shape .label{text-align:center;}#mermaid-svg-WROKOrNmloQNxnC7 .node.clickable{cursor:pointer;}#mermaid-svg-WROKOrNmloQNxnC7 .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-WROKOrNmloQNxnC7 .arrowheadPath{fill:#333333;}#mermaid-svg-WROKOrNmloQNxnC7 .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-WROKOrNmloQNxnC7 .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-WROKOrNmloQNxnC7 .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WROKOrNmloQNxnC7 .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-WROKOrNmloQNxnC7 .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WROKOrNmloQNxnC7 .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-WROKOrNmloQNxnC7 .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-WROKOrNmloQNxnC7 .cluster text{fill:#333;}#mermaid-svg-WROKOrNmloQNxnC7 .cluster span{color:#333;}#mermaid-svg-WROKOrNmloQNxnC7 div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-WROKOrNmloQNxnC7 .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-WROKOrNmloQNxnC7 rect.text{fill:none;stroke-width:0;}#mermaid-svg-WROKOrNmloQNxnC7 .icon-shape,#mermaid-svg-WROKOrNmloQNxnC7 .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-WROKOrNmloQNxnC7 .icon-shape p,#mermaid-svg-WROKOrNmloQNxnC7 .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-WROKOrNmloQNxnC7 .icon-shape .label rect,#mermaid-svg-WROKOrNmloQNxnC7 .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-WROKOrNmloQNxnC7 .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-WROKOrNmloQNxnC7 .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-WROKOrNmloQNxnC7 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 是









MDEVOBJ_Create

(pustrDeviceName, pdm)
分配初始 MDEV

sizeof(MDEVOBJ)+1×MDEVDISPLAY
cDev = 0

iDevNum = 0
指定设备名?
EngpFindGraphicsDevice(名字, 0)

只找这一个设备
EngpFindGraphicsDevice(NULL, iDevNum)

逐个枚举所有图形设备
找到设备?
枚举结束,跳出循环
无 pdm 参数?
用空 DEVMODE

(稍后从注册表读默认)
用请求的 pdm
读取加速等级

EngpGetDisplayDriverAccelerationLevel
LDEVOBJ_bProbeAndCaptureDevmode

选择最佳模式
PDEVOBJ_Create 成功?
扩展 MDEV 数组

(重分配 + 拷贝旧数据)
跳过该显示器
PDEVOBJ_vReference +

devcDev.ppdev = ppdev

cDev++
cDev == 0?
销毁 MDEV 返回 NULL
返回 MDEV

关键代码:

c 复制代码
PMDEVOBJ MDEVOBJ_Create(PUNICODE_STRING pustrDeviceName, PDEVMODEW pdm)
{
    pmdev = ExAllocatePoolZero(PagedPool, sizeof(MDEVOBJ) + sizeof(MDEVDISPLAY), GDITAG_MDEV);

    while (TRUE)
    {
        /* 枚举:指定设备 or 全部设备(逐个) */
        if (pustrDeviceName)
            pGraphicsDevice = (iDevNum == 0) ? EngpFindGraphicsDevice(pustrDeviceName, 0) : NULL;
        else
            pGraphicsDevice = EngpFindGraphicsDevice(NULL, iDevNum);
        iDevNum++;
        if (!pGraphicsDevice) break;

        /* 无请求模式 → 空 DEVMODE(LDEVOBJ_bProbeAndCaptureDevmode 从注册表读默认) */
        if (!pdm) { RtlZeroMemory(&dmDefault, sizeof(dmDefault)); dmDefault.dmSize = sizeof(dmDefault); }

        dwAccelerationLevel = EngpGetDisplayDriverAccelerationLevel(pGraphicsDevice);

        /* 选模式 + 创建/复用 PDEV */
        if (LDEVOBJ_bProbeAndCaptureDevmode(pGraphicsDevice, pdm ? pdm : &dmDefault, &localPdm, !pdm))
            ppdev = PDEVOBJ_Create(pGraphicsDevice, localPdm, dwAccelerationLevel, LDEV_DEVICE_DISPLAY);

        if (ppdev)
        {
            /* 数组已满 → 重分配扩展(拷贝旧数据后释放) */
            if (pmdev->cDev >= 1)
            {
                pmdevBigger = ExAllocatePoolZero(PagedPool,
                    sizeof(MDEVOBJ) + (pmdev->cDev + 1) * sizeof(MDEVDISPLAY), GDITAG_MDEV);
                RtlCopyMemory(pmdevBigger, pmdev, sizeof(MDEVOBJ) + pmdev->cDev * sizeof(MDEVDISPLAY));
                ExFreePoolWithTag(pmdev, GDITAG_MDEV);
                pmdev = pmdevBigger;
            }
            PDEVOBJ_vReference(ppdev);
            pmdev->dev[pmdev->cDev].ppdev = ppdev;
            pmdev->cDev++;
        }
    }

    if (pmdev->cDev == 0) { MDEVOBJ_vDestroy(pmdev); return NULL; }
    return pmdev;
}

要点:

  • 设备枚举用 EngpFindGraphicsDevice(device.c 提供),按序号逐个取或按名取
  • 每个设备独立选模式(LDEVOBJ_bProbeAndCaptureDevmode)并创建 PDEV(可复用,见《ReactOS 图形系统分析_9.md》)
  • 数组动态扩展:增加显示器时重新分配更大结构并迁移数据

4. 启用 / 禁用 / 销毁

4.1 MDEVOBJ_vEnable --- 启用全部设备

c 复制代码
VOID MDEVOBJ_vEnable(PMDEVOBJ pmdev)
{
    for (i = 0; i < pmdev->cDev; i++)
    {
        PDEVOBJ_vEnableDisplay(pmdev->dev[i].ppdev);   // 逐个启用显示
    }
}

4.2 MDEVOBJ_bDisable --- 禁用全部设备(带回滚)

c 复制代码
BOOL MDEVOBJ_bDisable(PMDEVOBJ pmdev)
{
    /* 逐个禁用 */
    for (i = 0; i < pmdev->cDev; i++)
    {
        if (!PDEVOBJ_bDisableDisplay(pmdev->dev[i].ppdev))
        {
            bSuccess = FALSE;
            break;                       // 遇到失败
        }
    }

    if (!bSuccess)
    {
        /* 回滚:重新启用已禁用的设备,保证一致性 */
        for (j = 0; j < i; j++)
            PDEVOBJ_vEnableDisplay(pmdev->dev[i].ppdev);   // FIXME: 应为 dev[j]
    }
    return bSuccess;
}

设计要点: 禁用是"尽力而为"------任一设备禁用失败则回滚已禁用的设备,避免部分禁用状态。注意代码中回滚循环用了 dev[i](疑似笔误,应为 dev[j])。

4.3 MDEVOBJ_vDestroy --- 销毁

c 复制代码
VOID MDEVOBJ_vDestroy(PMDEVOBJ pmdev)
{
    for (i = 0; i < pmdev->cDev; i++)
        PDEVOBJ_vRelease(pmdev->dev[i].ppdev);   // 释放每个物理设备引用

    if (pmdev->cDev > 1)
        PDEVOBJ_vRelease(pmdev->ppdevGlobal);    // 多显示器时释放全局桌面

    ExFreePoolWithTag(pmdev, GDITAG_MDEV);       // 释放 MDEV 本身
}

5. 多显示器架构中的角色

#mermaid-svg-VTkfYguBJyo1kB6g{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-VTkfYguBJyo1kB6g .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-VTkfYguBJyo1kB6g .error-icon{fill:#552222;}#mermaid-svg-VTkfYguBJyo1kB6g .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-VTkfYguBJyo1kB6g .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-VTkfYguBJyo1kB6g .marker{fill:#333333;stroke:#333333;}#mermaid-svg-VTkfYguBJyo1kB6g .marker.cross{stroke:#333333;}#mermaid-svg-VTkfYguBJyo1kB6g svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-VTkfYguBJyo1kB6g p{margin:0;}#mermaid-svg-VTkfYguBJyo1kB6g .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-VTkfYguBJyo1kB6g .cluster-label text{fill:#333;}#mermaid-svg-VTkfYguBJyo1kB6g .cluster-label span{color:#333;}#mermaid-svg-VTkfYguBJyo1kB6g .cluster-label span p{background-color:transparent;}#mermaid-svg-VTkfYguBJyo1kB6g .label text,#mermaid-svg-VTkfYguBJyo1kB6g span{fill:#333;color:#333;}#mermaid-svg-VTkfYguBJyo1kB6g .node rect,#mermaid-svg-VTkfYguBJyo1kB6g .node circle,#mermaid-svg-VTkfYguBJyo1kB6g .node ellipse,#mermaid-svg-VTkfYguBJyo1kB6g .node polygon,#mermaid-svg-VTkfYguBJyo1kB6g .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-VTkfYguBJyo1kB6g .rough-node .label text,#mermaid-svg-VTkfYguBJyo1kB6g .node .label text,#mermaid-svg-VTkfYguBJyo1kB6g .image-shape .label,#mermaid-svg-VTkfYguBJyo1kB6g .icon-shape .label{text-anchor:middle;}#mermaid-svg-VTkfYguBJyo1kB6g .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-VTkfYguBJyo1kB6g .rough-node .label,#mermaid-svg-VTkfYguBJyo1kB6g .node .label,#mermaid-svg-VTkfYguBJyo1kB6g .image-shape .label,#mermaid-svg-VTkfYguBJyo1kB6g .icon-shape .label{text-align:center;}#mermaid-svg-VTkfYguBJyo1kB6g .node.clickable{cursor:pointer;}#mermaid-svg-VTkfYguBJyo1kB6g .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-VTkfYguBJyo1kB6g .arrowheadPath{fill:#333333;}#mermaid-svg-VTkfYguBJyo1kB6g .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-VTkfYguBJyo1kB6g .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-VTkfYguBJyo1kB6g .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VTkfYguBJyo1kB6g .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-VTkfYguBJyo1kB6g .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VTkfYguBJyo1kB6g .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-VTkfYguBJyo1kB6g .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-VTkfYguBJyo1kB6g .cluster text{fill:#333;}#mermaid-svg-VTkfYguBJyo1kB6g .cluster span{color:#333;}#mermaid-svg-VTkfYguBJyo1kB6g div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-VTkfYguBJyo1kB6g .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-VTkfYguBJyo1kB6g rect.text{fill:none;stroke-width:0;}#mermaid-svg-VTkfYguBJyo1kB6g .icon-shape,#mermaid-svg-VTkfYguBJyo1kB6g .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-VTkfYguBJyo1kB6g .icon-shape p,#mermaid-svg-VTkfYguBJyo1kB6g .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-VTkfYguBJyo1kB6g .icon-shape .label rect,#mermaid-svg-VTkfYguBJyo1kB6g .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-VTkfYguBJyo1kB6g .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-VTkfYguBJyo1kB6g .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-VTkfYguBJyo1kB6g :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 全局使用者
MDEVOBJ (gpmdev)
gdiinfo.ulHorzRes/ulVertRes
PDEVOBJ_pSurface
分辨率
主显示器渲染
扩展显示器渲染
扩展显示器渲染
ppdevGlobal

虚拟全局桌面 PDEV
dev0.ppdev

显示器 0 (主)
dev1.ppdev

显示器 1
dev2.ppdev

显示器 2
winsta.c

SM_CXSCREEN/SM_CYSCREEN

UserAttachMonitor
ntgdi/device.c

GDI HandleTable DevCaps

GreGetPhysicalDeviceHandle
metric.c

屏幕尺寸度量
屏幕表面
屏幕表面
屏幕表面

ppdevGlobal 的作用: 多显示器时每个物理 PDEV 有独立的屏幕表面,但应用绘图需要统一的虚拟桌面坐标空间。ppdevGlobal 聚合所有显示器尺寸,作为全局坐标基准(SM_CXSCREEN 等系统度量读取它)。

关键使用者(winsta.c(file:///d:/reactos/win32ss/user/ntuser/winsta.c#L269-L333)):

c 复制代码
/* 窗口站初始化时创建全局 MDEV */
lRet = PDEVOBJ_lChangeDisplaySettings(NULL, NULL, NULL, &gpmdev, TRUE);

/* 更新系统度量 */
UserUpdateMonitorSize((HDEV)gpmdev->ppdevGlobal);
gpsi->aiSysMet[SM_CXSCREEN] = gpmdev->ppdevGlobal->gdiinfo.ulHorzRes;
gpsi->aiSysMet[SM_CYSCREEN] = gpmdev->ppdevGlobal->gdiinfo.ulVertRes;

/* 关联显示器 */
UserAttachMonitor((HDEV)gpmdev->ppdevGlobal);

6. 完整数据流(win32k 启动建立多显示器环境)

gpmdev (全局 MDEV) PDEVOBJ_Create EngpFindGraphicsDevice MDEVOBJ_Create PDEVOBJ_lChangeDisplaySettings win32k (winsta.c) gpmdev (全局 MDEV) PDEVOBJ_Create EngpFindGraphicsDevice MDEVOBJ_Create PDEVOBJ_lChangeDisplaySettings win32k (winsta.c) #mermaid-svg-0OLqKpdIUglkZXO0{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-0OLqKpdIUglkZXO0 .error-icon{fill:#552222;}#mermaid-svg-0OLqKpdIUglkZXO0 .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0OLqKpdIUglkZXO0 .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0OLqKpdIUglkZXO0 .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0OLqKpdIUglkZXO0 .marker.cross{stroke:#333333;}#mermaid-svg-0OLqKpdIUglkZXO0 svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0OLqKpdIUglkZXO0 p{margin:0;}#mermaid-svg-0OLqKpdIUglkZXO0 .actor{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0OLqKpdIUglkZXO0 text.actor>tspan{fill:black;stroke:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .actor-line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0OLqKpdIUglkZXO0 .innerArc{stroke-width:1.5;stroke-dasharray:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .messageLine0{stroke-width:1.5;stroke-dasharray:none;stroke:#333;}#mermaid-svg-0OLqKpdIUglkZXO0 .messageLine1{stroke-width:1.5;stroke-dasharray:2,2;stroke:#333;}#mermaid-svg-0OLqKpdIUglkZXO0 #arrowhead path{fill:#333;stroke:#333;}#mermaid-svg-0OLqKpdIUglkZXO0 .sequenceNumber{fill:white;}#mermaid-svg-0OLqKpdIUglkZXO0 #sequencenumber{fill:#333;}#mermaid-svg-0OLqKpdIUglkZXO0 #crosshead path{fill:#333;stroke:#333;}#mermaid-svg-0OLqKpdIUglkZXO0 .messageText{fill:#333;stroke:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .labelBox{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0OLqKpdIUglkZXO0 .labelText,#mermaid-svg-0OLqKpdIUglkZXO0 .labelText>tspan{fill:black;stroke:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .loopText,#mermaid-svg-0OLqKpdIUglkZXO0 .loopText>tspan{fill:black;stroke:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .loopLine{stroke-width:2px;stroke-dasharray:2,2;stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);}#mermaid-svg-0OLqKpdIUglkZXO0 .note{stroke:#aaaa33;fill:#fff5ad;}#mermaid-svg-0OLqKpdIUglkZXO0 .noteText,#mermaid-svg-0OLqKpdIUglkZXO0 .noteText>tspan{fill:black;stroke:none;}#mermaid-svg-0OLqKpdIUglkZXO0 .activation0{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0OLqKpdIUglkZXO0 .activation1{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0OLqKpdIUglkZXO0 .activation2{fill:#f4f4f4;stroke:#666;}#mermaid-svg-0OLqKpdIUglkZXO0 .actorPopupMenu{position:absolute;}#mermaid-svg-0OLqKpdIUglkZXO0 .actorPopupMenuPanel{position:absolute;fill:#ECECFF;box-shadow:0px 8px 16px 0px rgba(0,0,0,0.2);filter:drop-shadow(3px 5px 2px rgb(0 0 0 / 0.4));}#mermaid-svg-0OLqKpdIUglkZXO0 .actor-man line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;}#mermaid-svg-0OLqKpdIUglkZXO0 .actor-man circle,#mermaid-svg-0OLqKpdIUglkZXO0 line{stroke:hsl(259.6261682243, 59.7765363128%, 87.9019607843%);fill:#ECECFF;stroke-width:2px;}#mermaid-svg-0OLqKpdIUglkZXO0 :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} loop每个图形设备 初始化窗口站 (创建全局 MDEV)MDEVOBJ_Create(NULL, NULL) 全部设备EngpFindGraphicsDevice(NULL, i)GRAPHICS_DEVICE读加速等级 + 选模式PDEVOBJ_Create (创建/复用)ppdevdevcDev++.ppdev = ppdev (扩展数组)返回 gpmdev读 ppdevGlobal 分辨率 → 系统度量UserAttachMonitor (关联显示器)


7. 设计特点总结

  1. 聚合抽象 :MDEV 把多个物理设备(PDEV)聚合成一个可整体操作的映射设备,win32k 只需操作 gpmdev 一个对象。
  2. 灵活数组dev[0] 灵活数组 + 动态重分配,设备数量变化时按需扩展。
  3. 全局桌面设备ppdevGlobal 虚拟桌面聚合所有显示器尺寸,是系统度量与全局渲染的坐标基准。
  4. PDEV 复用PDEVOBJ_Create 内部对相同配置复用已有 PDEV,MDEV 只是引用聚合。
  5. 禁用回滚MDEVOBJ_bDisable 在部分失败时回滚已禁用的设备,保持状态一致。
  6. 单显示器退化cDev == 1 时不创建 ppdevGlobal(cDev > 1 才释放),单显示器场景零额外开销。

8. 源码索引

位置 关键内容
mdevobj.h(file:///d:/reactos/win32ss/gdi/eng/mdevobj.h#L8-L22) MDEVDISPLAY、MDEVOBJ 结构定义、gpmdev 全局
mdevobj.c(file:///d:/reactos/win32ss/gdi/eng/mdevobj.c#L16-L26) MDEVOBJ_vEnable
mdevobj.c(file:///d:/reactos/win32ss/gdi/eng/mdevobj.c#L28-L54) MDEVOBJ_bDisable(回滚逻辑)
mdevobj.c(file:///d:/reactos/win32ss/gdi/eng/mdevobj.c#L56-L161) MDEVOBJ_Create(设备聚合主流程)
mdevobj.c(file:///d:/reactos/win32ss/gdi/eng/mdevobj.c#L163-L177) MDEVOBJ_vDestroy
ntuser/winsta.c(file:///d:/reactos/win32ss/user/ntuser/winsta.c#L269-L333) gpmdev 全局使用(系统度量/显示器关联)
ntgdi/device.c(file:///d:/reactos/win32ss/gdi/ntgdi/device.c#L20-L42) ppdevGlobal 用于全局设备能力

关联模块:

  • pdevobj.h(file:///d:/reactos/win32ss/gdi/eng/pdevobj.h) / pdevobj.c --- MDEV 聚合的物理设备
  • ldevobj.h(file:///d:/reactos/win32ss/gdi/eng/ldevobj.h) / ldevobj.c --- PDEV 创建时用到的模式选择
  • device.c(file:///d:/reactos/win32ss/gdi/eng/device.c) --- EngpFindGraphicsDevice 设备枚举

关联文档:

  • 《ReactOS 图形系统分析_4.md》--- GDI 引擎整体架构(四层设备对象:LDEV→PDEV→SURFACE→MDEV)
  • 《ReactOS 图形系统分析_9.md》--- PDEVOBJ(MDEV 的组成单元)
  • 《ReactOS 图形系统分析_10.md》--- LDEVOBJ(PDEV 的函数表来源)

本文档基于 ReactOS 源代码 win32ss/gdi/eng/mdevobj.cmdevobj.h 分析(2026 年 8 月)

相关推荐
国科安芯20 小时前
详解ADC 规则组外部触发:CH2、CH3 与 TRGO2
运维·网络·单片机·嵌入式硬件·mcu·系统架构
Dr.kangder21 小时前
嵌入式面试总结(十)——流水线
单片机·嵌入式硬件·面试·职场和发展·系统架构·嵌入式
ZJU_统一阿萨姆1 天前
【推理优化】推理的本质:prefill 与 decode 两阶段
语言模型·系统架构
郑州光合科技余经理1 天前
餐饮预定系统架构拆解:订单链路、权限组织与私有化源码交付
java·开发语言·前端·数据库·人工智能·系统架构·php
youngerwang1 天前
【软件系统架构师案例分析每日深耕 Day 13】多质量属性权衡:电商大促架构设计
系统架构·案例分析·电商大促·多质量属性
火云牌神2 天前
前后端分离:约束 AI 分工,避免接口耦合与职责错乱
人工智能·系统架构·ai编程·前后端分离·vibecoding
简单同学2 天前
深入 NVIDIA GPU:CSDN 专栏发布索引
系统架构·gpu
youngerwang2 天前
【软件系统架构案例分析每日深耕 Day 17】CBAM成本效益分析:微服务改造决策
微服务·系统架构·cbam·案例每日深耕·微服务改造
新点一讯2 天前
玩CS2推荐一个高配置电竞显示器?蚂蚁电竞ANT257PF硬核解析
计算机外设