Windows进程窗口标题捕获遇重复键/集合修改异常求助
问题描述
我开发了一段用于捕获Windows进程窗口标题的VB.NET代码,目前程序抛出两个异常:
ArgumentException:已添加相同键InvalidOperationException:集合已修改,枚举操作未执行
调试后未找到问题根源,该函数本应正常返回每个活动进程的窗口标题。异常出自以下代码段:
GetWindowText(hWnd, stringBuilder, length + 1) If Not dictWindows.ContainsKey(hWnd) Then dictWindows.Add(hWnd, stringBuilder.ToString) else Return False End If
完整代码如下:
Private Declare Function GetForegroundWindow Lib "user32" Alias "GetForegroundWindow" () As IntPtr Private Declare Auto Function GetWindowThreadProcessId Lib "user32.dll" (ByVal hwnd As IntPtr, ByRef lpdwProcessId As Integer) As Integer Private Declare Function GetLastInputInfo Lib "user32.dll" (ByRef inputStructure As inputInfo) As Boolean <DllImport("USER32.DLL")> Private Shared Function GetShellWindow() As IntPtr End Function <DllImport("USER32.DLL")> Private Shared Function GetWindowText(ByVal hWnd As IntPtr, ByVal lpString As StringBuilder, ByVal nMaxCount As Integer) As Integer End Function <DllImport("USER32.DLL")> Private Shared Function GetWindowTextLength(ByVal hWnd As IntPtr) As Integer End Function <DllImport("user32.dll", SetLastError:=True)> Private Shared Function GetWindowThreadProcessId(ByVal hWnd As IntPtr, <Out()> ByRef lpdwProcessId As UInt32) As UInt32 End Function <DllImport("USER32.DLL")> Private Shared Function IsWindowVisible(ByVal hWnd As IntPtr) As Boolean End Function Private Delegate Function EnumWindowsProc(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Boolean <DllImport("USER32.DLL")> Private Shared Function EnumWindows(ByVal enumFunc As EnumWindowsProc, ByVal lParam As Integer) As Boolean End Function Private Structure inputInfo Dim structSize As Int32 Dim tickCount As Int32 End Structure Private hShellWindow As IntPtr = GetShellWindow() Private dictWindows As New Dictionary(Of IntPtr, String) Private currentProcessID As Integer Public Function GetOpenWindowsFromPID(ByVal processID As Integer) As IDictionary(Of IntPtr, String) Try dictWindows.Clear() currentProcessID = processID EnumWindows(AddressOf enumWindowsInternal, 0) Return dictWindows Catch ex As Exception writeExeption(ex) End Try Return Nothing End Function Private Function enumWindowsInternal(ByVal hWnd As IntPtr, ByVal lParam As Integer) As Boolean Try If hWnd <> hShellWindow Then Dim windowPid As UInt32 If Not IsWindowVisible(hWnd) Then Return True End If Dim length As Integer = GetWindowTextLength(hWnd) If length = 0 Then Return True End If GetWindowThreadProcessId(hWnd, windowPid) If windowPid <> currentProcessID Then Return True End If Dim stringBuilder As New StringBuilder(length) GetWindowText(hWnd, stringBuilder, length + 1) If Not dictWindows.ContainsKey(hWnd) Then dictWindows.Add(hWnd, stringBuilder.ToString) Else Return False End If End If Return True Catch ex As Exception writeExeption(ex) End Try Return False End Function Public Function GetCaption() As String Try Dim Caption As New StringBuilder(256) Dim hWnd As IntPtr = GetForegroundWindow() GetWindowText(hWnd, Caption, Caption.MaxCapacity) Return Caption.ToString() Catch ex As Exception writeExeption(ex) End Try Return "" End Function Public Function getApplications(ByVal currentWindow As String) As String dictWindows.Clear() Dim p() As Process = Process.GetProcesses() Try If p.Count > 0 Then For i = 0 To p.Count - 1 Dim windows As IDictionary(Of IntPtr, String) = GetOpenWindowsFromPID(p(i).Id) For j = 0 To windows.Count - 1 If windows.Values(j).ToString = currentWindow Then 'Console.WriteLine("Caption: " & windows.Values(j).ToString & " If p(i).ProcessName.ToString().Trim() = "Taskmgr" Then If reported_task_manager = False Then record_task_manager() End If Else reported_task_manager = False End If Return p(i).ProcessName.ToString() End If Next Next End If Catch ex As Exception writeExeption(ex) Console.WriteLine("Erro GetApplication: " & ex.Message.ToString) End Try Return "TaskBar" End Function
问题根源与修复方案
1. ArgumentException(重复键)
问题出在enumWindowsInternal函数:当检测到重复hWnd时直接返回False,会终止EnumWindows的枚举流程,且窗口枚举过程中可能因窗口状态变化(如临时窗口创建销毁)导致同一hWnd被多次扫描。另外,返回False终止枚举的逻辑不符合需求——我们需要遍历所有符合条件的窗口,而非遇到重复就停止。
修复代码:
GetWindowText(hWnd, stringBuilder, length + 1) If dictWindows.ContainsKey(hWnd) Then ' 可选:更新窗口标题(若需要实时同步) dictWindows(hWnd) = stringBuilder.ToString() Else dictWindows.Add(hWnd, stringBuilder.ToString()) End If ' 始终返回True,继续枚举剩余窗口 Return True
2. InvalidOperationException(集合已修改)
异常出现在getApplications函数:GetOpenWindowsFromPID返回的是类级全局变量dictWindows的引用,当遍历第一个进程的窗口集合时,下一次调用GetOpenWindowsFromPID会清空并修改这个集合,导致正在进行的枚举操作报错。
修复代码:
在GetOpenWindowsFromPID中返回集合的副本,避免后续操作影响正在遍历的原集合:
Public Function GetOpenWindowsFromPID(ByVal processID As Integer) As IDictionary(Of IntPtr, String) Try dictWindows.Clear() currentProcessID = processID EnumWindows(AddressOf enumWindowsInternal, 0) ' 返回字典副本,隔离原集合的修改操作 Return New Dictionary(Of IntPtr, String)(dictWindows) Catch ex As Exception writeExeption(ex) End Try Return Nothing End Function
额外优化点
- 移除类级别的
dictWindows全局变量,改用局部变量,彻底避免多线程或连续调用时的状态冲突。 - 删除重复的
GetWindowThreadProcessId声明(代码中同时存在Declare和DllImport版本),保留DllImport版本即可。
内容的提问来源于stack exchange,提问作者Luiz
相关产品推荐
相关产品推荐

