Win32:一个窗口在其整个生命周期中是不是具有相同的 HDC?

Posted

技术标签:

【中文标题】Win32:一个窗口在其整个生命周期中是不是具有相同的 HDC?【英文标题】:Win32: Does a window have the same HDC for its entire lifetime?Win32:一个窗口在其整个生命周期中是否具有相同的 HDC? 【发布时间】:2011-01-05 16:01:47 【问题描述】:

我可以在油漆周期之外使用直流电吗? 我的窗口的 DC 是否保证永久有效?

我正在尝试计算我的控件的设备上下文 (DC) 的有效期。

我知道我可以打电话:

GetDC(hWnd);

获取我的控件窗口的设备上下文,但这是否允许?

当 Windows 向我发送 WM_PAINT 消息时,我应该调用 BeginPaint/EndPaint 以正确确认我已绘制它,并在内部清除无效区域:

BeginPaint(hWnd, outpaintStruct);
try
   //Do my painting
finally
   EndPaint(hWnd, paintStruct);
end;

但是调用 BeginPaint 也会在 PAINTSTRUCT 结构中返回一个 DC。这是我应该作画的 DC。

我在文档中找不到任何内容表明 BeginPaint() 返回的 DC 与我从 GetDC() 获得的 DC 相同。

尤其是现在,在桌面合成时代,在我在 BeginPaint 之外获得的 DC 上绘画是否有效?

似乎有两种方法可以让我在绘画周期中使用 DC 进行绘画:

    dc = GetDC(hWnd);

    BeginPaint(&paintStruct);

有第三种方法,但它似乎是我开发的 Borland Delphi 的一个错误。

在WM_PAINT 处理期间,Delphi 认为 wParam 是一个 DC,并继续在其上进行绘制。而 MSDN 说 WM_PAINT 消息的 wParam 未使用。

为什么

我的真正目标is to try to keep a persistent GDI+ Graphics object 对抗 HDC,这样我就可以使用 GDI+ 的一些性能更好的功能,这些功能依赖于持久性 DC。

在 WM_PAINT 消息处理期间,我想在画布上绘制 GDI+ 图像。下面的nieve版本很慢:

WM_PAINT:

   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);
   Graphics g = new Graphics(ps.hdc);
   g.DrawImage(m_someBitmap, 0, 0);
   g.Destroy();
   EndPaint(h_hwnd, ps);

GDI 包含一个执行速度更快的位图,一个 CachedBitmap。但是不加思索地使用它不会带来性能优势:

WM_PAINT:

   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);

   Graphics g = new Graphics(ps.hdc);
   CachedBitmap bm = new CachedBitmap(m_someBitmap, g);
   g.DrawCachedBitmap(m_bm, 0, 0);
   bm.Destroy();
   g.Destroy();
   EndPaint(h_hwnd, ps);

性能提升来自于创建 CachedBitmap 一次,所以程序初始化:

m_graphics = new Graphics(GetDC(m_hwnd));
m_cachedBitmap = new CachedBitmap(b_someBitmap, m_graphcis);

现在在绘制周期:

WM_PAINT:

   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);
   m_graphics.DrawCachedBitmap(m_cachedBitmap, 0, 0);
   EndPaint(h_hwnd, ps);
        

除了现在我相信只要应用程序正在运行,我在程序初始化后获得的 DC 将与我的窗口相同。这意味着它可以通过:

快速用户切换 组合启用/禁用 主题切换 主题禁用

我在 MSDN 中找不到任何东西可以保证只要特定窗口存在,相同的 DC 就会用于该窗口。

注意:我没有使用双缓冲,because i want to be a good developer, and do the right thing。 * 有时这意味着双缓冲不好。

【问题讨论】:

【参考方案1】:

我所知道的唯一可能(或可能不会)做您正在寻找的事情的唯一方法是使用 CS_OWNDC 类样式创建窗口。

这样做是为类中的每个窗口分配一个唯一的设备上下文。

