windows Win32:窗口在其整个生命周期内是否具有相同的 HDC?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/2074294/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-09-15 13:46:12  来源:igfitidea点击:

Win32: Does a window have the same HDC for its entire lifetime?

windowsgdi+gdipaintdevice-context

提问by Ian Boyd

Am i allowed to use a DC outside of a paint cycle? Is my window's DC guaranteed to be valid forever?

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

i'm trying to figure out how long my control's Device Context (DC) is valid.

我想弄清楚我的控件的设备上下文 (DC) 的有效期有多长。

i know that i can call:

我知道我可以打电话:

GetDC(hWnd);

to get the device context of my control's window, but is that allowed?

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

When Windows sends me a WM_PAINT message, i am supposed to call BeginPaint/EndPaintto properly acknowledge that i've painted it, and to internally clear the invalid region:

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

BeginPaint(hWnd, {out}paintStruct);
try
   //Do my painting
finally
   EndPaint(hWnd, paintStruct);
end;

But calling BeginPaint also returns me a DC inside the PAINTSTRUCT structure. This is the DC that i shouldbe painting on.

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

i cannot find anything in the documentation that says that the DC returned by BeginPaint() is the same DC that i would get from GetDC().

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

Especially now, in the days of Desktop Composition, is it valid to paint on a DC that i obtain outside of BeginPaint?

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

There seem to be 2 ways i can get a DC to paint on during a paint cycle:

在油漆周期中,我似乎有两种方法可以让 DC 进行油漆:

  1. dc = GetDC(hWnd);

  2. BeginPaint(&paintStruct);

  1. dc = GetDC(hWnd);

  2. 开始绘画(&paintStruct);

There is a 3rd way, but it seems to be a bug with the Borland Delphi that i develop with.

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

During WM_PAINTprocessing, Delphi believes that the wParam is a DC, and proceeds to paint on it. Whereas the MSDN says that the wParam of a WM_PAINT message is unused.

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

The Why

为什么

My real goal is to try to keep a persistent GDI+ Graphics objectagainst an HDC, so that i can use some better performing features of GDI+ that depend on having a persistent DC.

我的真正目标是尝试针对 HDC保持持久的 GDI+ 图形对象,以便我可以使用依赖于持久 DC 的 GDI+ 的一些性能更好的功能。

During the WM_PAINT message handling i want to draw a GDI+ image to the canvas. The following nieve version is very slow:

在 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 contains a faster performing bitmap, a CachedBitmap. But using it without thinking gives no performance benefit:

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);
}

The performance gain comes from creating the CachedBitmap once, so on program initialization:

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

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

And now on the paint cycle:

现在在油漆周期:

WM_PAINT:
{
   PAINTSTRUCT ps;
   BeginPaint(m_hwnd, ps);
   m_graphics.DrawCachedBitmap(m_cachedBitmap, 0, 0);
   EndPaint(h_hwnd, ps);
}        

Except now i'm trusting that the DC i obtained after program initializtion will be the same DC for my window as long as the application is running. This means that it survives through:

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

  • fast user switches
  • composition enabled/disabled
  • theme switching
  • theme disabling
  • 快速用户切换
  • 组合启用/禁用
  • 主题切换
  • 主题禁用

i find nothing in MSDN that guarantees that the same DC will be used for a particular window for as long as the window exists.

我在 MSDN 中找不到任何内容来保证只要窗口存在,相同的 DC 将用于特定窗口。

Note:i am not using double-buffering, because i want to be a good developer, and do the right thing. *Sometimes that means you double-buffering is bad.

注意:我没有使用双缓冲,因为我想成为一名优秀的开发人员,并做正确的事情*有时这意味着双缓冲不好。

回答by

The only way I know of that may (or may not) do what you are looking for is to create the window with the CS_OWNDCclass style.

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

What that does is allocates a unique device context for each window in the class.

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

Edit

编辑

From the linked MSDN article:

来自链接的 MSDN 文章:

A device context is a special set of values that applications use for drawing in the client area of their windows. The system requires a device context for each window on the display but allows some flexibility in how the system stores and treats that device context.

If no device-context style is explicitly given, the system assumes each window uses a device context retrieved from a pool of contexts maintained by the system. In such cases, each window must retrieve and initialize the device context before painting and free it after painting.

To avoid retrieving a device context each time it needs to paint inside a window, an application can specify the CS_OWNDC style for the window class. This class style directs the system to create a private device context — that is, to allocate a unique device context for each window in the class. The application need only retrieve the context once and then use it for all subsequent painting.

Windows 95/98/Me: Although the CS_OWNDC style is convenient, use it carefully, because each device context uses a significant portion of 64K GDI heap.

设备上下文是一组特殊的值,应用程序用于在其窗口的客户区中进行绘制。系统要求显示器上的每个窗口都有一个设备上下文,但允许系统在如何存储和处理该设备上下文方面具有一定的灵活性。

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

