I tried to make this cross-section as small as possible
Method #1 below simply creates / destroys 1 DC and 1 brush using DllCall().
Method #2 uses some of my class wrapper bits to catalog created objects in gdipp.ObjList (an array) for easy cleanup.
Even though the objects (ptrs) are freed with appropriate DllCall(), and the ObjList is decremented with the .Destroy() method, the mem usage seems larger than I would expect.
I'm just not sure if this is expected mem usage, or likely a memory leak?
I've done lots of checking to ensure that i am able to catch a failed delete/release of a pointer, but no such errors are thrown.
Any ideas?
Code: Select all
; AHK v2
msgbox "starting loop"
Loop 4000 {
; ==================================================================
; Method #1
; no wrapping - uses much less memory, but still increases mem size
; - from 1,912K to 2,112K
; Only creates and destroys 1 DC and 1 brush per iteration.
; Still some extra memory usage, but not as bad as below.
; ==================================================================
; hDC := DllCall("CreateCompatibleDC", "UPtr", 0)
; brush := DllCall("CreateSolidBrush", "UInt", 0xFF0000)
; old := DllCall("SelectObject", "UPtr", hDC, "UPtr", brush)
; DllCall("DeleteDC", "UPtr", hDC)
; DllCall("DeleteDC", "UPtr", brush)
; DllCall("DeleteDC", "UPtr", old)
; ==================================================================
; Method #2
; wrapping - uses over 3x as much memory -> overhead? or leak?
; - from 1,936K to 6,432K
; This catalogs every object in gdipp.ObjList (an array).
; Stored objects are released with proper DllCall and the array size
; is reduced afterwards, but memory doesn't seem to be freed.
; ==================================================================
hDC2 := gdipp.CompatDC()
brush := hDC2.CreateBrush(0xFF0000)
hDC2.Destroy()
brush.Destroy()
}
msgbox "ObjList: " gdipp.ObjList.Length "`r`nCheck memory."
; ==================================================================
; gdipp class
; ==================================================================
class gdipp {
Static ObjList := [] ; trying to do automatic tracking of objects for easy cleanup
Static __New() {
this.LoadConstants()
}
Static BGR_RGB(_c) { ; this conversion works both ways, it ignores 0xFF000000
return (_c & 0xFF)<<16 | (_c & 0xFF00) | (_c & 0xFF0000)>>16 | (_c & 0xFF000000)
}
Static CompatDC(in_hDC:="") {
If (in_hDC="")
hDC := DllCall("CreateCompatibleDC", "UPtr", 0, "UPtr") ; mem DC
Else If IsInteger(in_hDC)
hDC := DllCall("CreateCompatibleDC", "UPtr", in_hDC, "UPtr") ; DC based on input DC ptr
Else If (IsObject(in_hDC) And in_hDC.cType = "DC")
hDC := DllCall("CreateCompatibleDC", "UPtr", in_hDC.ptr, "UPtr") ; DC based on input DC obj
Else
hDC := "" ; unsupported, throw an error
If !hDC
throw Exception("Compatible DC creation failed.`r`n`r`nA_LastError: " A_LastError)
return gdipp.DC([hDC,1])
}
Static ObjLookup(in_ptr) {
For i, obj in this.ObjList
If (in_ptr = obj.ptr)
return obj
return "" ; if no match
}
; ===================================================================
; Base Obj
; ===================================================================
class base_obj {
__New(p*) { ; p1 = _gdipp // p2 = ptr
this.ptr := p[2][1] ; obj pointer
this.temp := false ; is obj temp or no?
If (this.cType = "DC") { ; GetDC = 0 // CreateCompatibleDC = 1
(p[2][2]=0) ? (this.release := "ReleaseDC") : (p[2][2]=1) ? (this.release := "DeleteDC") : ""
this.StretchBltMode := 3 ; 3 = ColorOnColor / 4 = Halftone - 3 is a good default for speed
} Else If (this.cType != "DC")
this.CurDC := 0
gdipp.ObjList.Push(this)
}
__Delete() {
this.Destroy() ; both ways seem to not save as much memory as hoped
; this.Clean_Up()
}
Clean_Up(destroy:=true) {
For i, obj in gdipp.ObjList {
If (obj.ptr = this.ptr) {
(destroy) ? obj.Destroy() : ""
gdipp.ObjList[i] := ""
gdipp.ObjList.RemoveAt(i)
Break
}
}
}
Destroy(r1 := "") {
If !this.ptr
return
If (this.cType = "DC") {
r1 := DllCall(this.release, "UPtr", this.ptr) ; ReleaseDC / DeleteDC
} Else
r1 := DllCall("DeleteObject", "UPtr", this.ptr)
this.Clean_Up(false) ; remove obj list entry, but don't .Destroy() (circular reference)
If r1<0 ; doesn't fire... all deletions success?
msgbox "weird r1: " r1 " / type: " this.cType
If (!r1)
throw Exception("Error on obj release, or object not yet supported."
,,"Obj Type: " this.cType "`r`nObj Ptr: " this.ptr)
}
cType[] {
get => StrReplace(this.__Class,"gdipp.","")
}
}
; ===================================================================
; Brush - mostly LOGBRUSH
; ===================================================================
; Brush & Hatch Styles: https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logbrush
; - Brush: DibPattern, DibPattern8x8, DibPatternPT, Hatched, Hollow, Pattern, Pattern8x8, Solid (default)
; - Hatch: BDiagonal, Cross, DiagCross, FDiagonal, Horizontal (default), Vertical
; NOTE: For corresponding integer values, see constants below in .LoadConstants() method.
class Brush extends gdipp.base_obj {
}
; ===================================================================
; Device Context obj
; ===================================================================
class DC extends gdipp.base_obj { ; maybe have param to specify BltStretchMode()
delayMethod := "" ; for delayed drawing
delayParams := ""
CreateBrush(_ColorRef:=0x000000, _type:="solid", _hatch:="Horizontal") {
LOGBRUSH := BufferAlloc((A_PtrSize=8)?16:12,0)
BR_TYPE := IsInteger(_type) ? _type : gdipp.BrushTypes[_type]
COLORREF := gdipp.BGR_RGB(_ColorRef) ; reverse input RGB to BGR, leave alpha
HS_TYPE := IsInteger(_hatch) ? _hatch : gdipp.HatchTypes[_hatch]
NumPut("UInt", BR_TYPE, "UInt", COLORREF, "UPtr", HS_TYPE, LOGBRUSH) ; HS_TYPE can also be a ptr to a packed DIB
pLogbrush := DllCall("gdi32\CreateBrushIndirect", "UPtr", LOGBRUSH.ptr)
return gdipp.Brush([pLogbrush,0])
}
; SelectObject(p*) {
; If (p.Length = 1)
; DC := this, CurObj := p[1]
; Else If (p.Length = 2)
; DC := p[1], CurObj := p[2]
; Else If (!p.Length Or p.Length > 2)
; throw Exception("Invalid number of parameters.")
; If IsInteger(CurObj) { ; stock object
; old_ptr := DllCall("gdi32\SelectObject", "UPtr", DC.ptr, "UPtr", in_ptr := CurObj)
; } Else { ; wrapped object
; CurObj.CurDC := DC ; set obj.CurDC in the obj
; old_ptr := DllCall("gdi32\SelectObject", "UPtr", DC.ptr, "UPtr", in_ptr := CurObj.ptr)
; }
; return old_ptr
; }
; ====================================================================
; DC Properties
; ====================================================================
StretchBltMode[] { ; 4 = Halftone / 3 = ColorOnColor
set => DllCall("gdi32\SetStretchBltMode", "UPtr", this.ptr, "Int", value)
get => DllCall("gdi32\GetStretchBltMode", "UPtr", this.ptr)
}
}
; =============================================================================================
; method for loading constants - so we can turn CaseSense off in Map()
; =============================================================================================
Static LoadConstants() {
; Brush Types (0-3, 5-8) ; https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logbrush
bt := Map(), bt.CaseSense := false
bt["Solid"] := 0
, bt["Hollow"] := 1
, bt["Hatched"] := 2
, bt["Pattern"] := 3
, bt["DibPattern"] := 5
, bt["DibPatternPT"] := 6
, bt["Pattern8x8"] := 7
, bt["DibPattern8x8"] := 8
this.BrushTypes := bt
; Hatch Styles (0-5) ; https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-logbrush
ht := Map(), ht.CaseSense := false
ht["Horizontal"] := 0
, ht["Vertical"] := 1
, ht["FDiagonal"] := 2
, ht["BDiagonal"] := 3
, ht["Cross"] := 4
, ht["DiagCross"] := 5
this.HatchTypes := ht
; Stock Object types (1-14) ; https://docs.microsoft.com/en-us/windows/win32/api/wingdi/nf-wingdi-getstockobject
so := Map(), so.CaseSense := false
so["WhiteBrush"] := 0
, so["LtGrayBrush"] := 1
, so["GrayBrush"] := 2
, so["DkGrayBrush"] := 3
, so["BlackBrush"] := 4
, so["HollowBrush"] := 5 ; a.k.a. NULL_BRUSH
, so["WhitePen"] := 6
, so["BlackPen"] := 7
, so["NullPen"] := 8
, so["_Unknown_9"] := 9 ; always returns 0 (so far)
, so["OemFixedFont"] := 10
, so["AnsiFixedFont"] := 11
, so["AnsiVarFont"] := 12
, so["SystemFont"] := 13
, so["DefaultDeviceFont"] := 14
, so["DefaultPallette"] := 15
, so["SystemFixedFont"] := 16
, so["DefaultGuiFont"] := 17
, so["DcBrush"] := 18
, so["DcPen"] := 19
, so["DcColorSpace"] := 20 ; not documented
, so["DcBitmap"] := 21 ; not documented
this.StockObjectTypes := so
sop := Map(), sop.CaseSense := false ; StockObjectPtrs
For name, val in so
If (name != "_Unknown_9")
sop[name] := DllCall("gdi32\GetStockObject", "Int", val)
this.StockObjectPtrs := sop
}
Static GetFlag(iInput,member) { ; reverse lookup for Map() constants
For prop, value in gdipp.%member%
If (iInput = value)
return prop
return "" ; if no match
; throw Exception("GetFlag() method: Value not listed.`r`n`r`nValue: " iInput)
}
}