views:

448

answers:

3

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

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

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/EndPaint to properly acknowledge that i've painted it, and to internally clear the invalid region:

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 should be painting on.

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().

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

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

  1. dc = GetDC(hWnd);

  2. BeginPaint(&paintStruct);

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

During WM_PAINT processing, 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.

The Why

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

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:
{
   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:

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:

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:

  • 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.

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.

+1  A: 

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.

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.


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.


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.

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.

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

Chris Becke
Imagine calling GetDC(m_hwnd) and storing it. After weeks or months of continuous running is that hdc still valid and correct?
Ian Boyd
As to what i'm trying to achieve with GDI+, i can copy/paste the content i linked to.... Short version is that CachedBitmap class needs a persistent graphics object, and that graphics object is constructed as new Graphics(GetDC(m_hwnd))
Ian Boyd
+3  A: 

There are exceptions, but in general, you may get a different DC each time you call GetDC or 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 an instance of windows, but it doesn't sound like that's what you really need or want.)

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.

There are windows messages that tell you when the graphics mode changes, like WM_DISPLAYCHANGE and 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.

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_THEMECHANGED as well.

Adrian McCarthy
What about caching the DC, and then comparing the DC i've cached with the DC returned by BeginPaint()? It's probably safe to say that if i get the same DC number, then it's the same one...not a different one.
Ian Boyd
**"...you may get a different DC each time you call GetDC or BeginPaint"** Despite any followup questions i may want to ask, that directly answers the question: A DC is not guaranteed to be valid for any length of time.
Ian Boyd
Please cite references for the statements in your post.
@roygbiv: There are many statements in my post. Is there one in particular you suspect? Most of my answer is from experience, MSDN documentation, and the occasional Raymond Chen post (like this one http://blogs.msdn.com/oldnewthing/archive/2006/06/01/612970.aspx).
Adrian McCarthy
@Ian Boyd: Perhaps my answer wasn't clear enough. A DC won't change between `GetDC()` and `ReleaseDC()`. It's not unusual to get a different DC on subsequent calls to `GetDC()` or `BeginPaint()`. But those DC's will tend to be compatible with each other unless the graphics mode is actually changed. Thus you can create your cached bitmap and continue to use it (even if you get a different window DC). That cached bitmap will be valid for any DC you get unless/until the graphics mode changes.
Adrian McCarthy
i understand your answer, which is why it's accepted. But now for extra follups.. If i call **GetDC**, it should keep that DC valid until i call **ReleaseDC**. What happens if there is a graphics mode switch after i call **GetDC**, but before **ReleaseDC**? Or does nothing bad happen, and it is perfectly valid to hold onto a DC, without releasing it, for the lifetime of the application?
Ian Boyd
It's generally bad form to hold onto a DC for the lifetime of the application though you probably can get away with it on more recent versions of Windows. With older versions, "common" DCs were a limited resource. If you kept one, you could force the OS to run out. If you really need to keep one for the lifetime of a window, then use the `CS_OWNDC`. See http://blogs.msdn.com/oldnewthing/archive/2006/06/01/612970.aspx
Adrian McCarthy
+1  A: 

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_OWNDC class style.

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

Edit

From the linked MSDN article:

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.

Perhaps this example will illustrate the use of CS_OWNDC better:

#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 not to be confused with the CS_CLASSDC flag which:

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 reconstruct the 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.

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

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)(should work the same over differn OS versions... although it wouldn't hurt to test).

i don't really understand the meaning of that flag. The alternative is to have a single DC shared among all windows of that class. That means that every EDIT box, on every window, in every process, or every logged on user on the system, shares the same DC? i don't think so.
Ian Boyd
@Ian Boyd - Your understanding is incorrect. The flag only applies to the window class *you* create and register with the RegisterClassEx function. So, if you make a window class called `BitmapWindow` and register it with that flag, then all windows created with the `BitmapWindow` class will share one DC. After registering your class and creating your window you call GetDC *once* and store the DC. Your example of Edit boxes is flawed as those have window class 'Edit' not 'BitmapWindow.' Each logged on user would get a unique DC.
Upon closer reading, your suggestion looks like it is designed to help with what i want. Windows maintains a pool of device contexts, and you have to retrieve the dc before painting, and release it after painting. It also appears that CS_CLASSDC is the inverse of CS_OWNDC - where all windows of a specific class in a process share the same dc, and only one thread at a time is allowed access to that DC.
Ian Boyd
@Ian Boyd - Looks like your comment was made during my edit. It seems you have a better understanding now. Hope you find a suitable solution.
That line to quoted, about DrawCachedBitmap failing, looks like it could possibly be Microsoft's intended way to deal with canvas changes. So i wish i could +1 for that. And your sample program where you tested DC changes deserved another +1. But Adrian McCarthy still gets the answer credit, because his is the answer to my question - while you answered my problem. Should be able to mark two answers.
Ian Boyd