为了避免每次需要在窗口内绘制时检索设备上下文,应用程序可以为窗口类指定 CS_OWNDC 样式。这个类样式指示系统创建一个私有设备上下文——也就是说,为类中的每个窗口分配一个唯一的设备上下文。应用程序只需检索一次上下文,然后将其用于所有后续绘制。

Windows 95/98/Me:虽然 CS_OWNDC 样式很方便,但请谨慎使用,因为每个设备上下文使用 64K GDI 堆的很大一部分。

Perhaps this example will illustrate the use of CS_OWNDC better:

也许这个例子会更好地说明 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;
}

The CS_OWNDC flag is notto be confused with the CS_CLASSDC flag which:

该CS_OWNDC标志与CS_CLASSDC标志,该标志相混淆:

Allocates one device context to be shared by all windows in the class. Because window classes are process specific, it is possible for multiple threads of an application to create a window of the same class. It is also possible for the threads to attempt to use the device context simultaneously. When this happens, the system allows only one thread to successfully finish its drawing operation.

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

If all else fails just reconstructthe CachedBitmap.

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

When you construct a CachedBitmap object, you must pass the address of a Graphics object to the constructor. If the screen associated with that Graphics object has its bit depth changed after the cached bitmap is constructed, then the DrawCachedBitmap method will fail, and you should reconstruct the cached bitmap. Alternatively, you can hook the display change notification message and reconstruct the cached bitmap at that time.

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

I'm not saying that CS_OWNDC is the perfect solution, but it isone step towards a better solution.

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

Edit

编辑

The sample program seemed to retain the same DC during screen resolution / bit depth change testing with the CS_OWNDC flag, however, when that flag was removed, the DC's were different (Window 7 64-bit Ultimate)(shouldwork the same over differn OS versions... although it wouldn't hurt to test).

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

Edit2

编辑2

This example doesn't call GetUpdateRect to check if the window needs to be painted during the WM_PAINT. That is an error.

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

回答by Adrian McCarthy

There are exceptions, but in general, you may get a different DC each time you call GetDCor BeginPaint. Thus you shouldn't try to save state in the DC. (If you must do this for performance, there are special DCs you can create for a class of windows or a particular window instance, but it doesn't sound like that's what you really need or want.)

也有例外,但一般来说,每次调用GetDC或时可能会得到不同的 DC BeginPaint。因此,您不应该尝试在 DC 中保存状态。(如果您必须这样做以提高性能,您可以为一类窗口或特定窗口实例创建特殊的 DC,但这听起来并不是您真正需要或想要的。)

Most of the time, however, those DCs will be compatible. They will represent the same graphics mode, so your compatible bitmap should work, even if you get a different DC.

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

There are Windows messages that tell you when the graphics mode changes, like WM_DISPLAYCHANGEand WM_PALETTECHANGED. You can listen for these, and recreate your cached bitmap. Since those are rare events, you won't have to worry about the performance impact of recreating your cached bitmap at that point.

有 Windows 消息会告诉您图形模式何时更改,例如WM_DISPLAYCHANGEWM_PALETTECHANGED。您可以监听这些,并重新创建缓存的位图。由于这些是罕见事件,因此您不必担心此时重新创建缓存位图对性能的影响。

You can also get notifications for things like theme changes. Those don't change the graphics mode--they're a higher level concept--so your cached bitmap should still be compatible with any DC you get. But if you want to change bitmap when the theme changes, you can listen for WM_THEMECHANGEDas well.

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

回答by Chris Becke

You can draw onto whichever window dc pleases you. They're both valid. A window does not have just one dc that can represent it at a time. So each time you call GetDC - and BeginPaint internally does so, you will get a new, unique dc, that nonetheless represents the same display area. Just ReleaseDC (or EndPaint) when you're done with them. In the days of Windows 3.1 device contexts were a limited, or very expensive system resource, so applications were encouraged to never hold onto them, but to retrieve them from the GetDC cache. nowadays its perfectly acceptable to create a dc at window creation, and cache it for the life of the window.

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

The only "problem" is, when handling WM_PAINT, the dc returned by BeginPaint will be clipped to the invalid rect, and the saved one will not.

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



I don't however understand what you are attempting to achieve with gdiplus. Usually, if an object is ... selected into a dc for a long period of time, that dc is a memory dc, not a window dc.

然而,我不明白你试图用 gdiplus 实现什么。通常,如果一个对象被……长时间选择到 dc 中,则该 dc 是内存 dc,而不是窗口 dc。



Each time GetDC is called you WILL get a new HDC representing a distinct device context with its own state. So, objects, background colors, text modes etc. set on one DC will NOT effect that state of another DC retrieved by a different call to GetDC or BeginPaint.

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

The system cannot randomly invalidate HDCs retrieved by the client, and actually does a lot of work in the background to ensure that HDCs retrieved before a display mode switch, continue to function. Even changing the bit depth, that technically makes the dc's incompatible, will not, in any way, prevent an application from continuing to use an hdc to blit.

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

That said, it is wise to watch at LEAST for WM_DISPLAYCHANGE, release any cached DCs and device bitmaps, and recreate them.

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