News:

Masm32 SDK description, downloads and other helpful links
Message to All Guests
NB: Posting URL's See here: Posted URL Change

Main Menu

Do we really need to reset DC selections?

Started by NoCforMe, June 17, 2023, 12:22:05 PM

Previous topic - Next topic

NoCforMe

Since in another thread the issue of resetting DC selections came up, I'm bringing it up here in its own post. The question is whether we really need to reset selections into a device context (DC) by selecting the "old" object after setting a new one. The age-old rule is that yes, you must do this. Like this:


INVOKE SelectObject, hDC, NewPen
MOV oldPen, EAX

; ... do some other painting stuff ...

INVOKE SelectObject, hDC, oldPen
INVOKE EndPaint, hWin, ADDR ps


so that each selection of an object (pen, brush, bitmap, font) has a corresponding reset to the previous object.

I question the need to do this. I don't know for sure that it's not necessary, which is why I'm bringing it up here, but my sense is that it isn't. For one thing, I have written a lot of code that doesn't do this resetting, and I've never experienced any problems because of this (that I know of, anyhow).

Here's my reasoning why this shouldn't be necessary:

First, the device context you use in your program to do painting is, so far as I know, yours to use as you please. It's not shared with other processes or threads, and if it is, then these other processes or threads are capable of selecting the objects they need into it, so there should be no conflict. (My reasoning here is that when you handle a WM_PAINT message, for instance, you're not going to use the DC as-is: you're going to set it up the way you want it so the results look the way you want.)

Next, I don't see any intrinsic reason why you'd need to reset the selection in a DC. A device context is basically an internal Windows data structure that contains all the characteristics of a "surface" that can be drawn on or painted, either a physical screen or a printed page. The selections are simply the handles to objects (pens, brushes, bitmaps, etc.) that are to be used to draw and paint. When you call SelectObject() to set a new font, for instance, the handle for the font just gets plugged into this structure (the function gives you back the handle to the previously-current font as well so you can restore it). Nothing magic about this.

The only case I can think of which would require having to reset selections would be if Windows sets a reference count for selected objects, where the count gets incremented by SelectObject(newObject) and then decremented by SelectObject(previousObject). In this case, there would be a problem if you leave the DC with a non-zero reference count But I can't think why it would do that, not to mention that it would be horrendously complicated to implement this scheme.

Another possibility: resetting selections actually was necessary in ancient versions of Windows (3.1?), but no longer is, so doing this is just an unnecessary hangover from the past.

It'd be nice if someone who actually knows how all this works could respond here, if any such person exists here. If not, then we'll just have to sift through the inevitable speculation to decide what's what here.
Assembly language programming should be fun. That's why I do it.

jj2007

Very good question indeed :thumbsup:

One aspect to consider is whether an object remains selected in the DC if you do it once. Attached is a test case that demonstrates that no, it doesn't remain selected - in spite of the fact that the window was created with CS_OWNDC.

This doesn't answer the question, though.

Greenhorn

#2
The documentation of the SelectObject function sais it all:

QuoteThis function returns the previously selected object of the specified type. An application should always replace a new object with the original, default object after it has finished drawing with the new object.

An application cannot select a single bitmap into more than one DC at a time.

So, you do good at it if you follow this rule, it's just one line of code. Depending on what you exactly do it can do no harm but it's not guaranteed.
On a memory DC or a private DC it may cause no harm but it always depends on what you are doing with the GDI objects.

For example, DeleteObject will fail if the object is still selected into the device context and this can cause GDI leaks.

If you retrieve a device context by calling BeginPaint, GetDC(Ex) it's not guaranteed that you will always get the same DC.
After releasing a common DC all modifications - i.e. selected objects apart from the default - are lost.

However, the attached application shows what happens if you don't delete GDI objects properly. Not exactly related to your question but just an example for sloppy coding style. Grab the window border and size the window 40 to 60 times ...
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

C3

Well, as Microsoft Windows API documentation for SelectObjet says:
https://learn.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-selectobject

Greenhorn got this first: "An application should always replace a new object with the original, default object after it has finished drawing with the new object."  :eusa_clap:

All old books and tutorials suggest that also.

HSE

Quote from: jj2007 on June 17, 2023, 02:14:56 PM
One aspect to consider is whether an object remains selected in the DC if you do it once. Attached is a test case that demonstrates that no, it doesn't remain selected

Correct. But you can create a DC in wich all objects remain selected, in this example DIBSection:     case WM_CREATE
          mov hdc, rv(GetDC ,hWnd)
          mov bufDIBDC , rv(CreateCompatibleDC, hdc)
          mov hMainDIB , rv(CreateDIBSection, hdc, addr bi, DIB_RGB_COLORS, addr pMainDIB, NULL, 0);
          fn SelectObject, bufDIBDC, hMainDIB
          fn ReleaseDC, hWnd, hdc     ;   // Libera device context


Then you retrieve a new DC:
   case WM_PAINT
          mov hdc , rv(BeginPaint,hWnd, addr ps);
          fn BitBlt,hdc, 0, 0, cdXSize, cdYSize, bufDIBDC, 0, 0, SRCCOPY
          fn EndPaint,hWnd, ADDR ps


Equations in Assembly: SmplMath

BugCatcher

Would selecting multiple objects create a memory leak? if it doesn't, then who cares, just replace the object with the old object at end of program execution.

HSE

Quote from: BugCatcher on June 18, 2023, 12:21:43 AM
Would selecting multiple objects create a memory leak? if it doesn't, then who cares, just replace the object with the old object at end of program execution.

You can do how you want (if you store the handle in a global variable, that must be an array with one item for each creation). Not so crazy, the "protocol" is to destroy a object inside the procedure of creation, in part, because handle is stored in a local variable.

