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

NoCforMe

While we're all mourning Steve's demise, I created this little scheme to deal with the age-old problem of creating "objects" (brushes, pens, etc.) and then having to chase after them and delete them when the program ends. (Or, I'll admit, sometimes just not caring about them; word has it that Windows automagically deletes them anyhow for you, so why bother?)

So here it is, a very simple "objects" manager. Instead of the Win32 functions to create stuff (CreatePen(), CreateSolidBrusH(), etc.) you use the generic function CreateObject(). It creates the thing, stores its handle in a list, then returns with the handle. It knows how to create brushes, pens, fonts and bitmaps. (Other things could be added.)

At program end you just call DestroyObjects(), which goes through the list and destroys all objects that have non-NULL handles. Easy peasy.

Of course you can modify this for your own purposes. I made the handle list 64 items long, which could be easily changed. Minimal error checking (just to see if the handle list is full). If you need to delete just one object before program end, that's on you to keep track of its handle. I didn't want to add that level of complexity (being able to address a particular handle in the list). But you could do it if you wanted to.


;====================================
; Objects.asm
;
; Object-creating and -destroying code
;
; Keeps track of objects (brushes, pens, fonts and bitmaps)
; for you and deletes them at program end (using DestroyObjects() ).
;
; Use CreateObject (typ, parm1, parm2, parm3) to create stuff.
; typ = type of object (from list below)
; parm1-parm3 depend on object type:
; o Brush: parm1 = color
; o Pen: parm1 = style
; parm2 = width
; parm3 = color
; o Font: parm1 = pointer to a LOGFONT structure
; o Bitmap: parm1 = pointer to a BITMAP structure
;====================================

;***** Type codes:  *****
$objBrush EQU 1
$objPen EQU 2
$objFont EQU 3
$objBitmap EQU 4

$maxObjects EQU 64 ;Change if you need more objects

CreateObject PROTO typ:DWORD, parm1:DWORD, parm2:DWORD, parm3:DWORD
DestroyObjects PROTO

.data

ObjStore HANDLE $maxObjects DUP(0) ;All handles initially = NULL.
ObjStoreNextPtr DD ObjStore
NumObjects DD 0

.code
;====================================
; CreateObject (typ, parm1, parm2, parm3)
;
; Returns:
; handle to object created, or
; NULL if object couldn't be created, or
; -1 if max. # objects already created.
;====================================

CreateObject PROC typ:DWORD, parm1:DWORD, parm2:DWORD, parm3:DWORD

; See if we've already created the max. # of objects:
CMP NumObjects, $maxObjects
JNE @F
MOV EAX, -1
RET

@@: MOV EAX, typ
CMP EAX, $objFont
JE do_font
CMP EAX, $objPen
JE do_pen
CMP EAX, $objBitmap
JE do_bitmap

; Must be a brush:
INVOKE CreateSolidBrush, parm1 ;Color

store_handle:
TEST EAX, EAX ;If handle wasn't created because of error,
JZ @F ;  don't store it.
MOV EDX, ObjStoreNextPtr
MOV [EDX], EAX
ADD ObjStoreNextPtr, SIZEOF HANDLE
INC NumObjects
@@: RET

do_font:
INVOKE CreateFontIndirect, parm1 ;Ptr. to LOGFONT struct.
JMP store_handle

do_pen:
INVOKE CreatePen, parm1, parm2, parm3 ;Style, width, color
JMP store_handle

do_bitmap:
INVOKE CreateBitmapIndirect, parm1 ;Ptr. to BITMAP struct.
JMP store_handle

CreateObject ENDP


;====================================
; DestroyObjects()
;
; Destroys any objects that have been created.
;
; Returns nothing.
;====================================

DestroyObjects PROC

PUSH EBX
MOV EBX, OFFSET ObjStore
MOV ECX, $maxObjects
dloop: PUSH ECX
MOV EAX, [EBX]
TEST EAX, EAX ;If handle is 0,
JZ next ;  skip it.
INVOKE DeleteObject, EAX

next: ADD EBX, SIZEOF HANDLE
POP ECX
LOOP dloop

POP EBX
RET

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

jj2007

Well done, David :thumbsup:

I'm not quite sure if store_handle: should or should not store a zero handle - but that's not a criticism, just food for thought.

NoCforMe

Sure, but keep in mind that in this scheme all handles are NULL by default, so nothing to be gained by storing it in that case.
Assembly language programming should be fun. That's why I do it.

jj2007

Quote from: NoCforMe on June 14, 2023, 02:58:05 PM
Sure, but keep in mind that in this scheme all handles are NULL by default, so nothing to be gained by storing it in that case.

Yes, that's the point ;-)

zedd151

That seems like a nifty idea, David. I would however use the same parameter number for 'color' for instance. Also any structure ought be the same parameter number, etc. It would be easier imo to remember than trying to remember the parameters  for every possible object. There will always be some arguments that are NULL in any case, so instead of using parm1 always if the object needs only one parameter keep some semblance of uniformity maybe... just a thought. Gotta be a little better than parm1=color for one object but parm3=color for another...
So maybe 4 'parms', parm1=style, parm2=color, parm3=width, parm4=structure... (same parm - color for instance - always in same location) or whichever way seems reasonable. Would have to look how many other types of objects can be created and the arguments that might be needed for them. I am on my iPad presently I'll look at this again when I'm at my computer.
~~ later that day ~~
Okay, I'm inside here is what I am talking about...
; o any object:     parm1 = style
;                   parm2 = width
;                   parm3 = color
;                   parm4 = pointer to a ?? structure - structure associated with given object


Unused 'parms' of course will be null

jj2007

A variant (pure Masm32 SDK; 65 lines GUI example attached see reply #11 - check the Gdi handles in Task Manager for leaks):

include \masm32\include\masm32rt.inc
include CreateAndSelect.inc
; coThrowErrors=0 ; default is 1=throw an error if you get a zero handle (inserts extra code)

.data
MyBitmap dd 1, 2, 3
MyLogFont dd 1, 2, 3

.code
MyAlgo proc argDC, arg1, arg2
Local hPen, hBitmap, hFont
  CreateAndSelect(argDC) ; one arg: set the DC and allocate a local handle table
  mov hBrush, CreateAndSelect($objBrush, 0FFDD00h)
  void CreateAndSelect($objBrush, 0FFDD00h) ; no need for a variable if you just select it once
  mov hPen, CreateAndSelect($objPen, PS_SOLID, 3, 0FFDD00h)
  mov hBitmap, CreateAndSelect($objBitmap, addr MyBitmap)
  mov hFont, CreateAndSelect($objFont, addr MyLogFont)
  ; mov hFont, CreateAndSelect($objFont, addr MyLogFont, 123) ; assembly time error
  ReleaseObjects
  ret
MyAlgo endp
start:
  invoke MyAlgo, rv(GetWindowDC, 0), 12, 34
  inkey "done"
  exit

end start


Coding is fun :tongue:

jj2007

Here is the core of the GUI example, called from the WM_PAINT handler:

MyTest proc uses edi hDC
Local hBrush, hPen, rc:RECT, buffer[40]:BYTE
  CreateAndSelect(hDC, objct=4) ; one arg or explicit object count: set the DC
  mov hBrush, CreateAndSelect($objBrush, 0FFDD00h)
  void CreateAndSelect($objBrush, 0FFDD00h) ; no need for a variable if you just select it once
  mov hPen, CreateAndSelect($objPen, PS_SOLID, 3, 0FFDD00h)
  void CreateAndSelect($objFont, addr MyLogFont)
  invoke GetWindowRect, hWin, addr rc
  lea edi, buffer
  invoke crt_sprintf, edi, addr txRcRight, str$(rc.right) ; db "rc.right=%s", 0
  invoke Rectangle, hDC, 9, 9, 200, 64
  invoke TextOut, hDC, 20, 20, edi, len(edi)
  ReleaseObjects
  ret
MyTest endp


As mentioned above, it works also without the local variables hBrush and hPen - just use void CreateAndSelect(...).

NoCforMe

Ackshooly, speaking of the clunkiness of the parm1 ... parm3 scheme where you have to figure out what the parms mean, what would be far better would be making the function a vararg function, like wsprintf(), where it takes only the number of arguments needed. So for creating a brush, you'd only need to use


INVOKE CreateObject, $objBrush, brushColor


and not to have to push two useless parms on the stack.

However, I don't know how to code that. Maybe someone can help out here.
Assembly language programming should be fun. That's why I do it.

zedd151

Quote from: NoCforMe on June 15, 2023, 01:34:34 PM
So for creating a brush, you'd only need to use

   INVOKE   CreateObject, brushColor



I think that you would need at least two arguments. One to specify type of object. The other for parm value(s)
Quote
CreateObject PROC C TypeObj:REQ, Args:VarArg
The arg count could probably be inferred depending on the type of object  :icon_idea: 
Don't forget caller must balance the stack. I had never made a vararg procedure before btw. Would need a little experimentation for sure.




NoCforMe

Quote from: zedd151 on June 15, 2023, 02:20:43 PM
Don't forget caller must balance the stack.

Perhaps, but all I know is that I use wsprintf() all the time, with all kinds of different sets of parameters, and I never have to do anything (explicitly) to balance the stack; that's all taken care of for me. So once it's programmed, it's easy peasy to use.
Assembly language programming should be fun. That's why I do it.

zedd151

Quote from: NoCforMe on June 15, 2023, 02:37:24 PM
Quote from: zedd151 on June 15, 2023, 02:20:43 PM
Don't forget caller must balance the stack.
... I never have to do anything (explicitly) to balance the stack ...


Whoops you're right. Only need to fiddle with the stack if using "push, push, call"  nevermind. It's been a long day.  :tongue:

jj2007

Quote from: NoCforMe on June 15, 2023, 01:34:34 PM
Ackshooly, speaking of the clunkiness of the parm1 ... parm3 scheme where you have to figure out what the parms mean, what would be far better would be making the function a vararg function

Like this? Two brushes, a pen and a font (Times New Roman); check in Task Manager, columns user and Gdi objects, whether there's any leak.

MyTest proc uses edi hDC, pTxt ; pass the DC and one arbitrary argument (in this case, a string showing rc.right)
Local hBrush
  CreateAndSelect(hDC) ; one arg: set the DC
  mov hBrush, CreateAndSelect($objBrush, 0FF0000h)
  invoke Rectangle, hDC, 3, 3, 208, 70
  void CreateAndSelect($objBrush, 0FFh) ; no need for a variable if you just select it once
  void CreateAndSelect($objPen, PS_SOLID, 2, 0FFFFFFh) ; white pen
  void CreateAndSelect($objFont, addr MyLogFont)
  invoke Rectangle, hDC, 9, 9, 204, 64
  invoke TextOut, hDC, 20, 18, pTxt, len(pTxt)
  ReleaseObjects
  ret
MyTest endp


CreateAndSelect will throw an assembly time error if the number of parameters is not correct, and a runtime error if the handle is zero and casThrowErrors=1. Which is the default - it adds some code; once you are sure it works, set casThrowErrors=0 to produce the shortest possible code.

Macros and full test case attached (see WM_PAINT handler; 67 lines of code, no MasmBasic, just plain Masm32 SDK).

NoCforMe

Can you show us what that proc (CreateAndSelect) looks like so we can see how the vararg stuff works?
Assembly language programming should be fun. That's why I do it.

jj2007

CreateAndSelect is a macro, and it's attached above (CreateAndSelect.inc in the archive). I hope it's self-explanatory, but feel free to ask :thup:

NoCforMe

Sorry, JJ: instead of tl:dr, this one's tc:dr (too complicated, didn't read).

Remember, I don't like macros ...
Assembly language programming should be fun. That's why I do it.