编辑

来自链接的 MSDN 文章:

设备上下文是一组特殊的 应用程序使用的值 在他们的客户区绘图 视窗。系统需要设备 显示器上每个窗口的上下文 但允许在如何 系统存储和处理该设备 上下文。

如果没有设备上下文样式 明确给出,系统假设 每个窗口使用一个设备上下文 从上下文池中检索 由系统维护。在这样的 情况下,每个窗口必须检索和 之前初始化设备上下文 绘画并在绘画后释放它。

为了避免检索设备上下文 每次它需要在一个 窗口中,应用程序可以指定 窗口类的 CS_OWNDC 样式。 这种类风格指导系统 创建一个私有设备上下文——即 是,分配一个唯一的设备 类中每个窗口的上下文。 应用程序只需要检索 上下文一次,然后全部使用 后续绘画。

Windows 95/98/Me:虽然 CS_OWNDC风格很方便,用它 仔细,因为每个设备上下文 使用很大一部分 64K GDI 堆。

也许这个例子会更好地说明 CS_OWNDC 的使用:

#include <windows.h>

static TCHAR ClassName[] = TEXT("BitmapWindow");
static TCHAR WindowTitle[] = TEXT("Bitmap Window");

HDC m_hDC;
HWND m_hWnd;

LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)

    static PAINTSTRUCT ps;

    switch (msg)
    
    case WM_PAINT:
        
            BeginPaint(hWnd, &ps);

            if (ps.hdc == m_hDC)
                MessageBox(NULL, L"ps.hdc == m_hDC", WindowTitle, MB_OK);
            else
                MessageBox(NULL, L"ps.hdc != m_hDC", WindowTitle, MB_OK);

            if (ps.hdc == GetDC(hWnd))
                MessageBox(NULL, L"ps.hdc == GetDC(hWnd)", WindowTitle, MB_OK);
            else
                MessageBox(NULL, L"ps.hdc != GetDC(hWnd)", WindowTitle, MB_OK);

            RECT r;
            SetRect(&r, 10, 10, 50, 50);
            FillRect(m_hDC, &r, (HBRUSH) GetStockObject( BLACK_BRUSH ));

            EndPaint(hWnd, &ps);
            return 0;
        
    case WM_DESTROY:
        
            PostQuitMessage(0);
            return 0;
        
    
    return DefWindowProc(hWnd, msg, wParam, lParam);


int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd)
   
    WNDCLASSEX wcex;

    wcex.cbClsExtra = 0;
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.cbWndExtra = 0;
    wcex.hbrBackground = (HBRUSH) GetStockObject( WHITE_BRUSH );
    wcex.hCursor = LoadCursor( NULL, IDC_ARROW );
    wcex.hIcon = LoadIcon( NULL, IDI_APPLICATION );
    wcex.hIconSm = NULL;
    wcex.hInstance = hInstance;
    wcex.lpfnWndProc = WndProc;
    wcex.lpszClassName = ClassName;
    wcex.lpszMenuName = NULL;
    wcex.style = CS_OWNDC;

    if (!RegisterClassEx(&wcex))
        return 0;

    DWORD dwExStyle = 0;
    DWORD dwStyle = WS_OVERLAPPEDWINDOW | WS_VISIBLE;

    m_hWnd = CreateWindowEx(dwExStyle, ClassName, WindowTitle, dwStyle, 0, 0, 300, 300, NULL, NULL, hInstance, NULL);

    if (!m_hWnd)
        return 0;

    m_hDC = GetDC(m_hWnd);

    MSG msg;
    while (GetMessage(&msg, NULL, 0, 0))
    
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    

    return msg.wParam;

CS_OWNDC 标志与 CS_CLASSDC 标志混淆:

分配一个设备上下文以供类中的所有窗口共享。因为窗口类是特定于进程的,所以一个应用程序的多个线程可以创建同一个类的窗口。线程也有可能同时尝试使用设备上下文。发生这种情况时,系统只允许一个线程成功完成其绘图操作。

