Jump to content

AutoItX, WinList function memory leak


Recommended Posts

All my tests done on WinXP SP3, AutoItX v3.3.0.0

My AutoItX is not up-to-date, however the changelog does not record any change regarding this problem.

The issue is known :

* each call to o_AutoItX3Obj.WinList can consumme up to 4ko (in my tests),

which are not released even when exiting a sub or setting the o_AutoItX3Obj object to Nothing

-> this is a problem, for example, for ever-runnning scripts that do repetitive call to WinList

* a work-around : enumerate windows instead of using WinList

VBS Code

Option Explicit

MAIN()

Sub MAIN()
    Dim s_win_id, arr
    Dim o_AutoItX3Obj

    Set o_AutoItX3Obj = CreateObject("AutoItX3.Control")

    ' s_win_id : the advanced filter parameter that would have been used in the WinList function
    ' s_win_id : must not contain the INSTANCE parameter (it seems it just would be ignored)
    ' WinTitleMatchMode : the title match mode that would have been used with the WinList function
    ' WinSearchChildren : the child windows mode that would have been used with the WinList function

    ' *** EXAMPLE 1 ***

    ' s_win_id = "TITLE:a", with WinTitleMatchMode substring (=2), No WinSearchChildren (=0)
    ' there should be some of theses
    s_win_id  = "TITLE:a"
    o_AutoItX3Obj.AutoItSetOption "WinTitleMatchMode", 2
    o_AutoItX3Obj.AutoItSetOption "WinSearchChildren", 0

    ' call the pseudo WinList function (ENUM_WIN)
    arr = ENUM_WIN(o_AutoItX3Obj, s_win_id)

    ' look for results
    SHOW_RESULTS o_AutoItX3Obj, s_win_id, arr

    ' *** EXAMPLE 2 ***

    ' s_win_id = "TITLE:a; REGEXPCLASS:a", with WinTitleMatchMode substring (=2), No WinSearchChildren (=0)
    ' there should be at least the 'Program Manager' [Class:Progman], which is the Windows desktop
    s_win_id  = "TITLE:a; REGEXPCLASS:a"
    o_AutoItX3Obj.AutoItSetOption "WinTitleMatchMode", 2
    o_AutoItX3Obj.AutoItSetOption "WinSearchChildren", 0

    ' call the pseudo WinList function (ENUM_WIN)
    arr = ENUM_WIN(o_AutoItX3Obj, s_win_id)

    ' look for results
    SHOW_RESULTS o_AutoItX3Obj, s_win_id, arr
End Sub

Sub SHOW_RESULTS(ByVal o_AutoItX3Obj, ByVal s_win_id, ByVal arr)
    Dim s_res, i

    s_res = "Number of windows matching [" & s_win_id & "] : " & arr(0, 0) & vbCr
    s_res = s_res & "WinTitleMatchMode : " & o_AutoItX3Obj.AutoItSetOption("WinTitleMatchMode", 0) & ", "
    s_res = s_res & "WinSearchChildren : " & o_AutoItX3Obj.AutoItSetOption("WinSearchChildren", 0) & vbCr
    s_res = s_res & "(message boxes can cut long text)" & vbCr & vbCr
    s_res = s_res & "Num Handle" & vbTab & "Title" & vbCr & vbCr

    For i = 1 To UBound(arr, 2)
        s_res = s_res & i & ". " & arr(1, i) & vbTab & "[" & arr(0, i) & "]" & vbCr
    Next

    MsgBox s_res, vbInformation, WScript.ScriptName
End Sub

' Re-usable code begins here (could be optimized if hundreds of matching windows are expected)
' ENUM_WIN returns an 2-Dim array, same formatting as by WinList function
Function ENUM_WIN(ByVal o_AutoItX3Obj, ByVal s_win_id)
    Dim s_win_handle, s_win_title, arr, i

    ReDim arr(1, 0)
    arr(0, 0) = 0 ' will hold the number of found windows, like the WinList function
    arr(1, 0) = 0 ' will not be used

    i = 1 ' because Instances are 1-based
    Do
        s_win_handle = o_AutoItX3Obj.WinGetHandle("[" & s_win_id & "; INSTANCE:" & i & "]")
        If s_win_handle = "" Then Exit Do ' enumeration finished
        s_win_title = o_AutoItX3Obj.WinGetTitle("[HANDLE:" & s_win_handle & "]")

        ReDim Preserve arr(1, i)
        arr(0, i) = s_win_title ' title can be an empty string
        arr(1, i) = s_win_handle ' handle should always be a valid hex string number (32/64 bits ?)

        i = i + 1
    Loop
    arr(0, 0) = i - 1 ' record the number of found windows

    ENUM_WIN = arr
