Image list memory leak Topic is solved

Get help with using AutoHotkey (v2 or newer) and its commands and hotkeys
User avatar
RaptorX
Posts: 395
Joined: 06 Dec 2014, 14:27
Contact:

Image list memory leak

15 Jan 2024, 10:02

Hello using v2.0.10 here!

I am just curious about how image lists work.
Does calling IL_Destroy release the icon resources that are held by the list?

I am having a bit of a memory leak. I use a function to create an hIcon pointer to pass to the IL_Add function, that looks like this:

Code: Select all

icon_index := IL_Add(Main.Icon.list, 'HICON:' hIcon:=HandleFromBase64(b64icon))
Thing is, due to what I'm doing, this is growing my scripts memory usage by ~30MB on each function run (is a saving function). I tried doing this prior to the call:

Code: Select all

IL_Destroy(Main.Icon.list)
Main.Icon.list := IL_Create(10,10)
But that doesnt seem to help with my problem.

I also tried keeping track of the handles in an object and then doing this:

Code: Select all

for handle in Main.Icon.handles
	OutputDebug handle ' ' DllCall('DestroyIcon', 'ptr', handle) ' ' A_LastError '`n'
But I get the follwing:

Code: Select all

557122425 0 1402
557187961 0 1402
557253497 0 1402
557319033 0 1402
557384569 0 1402
557450105 0 1402
557515641 0 1402
557581177 0 1402
557646713 0 1402
557712249 0 1402
557777785 0 1402
557843321 0 1402
557908857 0 1402
I do get handles, but the function fails with error code 1402 which indicates invalid cursor handle even though the handles are working just fine in all other senses (they are loaded in my listview/treeview).

How can i release all those handles?
Projects:
AHK-ToolKit
geek
Posts: 1055
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Image list memory leak

15 Jan 2024, 10:29

Can you please share the content of HandleFromBase64
User avatar
RaptorX
Posts: 395
Joined: 06 Dec 2014, 14:27
Contact:

Re: Image list memory leak

15 Jan 2024, 13:08

geek wrote:
15 Jan 2024, 10:29
Can you please share the content of HandleFromBase64

Code: Select all

/** v0.1.0 | By RaptorX | [AHK Forum Post]()
 * Get a Handle for an icon or bitmap from a base64 string
 * ---
 *
 * ### Params:
 * - `Base64`            - Base64 encoded string
 * - `IsIcon` [Optional] - Determines wether to return an HICON OR HBITMAP
 *
 * ### Returns:
 * - returns
 * 	- `HICON`
 * 	- `HBITMAP`
 *
 * ### Examples:
 * Desc
 * ```
hIcon:=HandleFromBase64(b64ico)

main := Gui()
main.AddPicture('', 'HICON:' hIcon)
main.Show()
 * ```
 */
HandleFromBase64(Base64, IsIcon := true) {
	static CRYPT_STRING_BASE64 := 0x00000001

	GdiPlusStartupInput := Buffer(A_PtrSize = 8 ? 24 : 16, 0)
	NumPut("uint", 1, GdiPlusStartupInput, 0) ; GdiPlusVersion


	; add parameter type checking
	if !DllCall("Crypt32\CryptStringToBinaryW",
	             "Str"  , Base64,
	             "UInt" , 0,
	             "UInt" , CRYPT_STRING_BASE64,
	             "Ptr"  , 0,
	             "Ptr*", &Size := 0,
	             "Ptr"  , 0,
	             "Ptr"  , 0)
		throw OSError()

	Decoded := Buffer(Size)
	if !DllCall("Crypt32\CryptStringToBinaryW",
	             "Str"  , Base64,
	             "UInt" , 0,
	             "UInt" , CRYPT_STRING_BASE64,
	             "Ptr"  , Decoded,
	             "Ptr*", &Size,
	             "Ptr"  , 0,
	             "Ptr"  , 0)
		throw OSError()

	if res := DllCall("GDIPlus\GdiplusStartup",
	                  "ptr*", &pToken := 0,
	                  "ptr", GdiPlusStartupInput,
	                  "ptr", 0)
		throw Error('Could not start the GDI library', A_ThisFunc, res)

	if !pStream := DllCall("shlwapi\SHCreateMemStream",
	                       "ptr", Decoded,
	                       "uint", Decoded.size, "ptr")
		throw Error('Could not create the memory stream', A_ThisFunc)

	DllCall "GDIPlus\GdipCreateBitmapFromStreamICM",
	        "ptr" , pStream,
	        "ptr*", &pBitmap := 0
	DllCall "GDIPlus\GdipCreateHICONFromBitmap",
	        "ptr" , pBitmap,
	        "ptr*", &hIcon := 0
	DllCall "GDIPlus\GdipCreateHBITMAPFromBitmap",
	        "ptr" , pBitmap,
	        "ptr*", &hBitmap := 0,
	        "int" , 0xffffffff

	ObjRelease(pStream)
	; DllCall('DestroyIcon', 'ptr', hIcon)
	
	; DllCall "GDIPlus\GdiplusShutdown", "ptr", pToken
	return (IsIcon ? hIcon : hBitmap)
}
This is what i have for now.

