Yincognito Posted November 24, 2023 Share Posted November 24, 2023 It's my first post here, but I'll get right into it in a direct fashion - remember, I'm aiming for the same thing as you do: a better AutoIt. 😉 The _ProcessGetName() function is extremely slow when placed within a loop - well, two loops in my case, a For ... Next one nested into a Do ... Until one, getting 10 standard properties for all existing windows in the system, in order to message them to another program for displaying and further handling. The compiled 32bit script made by v.3.3.16.1 takes around 8% of my CPU when grabbing the aforementioned window properties every second, with the function clearly the culprit since the CPU usage drastically drops to around 0.9% after commenting it out and replacing it with ... the same code as in the "...\Include\Process.au3" file from the AutoIt package, but placed differently. I suppose that the difference lies in that I'm getting the ProcessList() array inside the 1st loop but outside the 2nd, while the included function inevitably gets the array where I call the former (so everything in the 2nd loop). For the record, I only noticed that my workaround code is the same as the included one when I began a deeper investigation into what could be the cause of it. Also, for the sake of comparison, the rival AutoHotKey works flawlessly after compiling the equivalent script for its v.1 on 32bit, with only 0.5% CPU usage taken in precisely the same circumstances (but, of course, its syntax is not exactly user friendly, as you probably already know). I can provide code samples to illustrate the issue if needed, but you can easily replicate the behavior in about 25 lines or some simple stress test anyway, based on the description above - let me know if you still need the samples though. Naturally, the question / purpose / goal of the topic is to make you aware of the problem and hopefully determine a better / more efficient implementation of the included function, in terms of speed. P.S. There is also another probably unrelated (though apparently common, if looking for other similar topics) issue about occasional warnings / errors regarding subscripts on quickly closing windows, but I'll skip this one, I don't want to seem like I'm criticizing your otherwise fine piece of software. Link to comment Share on other sites More sharing options...
Danp2 Posted November 24, 2023 Share Posted November 24, 2023 Pretty sure you are going to be better off using a revised copy of the code from Process.au3, which will allow you to call ProcessList only once instead of multiple times (once for each window). Latest Webdriver UDF Release Webdriver Wiki FAQs Link to comment Share on other sites More sharing options...
argumentum Posted November 24, 2023 Share Posted November 24, 2023 2 hours ago, Yincognito said: inside the 1st loop but outside the 2nd, while the included function inevitably gets the array where I call the former (so everything in the 2nd loop). English ( or any spoken language for that matter ) is not as good as code. Post an example code. It's easier in the long run to have users replicate it and try to come up with a code solution/optimization. Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Yincognito Posted November 24, 2023 Author Share Posted November 24, 2023 1 hour ago, Danp2 said: Pretty sure you are going to be better off using a revised copy of the code from Process.au3, which will allow you to call ProcessList only once instead of multiple times (once for each window). Indeed, that's what I've been using as a workaround. The idea of this post was to let folks here know of the existing problem and who knows, maybe come up with a better solution for the Process.au3 code going forward. I'm just one user and I was lucky enough to rewrite the code more efficiently before finding out that the include file used the same (but restricted to where the function was used) code, but once more people are confronted with this, a faster alternative (if possible, of course) would probably be needed. The obvious one would be to include getting the associated process list in the implementation of WinList() itself, otherwise similar scenarios to mine would make the existing _ProcessGetName() unusable in terms of speed, depending on the sleep interval. 45 minutes ago, argumentum said: English ( or any spoken language for that matter ) is not as good as code. Post an example code. It's easier in the long run to have users replicate it and try to come up with a code solution/optimization. Sure thing! Here is the .au3 code (uncommenting the existing comments as well as commenting out the ProcessList() line and the For loop would switch to the slow built in solution): expandcollapse popup#NoTrayIcon #include <WinAPI.au3> ; Slow Alternative: #include <Process.au3> #include <SendMessage.au3> AutoItSetOption("WinTitleMatchMode", -2) Func SendBang($szBang) Local Const $hWnd = WinGetHandle("[CLASS:RainmeterMeterWindow]") If $hWnd Then Local Const $iSize = StringLen($szBang) + 1 Local Const $pMem = DllStructCreate("wchar[" & $iSize & "]") Local Const $pCds = DllStructCreate("dword;dword;ptr") Local Const $WM_COPYDATA = 0x004A DllStructSetData($pMem, 1, $szBang) DllStructSetData($pCds, 1, 1) DllStructSetData($pCds, 2, ($iSize * 2)) DllStructSetData($pCds, 3, DllStructGetPtr($pMem)) _SendMessage($hWnd, $WM_COPYDATA, 0, DllStructGetPtr($pCds)) EndIf EndFunc Func GetResult() Local $Action = $CmdLine[1], $Separator = $CmdLine[2], $Interval = $CmdLine[3] Do Local $Output = "" Local $WList = WinList() Local $PList = ProcessList() Local $WTitle, $WHandle, $WClass, $WState, $WPosition, $WX, $WY, $WW, $WH, $PID, $PName For $i = 1 To $WList[0][0] $WTitle = $WList[$i][0] $WHandle = $WList[$i][1] If $WHandle Then $WClass = _WinAPI_GetClassName($WHandle) $WState = WinGetState($WHandle) $WPosition = WinGetPos($WHandle) $WX = $WPosition[0] $WY = $WPosition[1] $WW = $WPosition[2] $WH = $WPosition[3] $PID = WinGetProcess($WHandle) $PName = "" ; Slow Alternative: $PName = _ProcessGetName($PID) For $j = 1 to $PList[0][0] If $PList[$j][1] = $PID Then $PName = $PList[$j][0] Next $Output = $Output & $WTitle & @TAB & $WClass & @TAB & $WHandle & @TAB & $WState & @TAB & $WX & @TAB & $WY & @TAB & $WW & @TAB & $WH & @TAB & $PID & @TAB & $PName & $Separator EndIf Next SendBang(StringReplace($Action, "$Output", $Output)) Sleep($Interval) Until $Interval < 0 EndFunc GetResult() And here is the .ahk v.1 code, for comparison (I know it isn't needed here, but that's why I pursued both, to choose the one that fares better in as many aspects as possible): expandcollapse popup#NoTrayIcon #Requires AutoHotkey v1.0 DetectHiddenWindows, On SendBang(ByRef szBang) { TargetWindowClass := "RainmeterMeterWindow" iSize := (StrLen(szBang) + 1) * (A_IsUnicode ? 2 : 1) VarSetCapacity(Cds, 3 * A_PtrSize, 0) NumPut(1, Cds) NumPut(iSize, Cds, A_PtrSize) NumPut(&szBang, Cds, 2 * A_PtrSize) SendMessage, 0x4a, 0, &Cds,, ahk_class %TargetWindowClass% return ErrorLevel } GetResult() { Action := A_Args[1], Separator := A_Args[2], Interval := A_Args[3] Loop { Output := "" WinGet, windows, List Loop, % windows { WHandle := windows%A_Index% WinGetTitle, WTitle, % "ahk_id " WHandle WinGetClass, WClass, % "ahk_id " WHandle WinGetPos, WX, WY, WW, WH, % "ahk_id " WHandle WinGet, PID, PID, % "ahk_id " WHandle WinGet, PName, ProcessName, % "ahk_id " WHandle WinGet, WState, MinMax, % "ahk_id " WHandle Output := % Output WTitle "`t" WClass "`t" WHandle "`t" WState "`t" WX "`t" WY "`t" WW "`t" WH "`t" PID "`t" PName Separator } SendBang(StrReplace(Action, "$Output", Output)) Sleep Interval If (Interval < 0) Break } } GetResult() You can disregard the rest of the code if you want, it's just sending a message to another program (Rainmeter), where I can display these in a "skin" / "widget" and send various other messages to desired windows from this list via a WindowMessagePlugin in order to control some music player, some HWiNFO sensor window and other similar productive / entertainment apps in the system (yes, I know I can do this directly from an .au3 / .ahk script, but like this I don't have to bother with creating the interface anymore). The chosen script is run with 3 parameters from the said application, i.e. a specific action / "bang" / command to send data back to the skin, a separator that's usually the newline character, and the interval at which this list is sent to the skin in milliseconds (-1 if only one pass through the outer loop is desired). argumentum 1 Link to comment Share on other sites More sharing options...
argumentum Posted November 24, 2023 Share Posted November 24, 2023 (edited) $PName = _ProcessGetName_FromTasklist($PID, $aPids) Func _ProcessGetName_FromTasklist($PID, ByRef $aPids, $iLoop = 0) ; pass $aPids as a string to init the array If UBound($aPids) < 2 Then Local $hTimer = TimerInit(), $iLines = 0, $aTemp, $sOutput, $iPID = Run(@ComSpec & ' /C tasklist /fo list', @TempDir, @SW_HIDE, $STDOUT_CHILD) ProcessWaitClose($iPID) $aTemp = StringSplit(StdoutRead($iPID), @CRLF, 1) Dim $aPids[UBound($aTemp)][6] For $n = 2 To $aTemp[0] Step 6 $iLines += 1 $aPids[$iLines][0] = justTheData($aTemp[$n + 0]) $aPids[$iLines][1] = justTheData($aTemp[$n + 1]) $aPids[$iLines][2] = justTheData($aTemp[$n + 2]) $aPids[$iLines][3] = justTheData($aTemp[$n + 3]) $aPids[$iLines][4] = justTheData($aTemp[$n + 4]) $aPids[$iLines][5] = justTheData($aTemp[$n + 5]) Next ReDim $aPids[$iLines + 1][6] $aPids[0][0] = $iLines ConsoleWrite('> $iLines >' & $iLines & @TAB & TimerDiff($hTimer) & @CRLF) EndIf For $n = 1 To $aPids[0][0] If $PID = $aPids[$n][1] Then Return $aPids[$n][0] Next If $iLoop Then Return "" $aPids = "" ; refresh the list only if not found ; you're not starting processes every second Return _ProcessGetName_FromTasklist($PID, $aPids, $iLoop + 1) EndFunc Func justTheData($sStr) Return StringStripWS(StringTrimLeft($sStr, StringInStr($sStr, ":") + 1), 3) EndFunc ..it's faster than one by one. CPU wise, is not much better. Edit: added to refresh the array only on not found. Makes it easier on the CPU. Edited November 24, 2023 by argumentum better code Yincognito 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Moderators Melba23 Posted November 24, 2023 Moderators Share Posted November 24, 2023 Moved to the appropriate forum. Moderation Team Any of my own code posted anywhere on the forum is available for use by others without any restriction of any kind Open spoiler to see my UDFs: Spoiler ArrayMultiColSort ---- Sort arrays on multiple columnsChooseFileFolder ---- Single and multiple selections from specified path treeview listingDate_Time_Convert -- Easily convert date/time formats, including the language usedExtMsgBox --------- A highly customisable replacement for MsgBoxGUIExtender -------- Extend and retract multiple sections within a GUIGUIFrame ---------- Subdivide GUIs into many adjustable framesGUIListViewEx ------- Insert, delete, move, drag, sort, edit and colour ListView itemsGUITreeViewEx ------ Check/clear parent and child checkboxes in a TreeViewMarquee ----------- Scrolling tickertape GUIsNoFocusLines ------- Remove the dotted focus lines from buttons, sliders, radios and checkboxesNotify ------------- Small notifications on the edge of the displayScrollbars ----------Automatically sized scrollbars with a single commandStringSize ---------- Automatically size controls to fit textToast -------------- Small GUIs which pop out of the notification area Link to comment Share on other sites More sharing options...
Yincognito Posted November 25, 2023 Author Share Posted November 25, 2023 @argumentum: Thank you very much for the code. I'm on my phone ATM, but I'll try it when I get back to my laptop. On a first look, I agree that while it may be a faster approach to iterate the array, in practice the CPU usage won't drop drastically given the tasklist disk access and string manipulation. I'll never know until I try it though, so I might be wrong. @Melba23: Thanks for moving the topic appropriately. As someone new to this forum, I posted this in the technical discussion thinking it's the AutoIt built-in function for this that first needs help in this specific context, and only then myself (just my honest assessment based on evidence, not criticizing in any way), but if you say so... Link to comment Share on other sites More sharing options...
Yincognito Posted November 25, 2023 Author Share Posted November 25, 2023 (edited) 19 hours ago, argumentum said: $PName = _ProcessGetName_FromTasklist($PID, $aPids) Func _ProcessGetName_FromTasklist($PID, ByRef $aPids, $iLoop = 0) ; pass $aPids as a string to init the array If UBound($aPids) < 2 Then Local $hTimer = TimerInit(), $iLines = 0, $aTemp, $sOutput, $iPID = Run(@ComSpec & ' /C tasklist /fo list', @TempDir, @SW_HIDE, $STDOUT_CHILD) ProcessWaitClose($iPID) $aTemp = StringSplit(StdoutRead($iPID), @CRLF, 1) Dim $aPids[UBound($aTemp)][6] For $n = 2 To $aTemp[0] Step 6 $iLines += 1 $aPids[$iLines][0] = justTheData($aTemp[$n + 0]) $aPids[$iLines][1] = justTheData($aTemp[$n + 1]) $aPids[$iLines][2] = justTheData($aTemp[$n + 2]) $aPids[$iLines][3] = justTheData($aTemp[$n + 3]) $aPids[$iLines][4] = justTheData($aTemp[$n + 4]) $aPids[$iLines][5] = justTheData($aTemp[$n + 5]) Next ReDim $aPids[$iLines + 1][6] $aPids[0][0] = $iLines ConsoleWrite('> $iLines >' & $iLines & @TAB & TimerDiff($hTimer) & @CRLF) EndIf For $n = 1 To $aPids[0][0] If $PID = $aPids[$n][1] Then Return $aPids[$n][0] Next If $iLoop Then Return "" $aPids = "" ; refresh the list only if not found ; you're not starting processes every second Return _ProcessGetName_FromTasklist($PID, $aPids, $iLoop + 1) EndFunc Func justTheData($sStr) Return StringStripWS(StringTrimLeft($sStr, StringInStr($sStr, ":") + 1), 3) EndFunc ..it's faster than one by one. CPU wise, is not much better. Edit: added to refresh the array only on not found. Makes it easier on the CPU. Well, what do you know... it's actually faster than my workaround - well done! It takes around 0.77% of the CPU, so it's better than the 0.9% of my method. I take back what I said earlier after just seeing it, it seems it's actually quite practical. Taking one of the ideas from your code (that I didn't apply earlier to mine), exiting the For loop early once a matching PID is found appears to produce the same effect (same 0.77% CPU as a maximum): Do ... Local $PList = ProcessList() ... For ... ... $PName = "" For $j = 1 to $PList[0][0] If $PList[$j][1] = $PID Then $PName = $PList[$j][0] ExitLoop EndIf Next ... Next ... Until ... Not sure if sorting the array first (or using a dictionary?) would make it even faster, given the array's simplicity. What do you guys think? Edited November 25, 2023 by Yincognito Further clarification. Link to comment Share on other sites More sharing options...
argumentum Posted November 25, 2023 Share Posted November 25, 2023 I wouldn't but try all the ideas that come to mind. Go figure. We may just learn something interesting Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Yincognito Posted November 25, 2023 Author Share Posted November 25, 2023 (edited) 1 hour ago, argumentum said: I wouldn't but try all the ideas that come to mind. Go figure. We may just learn something interesting So true. Well, I just made myself a PNames[] map out of the ProcessList() array from the outer loop, and got rid entirely of the For from the inner loop: Func GetResult() Local $Action = $CmdLine[1], $Separator = $CmdLine[2], $Interval = $CmdLine[3] Do Local $Output = "" Local $WList = WinList() Local $PList = ProcessList() Local $WTitle, $WHandle, $WClass, $WState, $WPosition, $WX, $WY, $WW, $WH, $PID, $PName, $PNames[] For $j = 1 to $PList[0][0] $PNames[$PList[$j][1]] = $PList[$j][0] Next For $i = 1 To $WList[0][0] $WTitle = $WList[$i][0] $WHandle = $WList[$i][1] If $WHandle Then $WClass = _WinAPI_GetClassName($WHandle) $WState = WinGetState($WHandle) $WPosition = WinGetPos($WHandle) $WX = $WPosition[0] $WY = $WPosition[1] $WW = $WPosition[2] $WH = $WPosition[3] $PID = WinGetProcess($WHandle) $PName = $PNames[$PID] $Output = $Output & $WTitle & @TAB & $WClass & @TAB & $WHandle & @TAB & $WState & @TAB & $WX & @TAB & $WY & @TAB & $WW & @TAB & $WH & @TAB & $PID & @TAB & $PName & $Separator EndIf Next SendBang(StringReplace($Action, "$Output", $Output)) Sleep($Interval) Until $Interval < 0 EndFunc Result: 0.26% CPU usage. Now the code's speed is even better than the AutoHotkey one (twice as fast)! 😎 By the way, in case maps are by any chance removed from AutoIt going forward (hopefully not, judging by the above), declaring $PNames[4294967295] also works... 🤣 EDIT: I still have to deal with the pesky "Subscript used on non-accessible variable" error though - any chance of getting rid of it? Or should I make another topic just for that? P.S. Suggestion: if the _ProcessGetName() function from <Process.au3> stays the same in the future, some note in the Remarks section of its page should mention users to be aware of its high CPU usage when using it in multiple loops updated frequently and attempt alternative implementations. Just in case others stumble upon this effect in their scripts. 😉 Edited November 25, 2023 by Yincognito Added related error. argumentum 1 Link to comment Share on other sites More sharing options...
argumentum Posted November 25, 2023 Share Posted November 25, 2023 (edited) ... Local $Output, $WList, $PList Local $WTitle, $WHandle, $WClass, $WState, $WPosition, $WX, $WY, $WW, $WH, $PID, $PName, $PNames Do $Output = "" $WList = WinList() $PList = ProcessList() Dim $PNames[] ... ..so you don't declare (Local/Global) in a loop. As far as maps, they have been there for quite some time. No user here foresees them going away. 37 minutes ago, Yincognito said: P.S. Suggestion: if the _ProcessGetName() function ... Func _ProcessGetName($iPID) Local $aProcessList = ProcessList() For $i = 1 To UBound($aProcessList) - 1 If $aProcessList[$i][1] = $iPID Then Return $aProcessList[$i][0] EndIf Next Return SetError(1, 0, "") EndFunc ;==>_ProcessGetName Do look at the UDFs. They are not magical. In this case you're writing a _ProcessGetNameEx_MyWay(), so to say Edit: oops Edited November 25, 2023 by argumentum oops Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
argumentum Posted November 25, 2023 Share Posted November 25, 2023 (edited) 42 minutes ago, Yincognito said: EDIT: I still have to deal with the pesky "Subscript used on non-accessible variable" error though - any chance of getting rid of it? Well, that is an error on your logic ... somewhere. Do write a separate script to debug that function. Is not a bad idea. But once you've got the bug figured out, re-incorporate the function back to the main script. No need to have 2 executables. Edited November 25, 2023 by argumentum spelling Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Yincognito Posted November 25, 2023 Author Share Posted November 25, 2023 (edited) 48 minutes ago, argumentum said: ..so you don't declare (Local/Global) in a loop. Good idea, thanks (was unsure about the Dim / Redim impact, so tried to avoid it earlier). 48 minutes ago, argumentum said: As far as maps, they have been there for quite some time. No user here foresees them going away. Great news, thanks for letting me know. I actually wanted to ask this, by the way, but you read my mind. 48 minutes ago, argumentum said: Do look at the UDFs. They are not magical. In this case you're writing a _ProcessGetNameEx_MyWay(), so to say I don't think I wrote that, since the function body is split between the loops in my codes, which is precisely why it's faster. Unfortunately, you can't do that with an UDF. So if I make an UDF out of the above, it will be equally slow, since ProcessList() will be called in the inner loop, i.e. where I need the process name. 37 minutes ago, argumentum said: Well, that is an error on your logic ... somewhere. Do write a separate script to debug that function. Is not a bad idea. But once you've got the bug figured out, re-incorporate the function back to the main script. No need to have 2 executables. That is the whole point of the "If $WHandle Then" part above. Despite the code being quite straightforward, I tried lots of ways to make the error go away to no avail, and short of having a check on every array via IsArray / IsObj or similar (which is preposterous), I can't figure out why or where it happens. Not sure if it's an logical error on my part, since the error happens randomly / occasionally making it difficult to identify the culprit (and for roughly the same approach in AutoHotKey, it didn't happen once). Anyway, I'll probably get to the bottom of it, I'll just comment out stuff part by part and be done with it (logically speaking, just about the only place where it could happen given the already clear WList iteration, is when accessing the WinGetPos() elements, which could be on occasion less than 4?)... Edited November 25, 2023 by Yincognito Link to comment Share on other sites More sharing options...
Solution argumentum Posted November 25, 2023 Solution Share Posted November 25, 2023 47 minutes ago, Yincognito said: That is the whole point of the "If $WHandle Then" part above. The handle should use "IsHWnd($WHandle)". But the error is in the array. I would use "if ubound($array) < 2 then ContinueLoop", hence the Sleep() would be placed up higher, in case of a ContinueLoop. I do test the return value (an array in this case), even if the there is no @error return in the function, I test for failure regardless. That's what I do. 51 minutes ago, Yincognito said: I don't think I wrote that, since the function body is ... That function is in the include. Just because is an include/udf/whatnot, does not mean that is efficient for the use case in the code. Yincognito 1 Follow the link to my code contribution ( and other things too ). FAQ - Please Read Before Posting. Link to comment Share on other sites More sharing options...
Yincognito Posted November 25, 2023 Author Share Posted November 25, 2023 1 hour ago, argumentum said: Well, that is an error on your logic ... somewhere. Turns out that it's an error alright, just not on my logic. I "reverse engineered" the line number where the error occurs (simulated an error on my last line and then subtracted the difference between the error messages' line numbers), and it's exactly where I thought it would be: when accessing the WinGetPos() elements - specifically, the Y. So basically, WinGetPos() will sometimes yield the X, i.e. WinGetPos()[0], but not the Y (and probably not the other coordinates either) - so your solution to check the ubound() of the array is spot on - thanks again. Still, it's against the documentation of the function, which states that the return value is always a 4 element array, except when the window is not found (which can't be true, since I iterate precisely through all existing windows in the first place, and secondly because the "X" or whatever of such a window is actually returned without any error)... Link to comment Share on other sites More sharing options...
Nine Posted November 25, 2023 Share Posted November 25, 2023 Hmm. doubt that. Unless you can make a repro that we can actually run, my take is that the window does not exist anymore when you try to get its position. “They did not know it was impossible, so they did it” ― Mark Twain Spoiler Block all input without UAC Save/Retrieve Images to/from Text Monitor Management (VCP commands) Tool to search in text (au3) files Date Range Picker Virtual Desktop Manager Sudoku Game 2020 Overlapped Named Pipe IPC HotString 2.0 - Hot keys with string x64 Bitwise Operations Multi-keyboards HotKeySet Recursive Array Display Fast and simple WCD IPC Multiple Folders Selector Printer Manager GIF Animation (cached) Screen Scraping Multi-Threading Made Easy Link to comment Share on other sites More sharing options...
Danp2 Posted November 25, 2023 Share Posted November 25, 2023 Check @error following the call to WinGetPos. argumentum 1 Latest Webdriver UDF Release Webdriver Wiki FAQs Link to comment Share on other sites More sharing options...
Yincognito Posted November 25, 2023 Author Share Posted November 25, 2023 4 hours ago, argumentum said: The handle should use "IsHWnd($WHandle)". But the error is in the array. I would use "if ubound($array) < 2 then ContinueLoop", hence the Sleep() would be placed up higher, in case of a ContinueLoop. I do test the return value (an array in this case), even if the there is no @error return in the function, I test for failure regardless. That's what I do. 2 hours ago, Nine said: Hmm. doubt that. Unless you can make a repro that we can actually run, my take is that the window does not exist anymore when you try to get its position. 1 hour ago, Danp2 said: Check @error following the call to WinGetPos. Well, I understand your point of view since I've been in a similar position helping and without replicating and testing things yourself is hard to both believe and provide a solution. Following @argumentum's reply about the "If $WHandle Then" part being futile I got rid of it, only assigned WPosition[] array values to my variables "if UBound($WPosition) = 4" and still got errors about subscripts being either "non-accessible" or "badly formatted" in the $WPosition[N] lines, despite accessing the array specifically avoided in the code if its size was under 4 (I even spawned my own MsgBox telling me the lower size to verify if I succeeded in bypassing the error). However, after applying both parts of @argumentum's advice, it "seems" that the issue was solved (must test this thoroughly though). This is how my code looks like now: expandcollapse popup#NoTrayIcon #include <WinAPI.au3> #include <SendMessage.au3> AutoItSetOption("WinTitleMatchMode", -2) Func SendBang($szBang) Local Const $hWnd = WinGetHandle("[CLASS:RainmeterMeterWindow]") If $hWnd Then Local Const $iSize = StringLen($szBang) + 1 Local Const $pMem = DllStructCreate("wchar[" & $iSize & "]") Local Const $pCds = DllStructCreate("dword;dword;ptr") Local Const $WM_COPYDATA = 0x004A DllStructSetData($pMem, 1, $szBang) DllStructSetData($pCds, 1, 1) DllStructSetData($pCds, 2, ($iSize * 2)) DllStructSetData($pCds, 3, DllStructGetPtr($pMem)) _SendMessage($hWnd, $WM_COPYDATA, 0, DllStructGetPtr($pCds)) EndIf EndFunc Func GetResult() Local $Action = $CmdLine[1], $Separator = $CmdLine[2], $Interval = $CmdLine[3] Local $Output, $WList, $PList, $PNames, $ACount, $WTitle, $WHandle, $WClass, $WState, $WPosition, $WX, $WY, $WW, $WH, $PID, $PName Do Dim $Output = "", $WList = WinList(), $PList = ProcessList(), $PNames[] For $j = 1 to $PList[0][0] $PNames[$PList[$j][1]] = $PList[$j][0] Next For $i = 1 To $WList[0][0] $WTitle = $WList[$i][0] $WHandle = $WList[$i][1] If IsHWnd($WHandle) Then $WClass = _WinAPI_GetClassName($WHandle) $WState = WinGetState($WHandle) $WPosition = WinGetPos($WHandle) Dim $WX = -32000, $WY = -32000, $WW = -32000, $WH = -32000 If (IsArray($WPosition)) And (UBound($WPosition) = 4) Then Dim $WX = $WPosition[0], $WY = $WPosition[1], $WW = $WPosition[2], $WH = $WPosition[3] EndIf $PID = WinGetProcess($WHandle) $PName = $PNames[$PID] $Output = $Output & $WTitle & @TAB & $WClass & @TAB & $WHandle & @TAB & $WState & @TAB & $WX & @TAB & $WY & @TAB & $WW & @TAB & $WH & @TAB & $PID & @TAB & $PName & $Separator EndIf Next SendBang(StringReplace($Action, "$Output", $Output)) Sleep($Interval) Until $Interval < 0 EndFunc GetResult() ; Editor Line 55 = Binary Line 13069 Yeah, the repro is a bit difficult to provide. I could provide the packed Rainmeter skin itself (containing the source scripts and their compiled versions), which can be installed with a double click once Rainmeter is installed as well, but I'm not sure if you'll manage to "determine" the script to throw the error (I quickly navigate between two browser tabs, trigger the context menu, select a few lines to copy them, and then open my Winamp only to quickly close it - I know, it's an entire "ritual", lol). Indeed, the window probably doesn't exist by that point, it's just that it's a bit inconvenient to always check for such things... in a loop that iterates only between existing windows to begin with (via the 1st For loop), if you know what I mean. One might ask what's the point in running that loop if you still have to check something that the loop supposedly already avoided. P.S. I will let you know if these error(s) make a comeback to haunt me again. So far so good, thanks to @argumentum - fingers crossed. 🙂 argumentum 1 Link to comment Share on other sites More sharing options...
Yincognito Posted December 8, 2023 Author Share Posted December 8, 2023 As promised, I return to let you know that the results of my tests indicate that I indeed have to check for errors on each line to entirely get rid of the subscript errors, as hinted earlier as well in the post I'll mark as solution (yes, I know there are some redundancies in the code, but they are intentional). Even a single If encompassing multiple WinGet... assignments is not enough since the amount of work done inside it has an impact on the errors occurrence. That being said, it's not that bad, since apparently using If IsHWnd(...) Then ... is much more resource friendly than having If @error Then ContinueLoop after each line (the former has the same CPU usage whether the data retrieval interval is 25 ms or 1000 ms, while the latter naturally takes more CPU when on 25 ms): expandcollapse popup#NoTrayIcon #include <WinAPI.au3> #include <SendMessage.au3> Func SendBang($Bang) Local Const $hWnd = WinGetHandle("[CLASS:RainmeterMeterWindow]") If $hWnd Then Local Const $iLen = StringLen($Bang) + 1 Local Const $pMem = DllStructCreate("wchar[" & $iLen & "]") Local Const $pCds = DllStructCreate("dword;dword;ptr") Local Const $WM_COPYDATA = 0x004A DllStructSetData($pMem, 1, $Bang) DllStructSetData($pCds, 1, 1) DllStructSetData($pCds, 2, ($iLen * 2)) DllStructSetData($pCds, 3, DllStructGetPtr($pMem)) _SendMessage($hWnd, $WM_COPYDATA, 0, DllStructGetPtr($pCds)) EndIf EndFunc Func GetResult() Local $aAct = $CmdLine[1], $aSep = $CmdLine[2], $aInt = $CmdLine[3] Do Local $sOut = "", $pMap[], $pLis = ProcessList(), $wLis = WinList() For $j = 1 to $pLis[0][0] $pMap[$pLis[$j][1]] = $pLis[$j][0] Next For $i = 1 To $wLis[0][0] Local $hWnd = $wLis[$i][1], $pNum = -1, $pNam = -1, $wSty = -1, $wSet = -1, $wTit = -1, $wHan = -1, $wCla = -1, $wSta = -1, $wPos = -1, $wPro = -1 If IsHWnd($hWnd) Then Dim $wHan = WinGetHandle($hWnd) If IsHWnd($wHan) Then Dim $wTit = WinGetTitle($wHan) If IsHWnd($wHan) Then Dim $wCla = _WinAPI_GetClassName($wHan) If IsHWnd($wHan) Then Dim $wSty = WinGetState($wHan), $wSta = BitAND($wSty,1)/1 & @TAB & BitAND($wSty,2)/2 & @TAB & BitAND($wSty,4)/4 & @TAB & BitAND($wSty,8)/8 & @TAB & BitAND($wSty,16)/16 & @TAB & BitAND($wSty,32)/32 If IsHWnd($wHan) Then Dim $wSet = WinGetPos($wHan), $wPos = $wSet[0] & @TAB & $wSet[1] & @TAB & $wSet[2] & @TAB & $wSet[3] If IsHWnd($wHan) Then Dim $pNum = WinGetProcess($wHan), $pNam = $pMap[$pNum], $wPro = $pNum & @TAB & $pNam If IsHWnd($WHan) Then $sOut &= StringSplit($sOut, $aSep, 1)[0] & @TAB & $wTit & @TAB & $wCla & @TAB & $wHan & @TAB & $wSta & @TAB & $wPos & @TAB & $wPro & @TAB & $aSep Next SendBang(StringReplace($aAct, "$Output", $sOut)) Sleep($aInt) Until $aInt < 0 EndFunc GetResult() Overall, I'm happy with the results and grateful for the help I got here from everybody. I couldn't keep the CPU at the 0.26% I got earlier, but it's not that far at between 0.39% and 0.77% with all the error checking and everything (the StringSplit() understandably adds a bit more up to 1% on occasion, of course). I will mark @argumentum's reply about the error checking as the solution, because he led me to the most efficient path to fix these errors and was instrumental in me finding the dictionary approach that helped replace the _ProcessGetName() function when it came to the ID and name coupling. Thanks again! argumentum 1 Link to comment Share on other sites More sharing options...
Recommended Posts
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 accountSign in
Already have an account? Sign in here.
Sign In Now