WildByDesign Posted yesterday at 12:41 PM Posted yesterday at 12:41 PM The current project that I am working on depends on every single millisecond. I always have the handles. But what I need is to get the process name. From my recent timer testing, _WinAPI_GetProcessName($iPID) is about as slow as a giant tortoise. I do have the PID as well. But it's just way to slow. I have been using _WinAPI_GetWindowFileName($hWnd) since I do already have the handles and it is the fast method that I have found. It returns the full binary path, eg: "C:\Windows\System32\AppHostRegistrationVerifier.exe" So what I am requesting help on is the fastest way to get "AppHostRegistrationVerifier.exe" from the full binary path. Here is my current timer testing script for this particular topic: expandcollapse popup#include <WinAPISysWin.au3> #include <WinAPIProc.au3> #include <Array.au3> #include <WinAPISys.au3> #include <File.au3> Global $maxruns = 10000 ; Run Notepad Run("notepad.exe") ; Wait 10 seconds for the Notepad window to appear. WinWait("[CLASS:Notepad]", "", 10) Global $iPID = ProcessExists("notepad.exe") ; Retrieve the handle of the Notepad window using the classname of Notepad. Global $hWnd = WinGetHandle("[CLASS:Notepad]") ConsoleWrite("Notepad handle: " & $hWnd & @CRLF) Example3() Func Example3() Local $hTimer = TimerInit() For $i = 1 To $maxruns Local $test = _WinAPI_GetWindowFileName($hWnd) Next Local $fDiff = TimerDiff($hTimer) ConsoleWrite("Timer: " & $fDiff & @CRLF) ConsoleWrite("_WinAPI_GetWindowFileName: " & $test & @CRLF) EndFunc Example31() Func Example31() Local $sDrive = "", $sDir = "", $sFileName = "", $sExtension = "" Local $hTimer = TimerInit() For $i = 1 To $maxruns Local $test = _WinAPI_GetWindowFileName($hWnd) If Not $test Then Return Local $aPathSplit = _PathSplit($test, $sDrive, $sDir, $sFileName, $sExtension) $sName = $aPathSplit[3] & $aPathSplit[4] Next Local $fDiff = TimerDiff($hTimer) ConsoleWrite("Timer: " & $fDiff & @CRLF) ConsoleWrite("_WinAPI_GetWindowFileName (broken down): " & $sName & @CRLF) EndFunc Example3 is giving the full path at a decent performance. Example31 is using _PathSplit() to break down the path and get the process name at not too much additional performance. This is what I am currently still using. The question is, can I be doing this any better? _PathSplit() uses an array of course. I do not know how to do regex at all. But I am wondering if that might be faster and smarter. Maybe more efficient too but I just don't know. If _WinAPI_GetWindowFileName($hWnd) returns blank, we Return out of the function on that. But otherwise it would always be in a path format (eg. "C:\Windows\System32\AppHostRegistrationVerifier.exe") and always end with ".exe". I have started to use TimerDiff on any functions that I use now and it's always quite eye-opening. There's always so many different ways to achieve the same goal. So I try to use the fastest method, at least in my current project which absolutely requires speed since it is an event hook. Thank you for your time.
Solution MattyD Posted yesterday at 02:45 PM Solution Posted yesterday at 02:45 PM (edited) Hey mate, Yeah so there's a bit you can do.. _WinAPI_GetWindowFileName calls a few things, which we can streamline if we're planning to call it 10000 times,. expandcollapse popupFunc _WinAPI_GetWindowFileName($hWnd) Local $iPID = 0 Local $aCall = DllCall("user32.dll", "bool", "IsWindow", "hwnd", $hWnd) If $aCall[0] Then $aCall = DllCall("user32.dll", "dword", "GetWindowThreadProcessId", "hwnd", $hWnd, "dword*", 0) $iPID = $aCall[2] EndIf If Not $iPID Then Return SetError(1, 0, '') Local $sResult = _WinAPI_GetProcessFileName($iPID) If @error Then Return SetError(@error, @extended, '') Return $sResult EndFunc Func _WinAPI_GetProcessFileName($iPID = 0) If Not $iPID Then $iPID = @AutoItPID Local $hProcess = DllCall('kernel32.dll', 'handle', 'OpenProcess', 'dword', ((_WinAPI_GetVersion() < 6.0) ? 0x00000410 : 0x00001010), _ 'bool', 0, 'dword', $iPID) If @error Or Not $hProcess[0] Then Return SetError(@error + 20, @extended, '') Local $sPath = _WinAPI_GetModuleFileNameEx($hProcess[0]) Local $iError = @error DllCall("kernel32.dll", "bool", "CloseHandle", "handle", $hProcess[0]) If $iError Then Return SetError(@error, 0, '') Return $sPath EndFunc Func _WinAPI_GetModuleFileNameEx($hProcess, $hModule = 0) Local $aCall = DllCall(@SystemDir & '\psapi.dll', 'dword', 'GetModuleFileNameExW', 'handle', $hProcess, 'handle', $hModule, _ 'wstr', '', 'int', 4096) If @error Or Not $aCall[0] Then Return SetError(@error + 10, @extended, '') Return $aCall[3] EndFunc Firstly I'd be using dll handles with DllOpen instead of using file names in DllCall, this should save open/closing Dlls all the time! That IsWindow call in GetWindowFilename is there as a sanity check. So if you're reasonably happy that you won't sent it rubbish, that can go. GetVersion doesn't need to be called 10000 times. 6.0 is Vista, so we probably don't really care about this anymore... but if you do - just call GetVersion once before your loop, or store it as a static or something. Use GetModuleBaseNameW instead of GetModuleFileNameExW - that will get you just the "notepad.exe" part of the process name We don't need to allocate the string buffer and free it 10000 times either (assuming the func returns a null-terminated string). Create a buffer once beforehand and just reuse it. expandcollapse popup#include <WinAPISysWin.au3> #include <WinAPIProc.au3> #include <Array.au3> #include <WinAPISys.au3> #include <File.au3> Global $maxruns = 10000 Global $hUser32 = DllOpen('user32.dll') Global $hKernel32 = DllOpen('kernel32.dll') Global $hpsapi = DllOpen('psapi.dll') ; Run Notepad Run("notepad.exe") ; Wait 10 seconds for the Notepad window to appear. WinWait("[CLASS:Notepad]", "", 10) Global $iPID = ProcessExists("notepad.exe") ; Retrieve the handle of the Notepad window using the classname of Notepad. Global $hWnd = WinGetHandle("[CLASS:Notepad]") ConsoleWrite("Notepad handle: " & $hWnd & @CRLF) Example3() Func Example3() Local $hTimer = TimerInit() For $i = 1 To $maxruns Local $test = _WinAPI_GetWindowFileName2($hWnd) Next Local $fDiff = TimerDiff($hTimer) ConsoleWrite("Timer: " & $fDiff & @CRLF) ConsoleWrite("_WinAPI_GetWindowFileName: " & $test & @CRLF) EndFunc Func _WinAPI_GetWindowFileName2($hWnd) Local Static $iNumChars = 512, $tBuff = DllStructCreate(StringFormat("wchar[%d]", $iNumChars)) Local $aCall = DllCall($hUser32, "dword", "GetWindowThreadProcessId", "hwnd", $hWnd, "dword*", 0) Local $iPID = $aCall[2] $aCall = DllCall($hKernel32, 'handle', 'OpenProcess', 'dword', 0x00001010, 'bool', 0, 'dword', $iPID) Local $hProc = $aCall[0] DllCall($hpsapi, 'dword', 'GetModuleBaseNameW', 'handle', $hProc, 'handle', 0, 'struct*', $tBuff, 'int', $iNumChars) $aCall = DllCall($hKernel32, "bool", "CloseHandle", "handle", $hProc) Return DllStructGetData($tBuff, 1) EndFunc Obviously you'll want to put some error checks back in, and clean up when you're done.. but you get the idea Edit: and the time comparison.. Notepad handle: 0x00000000005B0D40 Timer: 548.97 _WinAPI_GetWindowFileName2: Notepad.exe Timer: 1794.9062 _WinAPI_GetWindowFileName: C:\Program Files\WindowsApps\Microsoft.WindowsNotepad_11.2507.26.0_x64__8wekyb3d8bbwe\Notepad\Notepad.exe Edited yesterday at 02:59 PM by MattyD WildByDesign, SOLVE-SMART, Musashi and 1 other 4
SOLVE-SMART Posted yesterday at 03:15 PM Posted yesterday at 03:15 PM Wow @MattyD, that is impressive. I also played around with some variants but what really did the trick is mentioned by you: 25 minutes ago, MattyD said: Firstly I'd be using dll handles with DllOpen instead of using file names in DllCall, this should save open/closing Dlls all the time! My timer result was 980.0433, but you version is way better! I guess this will help @WildByDesign a lot, Best regards Sven WildByDesign and MattyD 2 ==> AutoIt related: 🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet Spoiler 🌍 Au3Forums 🎲 AutoIt (en) Cheat Sheet 📊 AutoIt limits/defaults 💎 Code Katas: [...] (comming soon) 🎭 Collection of GitHub users with AutoIt projects 🐞 False-Positives 🔮 Me on GitHub 💬 Opinion about new forum sub category 📑 UDF wiki list ✂ VSCode-AutoItSnippets 📑 WebDriver FAQs 👨🏫 WebDriver Tutorial (coming soon)
WildByDesign Posted yesterday at 03:29 PM Author Posted yesterday at 03:29 PM 11 minutes ago, SOLVE-SMART said: I guess this will help @WildByDesign a lot, Indeed, yes. My jaw is still on the floor. If this forum has any kind of Solution of the Year, Matty nailed it. As soon as I pick my jaw up from the floor I’m going to respond in more detail. MattyD and SOLVE-SMART 1 1
WildByDesign Posted yesterday at 03:32 PM Author Posted yesterday at 03:32 PM And the DllOpen makes sense too because this will be used often dozens of times per second during the entire time that my event hook process is running. As a matter of fact, I should probably look into all of the other WinAPI calls that I make and see about modifying those functions for performance as well. I would image they all probably call the same DLLs.
MattyD Posted 20 hours ago Posted 20 hours ago (edited) To be fair, Libraries like "user32.dll" would already be loaded by autoit for its own purposes, so I'd imagine doing a DllCall("user32.dll", blah) would probably just result in adding/removing a reference to the already loaded dll. So the time saving would be much less than say "wlanapi.dll" for example, which I assume fully loads/unloads the dll during the call.. #include <WinAPISys.au3> Local $hOrigUser32Handle = _WinAPI_GetModuleHandle("user32.dll") ConsoleWrite("user32 before load: " & $hOrigUser32Handle & @CRLF) $hUser32Dll = _WinAPI_LoadLibrary("user32.dll") ConsoleWrite("user32 after load: " & _WinAPI_GetModuleHandle("user32.dll") & @CRLF) ConsoleWrite("Same Handle = " & ($hUser32Dll = $hOrigUser32Handle) & @CRLF) _WinAPI_FreeLibrary($hUser32Dll) ConsoleWrite("user32 after free: " & _WinAPI_GetModuleHandle("user32.dll") & @CRLF & @CRLF) ConsoleWrite("wlanapi before load: " & _WinAPI_GetModuleHandle("wlanapi.dll") & @CRLF) $hWlanapiDll = _WinAPI_LoadLibrary("wlanapi.dll") ConsoleWrite("wlanapi after load: " & _WinAPI_GetModuleHandle("wlanapi.dll") & @CRLF) _WinAPI_FreeLibrary($hWlanapiDll) ConsoleWrite("wlanapi after free: " & _WinAPI_GetModuleHandle("wlanapi.dll") & @CRLF) Edited 18 hours ago by MattyD updated example SOLVE-SMART and WildByDesign 1 1
Popular Post AspirinJunkie Posted 12 hours ago Popular Post Posted 12 hours ago If every ounce of performance is really needed, then there are a few areas where optimization can be carried out to squeeze out a little more: expandcollapse popup#include <WinAPIProc.au3> Global $hUser32 = DllOpen('user32.dll') Global $hKernel32 = DllOpen('kernel32.dll') Global $hpsapi = DllOpen('psapi.dll') ; Run Notepad Run("notepad.exe") ; Wait up to 10 seconds for the Notepad window to appear. WinWait("[CLASS:Notepad]", "", 10) ; Retrieve the handle of the Notepad window using the classname of Notepad. Global $hWnd = WinGetHandle("[CLASS:Notepad]") ConsoleWrite("Notepad handle: " & $hWnd & @CRLF) ConsoleWrite("Notepad process: " & _WinAPI_GetWindowFileName3($hWnd) & @CRLF) ; time comparison Global Const $N = 1e4 Global $iT ConsoleWrite(@CRLF & " name time" & @CRLF & "---------------------------------------------" & @CRLF) ; the first measurement $iT = TimerInit() For $i = 1 To $N _WinAPI_GetWindowFileName($hWnd) Next $iT = TimerDiff($iT) ConsoleWrite(StringFormat("% 30s:\t%10.6f ms\n", "_WinAPI_GetWindowFileName ", ($iT) / $N)) ; the second measurement $iT = TimerInit() For $i = 1 To $N _WinAPI_GetWindowFileName2($hWnd) Next $iT = TimerDiff($iT) ConsoleWrite(StringFormat("% 30s:\t%10.6f ms\n", "_WinAPI_GetWindowFileName2", ($iT) / $N)) ; the third measurement $iT = TimerInit() For $i = 1 To $N _WinAPI_GetWindowFileName3($hWnd) Next $iT = TimerDiff($iT) ConsoleWrite(StringFormat("% 30s:\t%10.6f ms\n", "_WinAPI_GetWindowFileName3", ($iT) / $N)) ; close notepad WinKill($hWnd) Func _WinAPI_GetWindowFileName2($hWnd) Local Static $iNumChars = 512, $tBuff = DllStructCreate(StringFormat("wchar[%d]", $iNumChars)) Local $aCall = DllCall($hUser32, "dword", "GetWindowThreadProcessId", "hwnd", $hWnd, "dword*", 0) Local $iPID = $aCall[2] $aCall = DllCall($hKernel32, 'handle', 'OpenProcess', 'dword', 0x00001010, 'bool', 0, 'dword', $iPID) Local $hProc = $aCall[0] DllCall($hpsapi, 'dword', 'GetModuleBaseNameW', 'handle', $hProc, 'handle', 0, 'struct*', $tBuff, 'int', $iNumChars) $aCall = DllCall($hKernel32, "bool", "CloseHandle", "handle", $hProc) Return DllStructGetData($tBuff, 1) EndFunc Func _WinAPI_GetWindowFileName3($hWnd) Local $aCall = DllCall($hKernel32, 'HANDLE', 'OpenProcess', 'DWORD', 0x410, 'BOOL', 0, 'DWORD', WinGetProcess($hWnd)), _ $hProc = $aCall[0] $aCall = DllCall($hpsapi, 'DWORD', 'GetModuleBaseNameW', 'HANDLE', $hProc, 'HANDLE', 0, 'WSTR', '', 'INT', 65536) DllCall($hKernel32, "BOOL", "CloseHandle", "HANDLE", $hProc) Return $aCall[3] EndFunc Otherwise, the solutions presented so far completely lack reasonable error management. And if only this function needs the DLL handles, then the global variables could be bypassed by defining them directly in the function as local static variables instead. SOLVE-SMART, ioa747, Musashi and 2 others 5
SOLVE-SMART Posted 10 hours ago Posted 10 hours ago 1 hour ago, AspirinJunkie said: Otherwise, the solutions presented so far completely lack reasonable error management. True and already mentioned 😅 in post #2. 19 hours ago, MattyD said: Obviously you'll want to put some error checks back in, and clean up when you're done.. but you get the idea ------------------------ 1 hour ago, AspirinJunkie said: And if only this function needs the DLL handles, then the global variables could be bypassed by defining them directly in the function as local static variables instead. Also true, thanks @AspirinJunkie. Best regards Sven WildByDesign 1 ==> AutoIt related: 🔗 GitHub, 🔗 Discord Server, 🔗 Cheat Sheet Spoiler 🌍 Au3Forums 🎲 AutoIt (en) Cheat Sheet 📊 AutoIt limits/defaults 💎 Code Katas: [...] (comming soon) 🎭 Collection of GitHub users with AutoIt projects 🐞 False-Positives 🔮 Me on GitHub 💬 Opinion about new forum sub category 📑 UDF wiki list ✂ VSCode-AutoItSnippets 📑 WebDriver FAQs 👨🏫 WebDriver Tutorial (coming soon)
WildByDesign Posted 8 hours ago Author Posted 8 hours ago (edited) @MattyD Sorry that I wasn't able to send my reply to you yesterday. I wasn't able to access the AutoIt forums or even the AutoIt help file stuff at all for the majority of the day and evening. As a matter of fact, I ended up having to use AutoIt3Help.exe to reference some information that I was studying at the time. Your initial response was nothing short of phenomenal. Your response was thorough yet concise. I don't always learn well from all types of teaching styles. But the way that you laid everything out was perfect for me to understand and to learn from. Not only that, but it also gave me many different directions to consider; different potential performance saving opportunities. But on top of all of that, it really opened my mind quite a bit. Such as that possibility of using some of those WinAPI functions, modifying them for better performance and using those modified functions instead. The biggest savings, really, were the whole DllOpen stuff. Since this is a process that is constantly running in the background and utilizing SetWinEventHook, there were a handful of WinAPI functions that were loading and unloading the same DLL modules every second or so. Therefore I modified _WinAPI_IsWindowVisible(), _WinAPI_GetClassName(), _WinAPI_DwmSetWindowAttribute(), and several more to use the DllOpen handle instead. These were functions that are called repeatedly. Every single one of them had performance savings of anywhere from 50-60% just from the DllOpen alone. Some of you might already be aware, but this is for my Immersive UX (formerly DwmColorBlurMica) project. I haven't uploaded these changes yet, but from my own testing on my machine everything applies so much smoother and noticeably faster. Thank you again, Matty. As I said, your response was very insightful, helpful, educational and so much more. I spent many hours yesterday learning from your response and exploring all of the different possibilities from those suggestions. I appreciate the time that you spent to share that with me. Cheers! Edited 8 hours ago by WildByDesign spelling MattyD 1
WildByDesign Posted 8 hours ago Author Posted 8 hours ago 3 hours ago, AspirinJunkie said: If every ounce of performance is really needed, then there are a few areas where optimization can be carried out to squeeze out a little more: Thank you for sharing this nice time comparison script as well. I like how well organized your script is. _WinAPI_GetWindowFileName3($hWnd) is definitely the fastest option for getting the process name. I am going to switch to using your version since it is consistently the better performing option. I appreciate your time on this.
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