News:

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

Main Menu

A way to create "objects" and automatically delete them at program end

Started by NoCforMe, June 14, 2023, 02:15:24 PM

Previous topic - Next topic

jj2007

Quote from: NoCforMe on June 16, 2023, 09:13:17 AM
Sorry, JJ: instead of tl:dr, this one's tc:dr (too complicated, didn't read).

Remember, I don't like macros ...

Sorry for that, David. Here is a heavily commented version, I hope it helps.

Minimal use case:
MyTest proc hDC
  CreateAndSelect(hDC) ; set the DC
  void CreateAndSelect($objBrush, 0FF0000h) ; create a blue brush and select it
  invoke Rectangle, hDC, 3, 3, 208, 70 ; use the brush
  ReleaseObjects ; unselect the brush, then delete it
  ret
MyTest endp


The macros:
casThrowErrors=0 ; default is 1 (adds extra code)
casMaxDef=4
CreateAndSelect MACRO casType, arg0, args:VARARG
Local is, tmp$
  ifb <arg0> ; ifblank arg0: User passed CreateAndSelect(hDC) directly under the locals
casDC equ <casType> ; we define the DC
casMax=casMaxDef ; casMaxDef=4: two pens, a brush, a font; if you need more, increase casMaxDef or use CreateAndSelect(hDC:123)
is INSTR <casType>, <:> ; e.g. CreateAndSelect(hDC:8), i.e. user needs 8 Gdi objects
if is
tmp$ SUBSTR <casType>, is+1
casMax=tmp$ ; casMax=8
casDC SUBSTR <casType>, 1, is-1 ; hDC
endif
casCt=0 ; object counter
LOCAL casTable[casMax], casOld[casMax] ; define local tables for Gdi handles and the previously selected objects
EXITM <> ; allow CreateAndSelect(...) instead of mov handle, CreateAndSelect(...)
  else
$objBrush=1 ; define object identifiers
$objPen=2
$objFont=3
$objBitmap=4
if casType eq $objBrush
tmp$ equ <brush>
invoke CreateSolidBrush, arg0
ifnb <args> ; args not blank: user passed more than one argument, error
tmp$ CATSTR <### too many arguments for >, tmp$, < in line >, %@Line, < ###>
% echo tmp$
.err
endif
elseif casType eq $objPen
tmp$ equ <pen>
invoke CreatePen, arg0, args
elseif casType eq $objFont
tmp$ equ <font>
invoke CreateFontIndirect, arg0
ifnb <args> ; args not blank: user passed more than one argument, error
tmp$ CATSTR <### too many arguments for >, tmp$, < in line >, %@Line, < ###>
% echo tmp$
.err
endif
elseif casType eq $objBitmap
tmp$ equ <bitmap>
invoke CreateBitmapIndirect, arg0
ifnb <args> ; args not blank: user passed more than one argument, error
tmp$ CATSTR <### too many arguments for >, tmp$, < in line >, %@Line, < ###>
% echo tmp$
.err
endif
else
tmp$ CATSTR <### unknown object type >, <casType>, < ###>
% echo tmp$
.err
EXITM <>
endif
if casThrowErrors ; default: casThrowErrors=1 (inserts additional code for runtime error)
.if !eax
tmp$ CATSTR <print "*** line >, %@Line, < (>, tmp$, <): ">
% tmp$
print LastError$()
xor eax, eax
.endif
endif
push eax
mov casTable[casCt*4], eax ; store Gdi handle
invoke SelectObject, casDC, eax ; use it
mov casOld[casCt*4], eax ; store the previous handle
pop eax
casCt=casCt+1 ; advance counter for the two tables
EXITM <eax>
  endif
ENDM
ReleaseObjects MACRO
Local tmp$
  if casCt gt casMax ; throw assembly time error, give hint how to solve it
tmp$ CATSTR <## use 'CreateAndSelect(>, casDC, <:>, %casCt, <)' at start of proc ##>
% echo tmp$
.err
  endif
  push esi
  push ebx
  lea esi, casOld
  m2m ebx, casCt
  .While 1
dec ebx
.Break .if Sign?
invoke SelectObject, casDC, [esi+4*ebx] ; select old handles
invoke DeleteObject, [esi+4*ebx+4*casMax] ; delete Gdi objects created above
  .Endw
  pop ebx
  pop esi
ENDM
void MACRO args ; allow the void CreateAndSelect($objBrush, 0FF0000h) syntax
  @CatStr(<;>, <args>)
ENDM

NoCforMe

First of all, I appreciate the effort that went into this, as goes into all that you do (even if you didn't do it just for me!).

My purpose for this thing is different from yours; your examples show you using it in your WM_PAINT handler, where you create things to select into the DC.

I work a little differently. Instead of creating brushes, pens, etc., "on the fly" when handling WM_PAINT, then destroying them each time, I create them once only, somewhere near the top of my code, make them global variables, use them in the paint routine over and over, then destroy them at program end. I'm not suggesting this is a better way than yours; it's just the way I like to roll. (There are times I have to create them in the paint handler, say if I need a brush of a certain color that the user has selected.)

I see why you not only create objects but automatically select them into your DC, then select the original object when you destroy them. Makes a lot of sense for the way you work.

This is at best a marginal time-saver, but I guess every little bit helps, right?

P.S.: This makes me wonder: is it really necessary to save the "original" object when you select something into a DC, then restore it before you exit your paint handler? I often don't bother to do this, and it doesn't seem to affect program operation at all. I think this may be one of those things that was needed in a long-ago version of Windows but is no longer necessary. Probably our friend Ray Chen has had a word or three to say about this. I actually don't know; does anyone know for sure whether this is really necessary? (Similar to how Windows automatically gets rid of most things you created, fonts, bitmaps, etc., at program end if you fail to do so, so that this kind of memory leak due to stranded handles isn't really a thing anymore.)
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: NoCforMe on June 17, 2023, 07:11:21 AM
First of all, I appreciate the effort that went into this, as goes into all that you do (even if you didn't do it just for me!).

I simply found it an interesting idea ;-)

QuoteI work a little differently. Instead of creating brushes, pens, etc., "on the fly" when handling WM_PAINT, then destroying them each time, I create them once only, somewhere near the top of my code, make them global variables, use them in the paint routine over and over, then destroy them at program end.

That's an absolutely valid way of coding, and indeed, I often do it exactly like that. Raymond Chen might tell you that destroying the objects at program end is not necessary, though. The create-select-restore old-destroy sequence is orthodox Windows doctrine. With permanent handles, select-restore old looks like the minimum required. The restore old part may not be required in modern Windows versions, but test that carefully with a loop to avoid ugly surprises. Then there is the issue of private DCs...

NoCforMe

Assembly language programming should be fun. That's why I do it.