End Function

* note 1 :

enumerating windows could have timing issues if some windows matching the criteria

are appearing/disappearing while doing the enumeration... (I didn't test that)

* note 2 :

if it impossible to avoid memory leak in COM components returning arrays,

then WinList could return a simple string with titles/handles separated by Chr(0),

(assuming there are no Chr(0) inside window titles ?)

then the user would use the Split function to get an 1-Dim array : [title1, handle1, title2, handle2, title3, handle3] etc...

This string-type return could be an optional parameter given to the WinList function :

arr = o_AutoItX3Obj.Winlist("title" [, "text" [, return type]])

Edited by marc0v
Link to post
Share on other sites

Create an account or sign in to comment

You need to be a member in order to leave a comment

Create an account

Sign up for a new account in our community. It's easy!

Register a new account

Sign in

Already have an account? Sign in here.

Sign In Now
  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • By XGamerGuide
      I'm trying to call WinList() with a handle. That should be possible:
      But I only get back an 2d array that looks like this:
      ---------
      0 |
      (That means no window was found.)
      It's not the window or the handle. When I call other functions like WinExists() or WinGetHandle() it works.
      Is it because of me, the description in the reference or because of Autoit who makes a mistake?
      No, because of the rest of my program I don't want to use WinGetTitle() because the format of WinList() has to be kept. Alternatively, I could create a 2d array myself, but it should also work with WinList() ... I hope.
    • By Arlen
      I think I have memory leak issue...
      Every time I use _IENavigate() my Memory Ram goes up and never down.. I could reach 1gb just using this function. Which is weird..
      #include <IE.au3> #include <GUIConstantsEx.au3> #RequireAdmin Global $oIE = _IECreateEmbedded() Global $Form1 = GUICreate("Form1", 920, 466, 502, 342) GUICtrlCreateObj($oIE, 8, 72, 897, 370) ;IE OBJ Local $Button1 = GUICtrlCreateButton("FETCH", 808, 8, 91, 57) GUISetState(@SW_SHOW) While 1 Local $nMsg = GUIGetMsg() Switch $nMsg Case $GUI_EVENT_CLOSE Exit Case $Button1 Fetch() EndSwitch WEnd Func Fetch() For $i = 0 To 20 _IENavigate($oIE, 'https://www.google.com/search?source=hp&ei=hzprXM_zN-zs_QbExry4CQ&q=sad&btnK=Google+Search&oq=&gs_l=psy-ab.3..35i39j0i67l9.2993.3926..4083...1.0..0.137.749.0j6......0....1..gws-wiz.....6.eVCDaXHQXYA') Next EndFunc  
    • By argumentum
      I was asking around on how to detect memory leaks and came up with a logger ( or just stop execution, or restart ).
      Here is the code. ( should have done a UDF but maybe there is no need for such )
      #AutoIt3Wrapper_Au3Check_Parameters=-q -d -w 1 -w 2 -w 3 -w 4 -w 6 -w 7 #include <SQLite.au3> #include <WinAPISys.au3> #include <WinAPIProc.au3> #include <Date.au3> #include <Array.au3>; For _ArrayDisplay() Example() Func Example() ConsoleWrite(MemLeakTooMuch() & @CRLF) ; call once at first to initialize the baseline. Local $a = MemLeakLog(1) ; ..you could call this on the fly and get an array _ArrayDisplay($a) ; call on AdlibRegister() or other means ; Ex: AdlibRegister("OnMemLeakRestart", 3600000) ; once an hour ; While Sleep(50) ; ... ... ; WEnd EndFunc ;==>Example #Region MemLeak Func OnMemLeakRestart() ; Local Static $iTriggered = 0 If MemLeakTooMuch() Then If Not $iTriggered Then $iTriggered = 1 AdlibRegister("OnMemLeakRestart", 60000) ; since it triggered, check more often EndIf If _WinAPI_GetIdleTime() > 600000 Then ; over 10 min. maybe ?, to not bother the user ? ShellExecute(@ScriptFullPath) ; restart itself if it must, but if you do, ; keep that in mind while coding. Exit 3 ; use the exit code you please other than 1 or 2, EndIf ; as AutoIt may use it for itself. EndIf EndFunc ;==>OnMemLeakRestart Func MemLeakLog($ReturnAnArray = 0) Local $aMethod[5] = ["EXITCLOSE_NORMAL", "EXITCLOSE_BYEXIT", "EXITCLOSE_BYCLICK", "EXITCLOSE_BYLOGOFF", "EXITCLOSE_BYSHUTDOWN"] Local $iMethod = Execute("@exitMethod") Local $aDesc[14] = ["number of page faults", "peak working set size, in bytes", "current working set size, in bytes", _ "peak paged pool usage, in bytes", "current paged pool usage, in bytes", "peak nonpaged pool usage, in bytes", _ "current nonpaged pool usage, in bytes", "current space allocated for the pagefile, in bytes", _ "peak space allocated for the pagefile, in bytes", "current amount of memory that cannot be shared with other processes, in bytes", _ "ProcessHandleCount", "ProcessThreadsCount", "GDI objects", "USER objects"] Local $s = @YEAR & "." & @MON & "." & @MDAY & "_" & @HOUR & ":" & @MIN & ":" & @SEC $s &= " - RunTime: " & _Convert(gTimer()) & " - PID: " & @AutoItPID If IsInt($iMethod) Then $s &= " - ExitCode: " & @exitCode $s &= " - ExitMethod: " & $iMethod & " (" & $aMethod[$iMethod] & ")" EndIf Local $a[4][15] $a[1][0] = "Starting:" $a[2][0] = " Exiting:" $a[3][0] = " Diff.:" MemLeakTooMuch() Local $b = MemLeakTooMuch(-2), $c = MemLeakTooMuch(-3) For $n = 0 To UBound($c) - 1 $a[0][$n + 1] = $aDesc[$n] $a[1][$n + 1] = $b[$n] $a[2][$n + 1] = $c[$n] $a[3][$n + 1] = $c[$n] - $b[$n] Switch $n Case 0, 10, 11, 12, 13, 14 ; nothing, is a count number Case Else $a[3][$n + 1] = ByteSuffix($a[3][$n + 1]) EndSwitch $a[3][$n + 1] &= " ( x" & (($b[$n] > 0 And $b[$n] > 0) ? Round($c[$n] / $b[$n], 1) : "1") & " )" Next If Int(Eval("ReturnAnArray")) Then $a[0][0] = _Convert(gTimer()) Return $a Else FileWriteLine(StringTrimRight(@ScriptFullPath, 4) & ".ProcessInfo.log", $s & @CRLF & _ _SQLite_Display2DResult($a, 0, True) & Eval("sErrorHandler") & @CRLF) ; I save the data from ObjEvent("AutoIt.Error", "_ErrFunc") to $sErrorHandler EndIf EndFunc ;==>MemLeakLog Func MemLeakTooMuch($iPid = 0, $iMutliplier = 4) ; in case this is for some other PID, but only one =/ Local Static $aInitProcessMemoryInfo = 99, $aLastProcessMemoryInfo Local $ret = False, $a, $n, $aTemp If $iPid = -2 Then Return $aInitProcessMemoryInfo ; returns the initial readings If $iPid = -3 Then Return $aLastProcessMemoryInfo ; returns the last readings If $aInitProcessMemoryInfo = 99 Then $aTemp = _WinAPI_GetProcessMemoryInfo($iPid) If Not (UBound($aTemp) = 10) Then Return SetError(1, 0, $ret) $aLastProcessMemoryInfo = $aTemp Else $aTemp = _WinAPI_GetProcessMemoryInfo($iPid) If Not (UBound($aTemp) = 10) Then Return SetError(1, 0, $ret) $aLastProcessMemoryInfo = $aTemp For $n = 1 To UBound($aTemp) - 1 ; I don't think "page faults" is leakage, so it's omited. If $aTemp[$n] > $aInitProcessMemoryInfo[$n] * $iMutliplier Then $ret = True Next EndIf ReDim $aLastProcessMemoryInfo[14] ; to hold the next values $aLastProcessMemoryInfo[10] = _WinAPI_GetProcessHandleCount($iPid) If $aLastProcessMemoryInfo[10] > 2000 Then $ret = True ; dropping handles ? $a = _WinAPI_EnumProcessThreads($iPid) $aLastProcessMemoryInfo[11] = UBound($a) - 1 If $aLastProcessMemoryInfo[11] > 200 Then $ret = True ; dropping threads ? If Not $iPid Then $iPid = -1 $aLastProcessMemoryInfo[12] = _WinAPI_GetGuiResources(0, $iPid) ; count of GDI objects. $aLastProcessMemoryInfo[13] = _WinAPI_GetGuiResources(1, $iPid) ; count of USER objects If $aLastProcessMemoryInfo[12] > 1000 Then $ret = True ; dropping GDI objects ? If $aLastProcessMemoryInfo[13] > 2000 Then $ret = True ; dropping USER objects ? If $aInitProcessMemoryInfo = 99 Then gTimer() ; init. the timer now, if was not at the top of the script $aInitProcessMemoryInfo = $aLastProcessMemoryInfo OnAutoItExitRegister("MemLeakLog") ; ..if you wanna keep a log EndIf Return $ret EndFunc ;==>MemLeakTooMuch Func _Convert($ms) ; https://www.autoitscript.com/forum/topic/163621-convert-ms-to-dayhourminsec/?do=findComment&comment=1192334 Local $day, $hour, $min, $sec _TicksToTime($ms, $hour, $min, $sec) If $hour > 24 Then $day = $hour / 24 $hour = Mod($hour, 24) EndIf Return StringReplace(StringFormat("%03i %02i:%02i:%02i", $day, $hour, $min, $sec), "000 ", "") EndFunc ;==>_Convert Func gTimer() Local Static $Timer = TimerInit() Return TimerDiff($Timer) EndFunc ;==>gTimer Func ByteSuffix($iBytes) ; https://www.autoitscript.com/autoit3/docs/functions/FileGetSize.htm Local $iIndex = 0, $aArray = [' bytes', ' KB', ' MB', ' GB', ' TB', ' PB', ' EB', ' ZB', ' YB'] While $iBytes > 1023 $iIndex += 1 $iBytes /= 1024 WEnd Return Round($iBytes) & $aArray[$iIndex] EndFunc ;==>ByteSuffix #EndRegion MemLeak I'm gonna use it for a pain in the neck script.
      If you find ways to improve it, or something I missed, let me know.
      2019.01.05 update: Added GDI objects and USER objects. Added return array. Fixed boo-boo. Added Diff. multiplier.
    • By argumentum
      How do I know there is a memory leak ?, is it "Private Bytes" or "Working Set" too ?
      I have some clues but no definitive knowing. I have no experience looking at these.
      Also, If I can use AutoIt to self-check within the code would be most helpful. 
      Thanks
    • By AndyK70
      I'm trying to fill a ListView with all normal viewable windows to act with them.
      First I tried with WinList:
      Local $aWinList = WinList("[REGEXPTITLE:(?i)(.+)]") Local $aTmp, $iID ;~ _ArrayDisplay($aWinList) For $i = $aWinList[0][0] To 1 Step -1 ; going backwards not disturbing the index while cycling through and deleting some If StringStripWS( $aWinList[$i][0], 3) == "" Or _ Not BitAND(WinGetState($aWinList[$i][1]), $WIN_STATE_VISIBLE) Or _ BitAND(WinGetState($aWinList[$i][1]), $WIN_STATE_MINIMIZED ) Then _ArrayDelete($aWinList, $i) Else ; Window has a Title and is "visible" $aTmp = WinGetPos($aWinList[$i][1]) If $aTmp[0] < -1000 Or $aTmp[1] < -1000 Then ; Window is minimized or tray icon _ArrayDelete($aWinList, $i) EndIf EndIf Next $aWinList[0][0] = UBound($aWinList)-1 ; getting actual # of windows ; Each row is now [ID]=> [Title], [hWnd] But it keeps getting Windows which are definitely not there at least not visible:

      Those windows "Rechner", "Einstellungen", "Netflix", "Microsoft Store", ... are not there!?! 
      It should list only the first three windows, which are real.
      I even tried it with _WinAPI_ UDF:
      $hWnd = _WinAPI_GetForegroundWindow() ; Add items _GUICtrlListView_BeginUpdate($idListview) If $hWnd <> 0 Then $iI = 0 Do If _WinAPI_IsWindow($hWnd) And _WinAPI_IsWindowVisible Then _GUICtrlListView_AddItem($idListview, WinGetTitle($hWnd)) _GUICtrlListView_AddSubItem($idListview, $iI, $hWnd, 1) $iI += 1 $hWnd = _WinAPI_GetWindow($hWnd, $GW_HWNDNEXT) EndIf Until $hWnd = 0 EndIf But it is the same...
       
      How can i distinguish those invisible windows from normal ones?
      PS: I'm using Windows 10, maybe it is important to know?
×
×
  • Create New...