You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.05 13:00:50