Jump to content
Sign in to follow this  
marc0v

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

Share this post


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
Sign in to follow this  

  • Recently Browsing   0 members

    No registered users viewing this page.

  • Similar Content

    • 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?
    • By AutoitMike
      I am currently using Autoit's "WinList" function in an Autoit script, I fully understand it.
      Autoit provides a nice Library for Word VBA so that Some of Autoit's functions can be used within a MSWord VBA script, some of which I am using.
      I have a use for Autoit's "WinList" function which has been provided by AutoIt in their Library for MSWord VBA scripting. The WinList function Creates an array when run. MSWord VBA does not seem to allow this. To create an array in VBA you have to first DIM it, and this does not seem to work for the WinList function.
      For example:
      Dim AutoIt As New AutoItX3Lib.AutoItX3
      MyArray= Autoit(WinList)  produces an error, whether I dim the array or not. 
      It seems that AutoIt has provided a function for VBA that cant be used ??
      Can someone figure out a way to use WinList within Word VBA?
      Thanks
       
×
×
  • Create New...