如果所有其他方法都失败了,只需 reconstruct CachedBitmap。

在构造 CachedBitmap 对象时,必须将 Graphics 对象的地址传递给构造函数。如果与该 Graphics 对象关联的屏幕在构建缓存位图后其位深度发生了变化,则 DrawCachedBitmap 方法将失败,您应该重建缓存位图。或者,您可以挂钩显示更改通知消息并在那时重建缓存的位图。

我并不是说 CS_OWNDC 是完美的解决方案,但它是迈向更好解决方案的一步。

编辑

在使用 CS_OWNDC 标志进行屏幕分辨率/位深度更改测试期间,示例程序似乎保留了相同的 DC,但是,当删除该标志时,DC 是不同的(Window 7 64-bit Ultimate)(应该 在不同的操作系统版本上工作相同......虽然测试不会有什么坏处)。

编辑2

此示例不调用 GetUpdateRect 来检查窗口是否需要在 WM_PAINT 期间绘制。这是一个错误。

【讨论】:

我不太明白那个标志的含义。另一种方法是在该类的所有窗口之间共享一个 DC。这意味着每个编辑框、每个窗口、每个进程或系统上的每个登录用户都共享同一个 DC?我不这么认为。 @Ian Boyd - 您的理解不正确。该标志仅适用于 创建并使用 RegisterClassEx 函数注册的窗口类。因此,如果您创建一个名为BitmapWindow 的窗口类并使用该标志注册它,那么使用BitmapWindow 类创建的所有窗口将共享一个DC。注册您的课程并创建您的窗口后,您调用 GetDC 一次 并存储 DC。您的编辑框示例存在缺陷,因为它们的窗口类为“编辑”而不是“位图窗口”。每个登录的用户都会获得一个唯一的 DC。 仔细阅读后,您的建议似乎是为了帮助我想要的。 Windows 维护一个设备上下文池,您必须在绘制之前检索 dc,并在绘制之后释放它。看起来 CS_CLASSDC 是 CS_OWNDC 的倒数 - 进程中特定类的所有窗口共享相同的 dc,并且一次只允许一个线程访问该 DC。 @Ian Boyd - 看起来你的评论是在我编辑期间发表的。看来你现在有了更好的理解。希望您能找到合适的解决方案。 引用的那一行,关于 DrawCachedBitmap 失败,看起来这可能是微软处理画布更改的预期方式。所以我希望我可以为此 +1。并且您测试 DC 更改的示例程序值得另一个 +1。但是阿德里安·麦卡锡仍然得到了答案,因为他是我问题的答案——而你回答了我的问题。应该能够标记两个答案。【参考方案2】:

也有例外,但一般情况下,您每次致电GetDCBeginPaint 时可能会获得不同的DC。因此,您不应该尝试在 DC 中保存状态。 (如果您必须为了性能而这样做,您可以为一类窗口或特定窗口实例创建特殊的 DC,但这听起来并不是您真正需要或想要的。)

但是,大多数情况下,这些 DC 是兼容的。它们将代表相同的图形模式,因此即使您使用不同的 DC,您的兼容位图也应该可以工作。

有一些 Windows 消息会告诉您图形模式何时发生变化,例如 WM_DISPLAYCHANGEWM_PALETTECHANGED。您可以收听这些,并重新创建缓存的位图。由于这些事件很少见,因此您不必担心此时重新创建缓存位图对性能的影响。

您还可以收到主题更改等通知。那些不会改变图形模式——它们是一个更高级别的概念——所以你的缓存位图应该仍然与你得到的任何 DC 兼容。但是如果你想在主题改变时改变位图,你也可以听WM_THEMECHANGED

【讨论】:

如何缓存 DC,然后将我缓存的 DC 与 BeginPaint() 返回的 DC 进行比较?可以肯定地说,如果我得到相同的 DC 号码,那么它就是同一个......不是不同的。 "...每次调用 GetDC 或 BeginPaint 时,您可能会得到一个不同的 DC" 尽管我可能想问任何后续问题,但这直接回答了问题:A不保证 DC 在任何时间段内都有效。 @roygbiv:我的帖子中有很多声明。有没有你特别怀疑的?我的大部分回答来自经验、MSDN 文档和偶尔的 Raymond Chen 帖子(例如 blogs.msdn.com/oldnewthing/archive/2006/06/01/612970.aspx)。 我理解你的回答,这就是它被接受的原因。但是现在需要额外的后续操作。如果我调用 GetDC,它应该保持该 DC 有效,直到我调用 ReleaseDC。如果在我调用 GetDC 之后但在 ReleaseDC 之前有图形模式切换会发生什么?或者没有什么不好的事情发生,并且在应用程序的整个生命周期内保持一个 DC 而不释放它是完全有效的? 在应用程序的整个生命周期中保持 DC 通常是一种不好的形式,尽管您可能可以在较新版本的 Windows 上摆脱它。对于旧版本,“通用”DC 是一种有限的资源。如果你保留一个,你可以强制操作系统用完。如果您确实需要在窗口的整个生命周期内保留一个,请使用CS_OWNDC。见blogs.msdn.com/oldnewthing/archive/2006/06/01/612970.aspx【参考方案3】:

你可以在任何你喜欢的窗口上画画。它们都是有效的。一个窗口不只有一个 dc 可以一次代表它。因此,每次调用 GetDC - 并且 BeginPaint 内部都会这样做,您将获得一个新的、唯一的 dc,但它仍代表相同的显示区域。 完成后只需 ReleaseDC(或 EndPaint)。在 Windows 3.1 时代,设备上下文是一种有限或非常昂贵的系统资源,因此鼓励应用程序永远不要保留它们,而是从 GetDC 缓存中检索它们。现在,在窗口创建时创建一个 dc 并在窗口的生命周期内缓存它是完全可以接受的。

唯一的“问题”是,在处理WM_PAINT时,BeginPaint返回的dc会被裁剪为无效的rect,而保存的则不会。


但是,我不明白您尝试使用 gdiplus 实现的目标。通常,如果一个对象被...长时间选择到一个 dc 中,则该 dc 是一个内存 dc,而不是一个窗口 dc。


每次调用 GetDC 时,您都会获得一个新的 HDC,该 HDC 代表具有自己状态的不同设备上下文。因此,在一个 DC 上设置的对象、背景颜色、文本模式等不会影响通过对 GetDC 或 BeginPaint 的不同调用检索到的另一个 DC 的状态。

系统不能随机使客户端检索到的 HDC 无效,实际上在后台做了很多工作,以确保在显示模式切换之前检索到的 HDC 继续运行。即使更改位深度(从技术上讲会使 dc 不兼容),也不会以任何方式阻止应用程序继续使用 hdc 进行 blit。

也就是说,明智的做法是至少观察 WM_DISPLAYCHANGE,释放所有缓存的 DC 和设备位图,然后重新创建它们。

【讨论】:

想象一下调用 GetDC(m_hwnd) 并存储它。经过数周或数月的连续运行后,hdc 是否仍然有效且正确? 至于我想用 GDI+ 实现什么,我可以复制/粘贴我链接到的内容...。简短的版本是 CachedBitmap 类需要一个持久的图形对象,而该图形对象构造为 new Graphics(GetDC(m_hwnd))

以上是关于Win32:一个窗口在其整个生命周期中是不是具有相同的 HDC?的主要内容,如果未能解决你的问题,请参考以下文章

限制进程在其生命周期内可以打开的文件描述符的数量

Win32获取屏幕句柄的函数是啥?

React的父子组件生命周期

如何为具有生命周期'a的结构实现具有'静态生命周期的特征?

c++类中 各种成员的生命周期?

Android Activity组件及其生命周期