As you might have guessed destroying the icon inside the function would cause issues outside of it.
Projects:
AHK-ToolKit
geek
Posts: 1055
Joined: 02 Oct 2013, 22:13
Location: GeekDude
Contact:

Re: Image list memory leak  Topic is solved

15 Jan 2024, 13:56

According to the AutoHotkey documentation here https://www.autohotkey.com/docs/v2/misc/ImageHandles.htm , handles provided to an ImageList by HICON: or HBITMAP: are automatically freed by AutoHotkey when appropriate. Your HandleFromBase64 function is creating a bitmap pointer, which is not freed appropriately, and a bitmap handle which is neither used nor freed appropriately. It also does not cache the GDI+ instance by static variable or call GdiplusShutdown when it is done using GDI+, so there is additional memory leaked there. I would look into freeing those, rather than your hIcon values, to patch up the memory leak.

Additionally, there is a setting to put * like "HICON:*" hIcon where the contents of the handle will be copied and the original handle (I think) will not be freed automatically. This would allow you to manually free anything you manually loaded right away, rather than waiting until AHK is ready to release the resource. This could prove helpful to writing code that cleans up after itself effectively, especially if you can defer release by some kind of object destructor, e.g. by making your HandleFromBase64 function return that object rather than a raw handle:

return {Ptr: hIcon, __Delete: (this) => /* Free the hIcon */ } ... IL_Add(Main.Icon.list, 'HICON:*' HandleFromBase64(b64icon).Ptr)

That way the free event will be delayed until after the expression where the icon is used is evaluated.
User avatar
RaptorX
Posts: 395
Joined: 06 Dec 2014, 14:27
Contact:

Re: Image list memory leak

16 Jan 2024, 10:41

geek wrote:
15 Jan 2024, 13:56
According to the AutoHotkey documentation here https://www.autohotkey.com/docs/v2/misc/ImageHandles.htm , handles provided to an ImageList by HICON: or HBITMAP: are automatically freed by AutoHotkey when appropriate. Your HandleFromBase64 function is creating a bitmap pointer, which is not freed appropriately, and a bitmap handle which is neither used nor freed appropriately. It also does not cache the GDI+ instance by static variable or call GdiplusShutdown when it is done using GDI+, so there is additional memory leaked there. I would look into freeing those, rather than your hIcon values, to patch up the memory leak.

Additionally, there is a setting to put * like "HICON:*" hIcon where the contents of the handle will be copied and the original handle (I think) will not be freed automatically. This would allow you to manually free anything you manually loaded right away, rather than waiting until AHK is ready to release the resource. This could prove helpful to writing code that cleans up after itself effectively, especially if you can defer release by some kind of object destructor, e.g. by making your HandleFromBase64 function return that object rather than a raw handle:

return {Ptr: hIcon, __Delete: (this) => /* Free the hIcon */ } ... IL_Add(Main.Icon.list, 'HICON:*' HandleFromBase64(b64icon).Ptr)

That way the free event will be delayed until after the expression where the icon is used is evaluated.
I dont have much experience with GDI but after following your suggestions I was able to fix the memory leak as far as I can tell.
Projects:
AHK-ToolKit

Return to “Ask for Help (v2)”

Who is online

Users browsing this forum: Xeilous and 67 guests