IIRC, you can replace with old object (then system destroy it) and destroy new object, or just destroy old object. Anyway you have to store the handle.
Again, the "protocol" is to replace the object with old object.

Years have passed that I don't see leaks effects. Not because improved programming skill but because new systems have a lot of memory  :biggrin: :biggrin:
Equations in Assembly: SmplMath

fearless

You can also use SaveDC and RestoreDC. Create DC, save it, do stuff with it and objects, restore DC to saved state, then delete DC. Good practice to delete any graphical objects prior to deleting the DC. I have an example if usage here: https://github.com/mrfearless/ModernUI/blob/master/ModernUI/_ModernUI_GDIDoubleBuffer.asm

jj2007

Quote from: HSE on June 18, 2023, 12:18:53 AM
Quote from: jj2007 on June 17, 2023, 02:14:56 PM
One aspect to consider is whether an object remains selected in the DC if you do it once. Attached is a test case that demonstrates that no, it doesn't remain selected

Correct. But you can create a DC in wich all objects remain selected, in this example DIBSection

Indeed, CreateCompatibleDC produces a DC with stable selections - in contrast to BeginPaint.

Quote from: HSE on June 18, 2023, 12:52:13 AMYears have passed that I don't see leaks effects. Not because improved programming skill but because new systems have a lot of memory  :biggrin: :biggrin:

The problem is when your program has many WM_PAINT etc messages, e.g. in gaming, you are silently running into a big problem...

NoCforMe

Quote from: Greenhorn on June 17, 2023, 07:41:21 PM
However, the attached application shows what happens if you don't delete GDI objects properly. Not exactly related to your question but just an example for sloppy coding style. Grab the window border and size the window 40 to 60 times ...

First of all, I tried it, resized it a whole bunch of times, nothing happened, I got tired of fooling around with it.

Can you tell us exactly what you did in that app? (No source attached.) You didn't delete some GDI "objects"? meaning you created them (I'm assuming in your WM_PAINT handler), then didn't delete them? If so, then yes, totally unrelated to what I was discussing, and just a garden-variety memory leak caused by handle depletion, I'm guessing.
Assembly language programming should be fun. That's why I do it.

NoCforMe

Quote from: fearless on June 18, 2023, 01:08:02 AM
Good practice to delete any graphical objects prior to deleting the DC.

Why?

What does the DC care whether the object that you selected into it still exists or not? It's not as if it keeps phoning up the object to see if it's still OK or something. And once the DC is gone, there's no longer any possible connection to the object. I think this is another of those programming myths everyone just accepts without thinking things through.

As I wrote above, I usually create the "objects" I'm going to use to paint globally, once at the top of my program, select them in the paint handler, then delete them at program end. Nothing wrong with this approach that I've ever seen.
Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: NoCforMe on June 18, 2023, 04:23:53 AM
Quote from: Greenhorn on June 17, 2023, 07:41:21 PM
However, the attached application shows what happens if you don't delete GDI objects properly. Not exactly related to your question but just an example for sloppy coding style. Grab the window border and size the window 40 to 60 times ...

First of all, I tried it, resized it a whole bunch of times, nothing happened, I got tired of fooling around with it.
As I wrote, size the window for a while with the mouse (left-right, left-right, ...), it will freeze.

Quote
Can you tell us exactly what you did in that app? (No source attached.) You didn't delete some GDI "objects"? meaning you created them (I'm assuming in your WM_PAINT handler), then didn't delete them? If so, then yes, totally unrelated to what I was discussing, and just a garden-variety memory leak caused by handle depletion, I'm guessing.
Exactly, I've put the DeleteObject in a condition which not always seemed to be met.
Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Quote from: Greenhorn on June 18, 2023, 04:57:54 AM
Quote from: NoCforMe on June 18, 2023, 04:23:53 AM
Can you tell us exactly what you did in that app? (No source attached.) You didn't delete some GDI "objects"? meaning you created them (I'm assuming in your WM_PAINT handler), then didn't delete them? If so, then yes, totally unrelated to what I was discussing, and just a garden-variety memory leak caused by handle depletion, I'm guessing.
Exactly, I've put the DeleteObject in a condition which not always seemed to be met.

How exactly does that work (or not work)? Still puzzled as to exactly what you're doing here. Are you passing DeleteObject() an invalid handle?
Assembly language programming should be fun. That's why I do it.

Greenhorn

Quote from: NoCforMe on June 18, 2023, 05:00:16 AM
Quote from: Greenhorn on June 18, 2023, 04:57:54 AM
Quote from: NoCforMe on June 18, 2023, 04:23:53 AM
Can you tell us exactly what you did in that app? (No source attached.) You didn't delete some GDI "objects"? meaning you created them (I'm assuming in your WM_PAINT handler), then didn't delete them? If so, then yes, totally unrelated to what I was discussing, and just a garden-variety memory leak caused by handle depletion, I'm guessing.
Exactly, I've put the DeleteObject in a condition which not always seemed to be met.

How exactly does that work (or not work)? Still puzzled as to exactly what you're doing here. Are you passing DeleteObject() an invalid handle?

No, the DeleteObject wasn't executed because the condition where it was put in sometimes wasn't performed.
So in the WM_PAINT the object was created but not always deleted.

If you open the task manager and add the column "GDI objects" to the view you can see how the GDI objects for that process increases until the window freezes because of the GDI leak.

Kole Feut un Nordenwind gift en krusen Büdel un en lütten Pint.

NoCforMe

Quote from: Greenhorn on June 18, 2023, 05:12:19 AM
If you open the task manager and add the column "GDI objects" to the view you can see how the GDI objects for that process increases until the window freezes because of the GDI leak.

Hey, all these years and I had no idea you could do that (w/Task Manager). Thanks!
Assembly language programming should be fun. That's why I do it.