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

如何在VB.Net中获取外部ComboBox插入前的原始未排序项?

获取外部ComboBox原始插入顺序的VB.Net实现

首先得明白为什么你现在的方法拿不到原始顺序:如果目标ComboBox设置了CBS_SORT样式,它内部会自动对插入的项排序,CB_GETCOUNT和CB_GETLBTEXT返回的都是排序后的列表,自然看不到最初的插入顺序。

Spy++能做到是因为它捕获了ComboBox收到的插入类消息(比如CB_ADDSTRING、CB_INSERTSTRING),这些消息是项被添加时发送的,记录这些消息的参数就能还原原始顺序。下面是具体的VB.Net实现方案:

核心思路

通过Windows钩子监视目标ComboBox所在线程发送的窗口消息,捕获CB_ADDSTRING和CB_INSERTSTRING消息,读取消息携带的项文本并按插入顺序记录。由于是跨进程操作,需要用到进程内存读取和钩子注入的相关API,这里推荐用EasyHook简化托管代码的跨进程注入(可以通过NuGet直接安装)。

完整代码实现

Imports System.Runtime.InteropServices
Imports EasyHook

Public Class ComboBoxOriginalOrderTracker
    ' ComboBox消息常量
    Private Const CB_ADDSTRING As Integer = &H143
    Private Const CB_INSERTSTRING As Integer = &H14A
    ' 钩子类型:监视发送到窗口的消息
    Private Const WH_CALLWNDPROC As Integer = 4

    ' Windows API声明
    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function GetWindowThreadProcessId(hWnd As IntPtr, ByRef lpdwProcessId As UInteger) As UInteger
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Shared Function OpenProcess(dwDesiredAccess As UInteger, bInheritHandle As Boolean, dwProcessId As UInteger) As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Shared Function ReadProcessMemory(hProcess As IntPtr, lpBaseAddress As IntPtr, lpBuffer As Byte(), dwSize As Integer, ByRef lpNumberOfBytesRead As Integer) As Boolean
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function SetWindowsHookEx(idHook As Integer, lpfn As HookProc, hMod As IntPtr, dwThreadId As UInteger) As IntPtr
    End Function

    <DllImport("user32.dll", SetLastError:=True)>
    Private Shared Function UnhookWindowsHookEx(hhk As IntPtr) As Boolean
    End Function

    <DllImport("user32.dll")>
    Private Shared Function CallNextHookEx(hhk As IntPtr, nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
    End Function

    <DllImport("kernel32.dll", SetLastError:=True)>
    Private Shared Function CloseHandle(hObject As IntPtr) As Boolean
    End Function

    ' 钩子回调委托
    Private Delegate Function HookProc(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr

    ' 钩子句柄与目标对象
    Private hookHandle As IntPtr = IntPtr.Zero
    Private targetComboHwnd As IntPtr
    Private targetProcessHandle As IntPtr = IntPtr.Zero
    ' 存储原始顺序的项列表
    Public OriginalItems As New List(Of String)

    Public Sub New(comboHwnd As IntPtr)
        targetComboHwnd = comboHwnd
        ' 获取目标进程ID和线程ID
        Dim processId As UInteger
        Dim threadId As UInteger = GetWindowThreadProcessId(comboHwnd, processId)
        ' 打开目标进程(获取内存读取权限)
        targetProcessHandle = OpenProcess(&H1F0FFF, False, processId)
        If targetProcessHandle = IntPtr.Zero Then
            Throw New Exception("无法打开目标进程,请确保程序以管理员身份运行")
        End If
        ' 安装线程钩子
        hookHandle = SetWindowsHookEx(WH_CALLWNDPROC, AddressOf HookCallback, IntPtr.Zero, threadId)
        If hookHandle = IntPtr.Zero Then
            Throw New Exception("无法安装钩子,请检查目标线程是否存在")
        End If
    End Sub

    ' 停止跟踪并清理资源
    Public Sub StopTracking()
        If hookHandle <> IntPtr.Zero Then
            UnhookWindowsHookEx(hookHandle)
            hookHandle = IntPtr.Zero
        End If
        If targetProcessHandle <> IntPtr.Zero Then
            CloseHandle(targetProcessHandle)
            targetProcessHandle = IntPtr.Zero
        End If
    End Sub

    ' 钩子回调函数
    Private Function HookCallback(nCode As Integer, wParam As IntPtr, lParam As IntPtr) As IntPtr
        If nCode >= 0 Then
            ' 解析窗口消息结构体
            Dim cwp As CWPSTRUCT = Marshal.PtrToStructure(Of CWPSTRUCT)(lParam)
            ' 过滤目标ComboBox的消息
            If cwp.hWnd = targetComboHwnd Then
                Select Case cwp.message
                    Case CB_ADDSTRING
                        ' 读取目标进程中的字符串
                        Dim itemText = ReadStringFromProcess(cwp.lParam)
                        If Not String.IsNullOrEmpty(itemText) Then
                            OriginalItems.Add(itemText)
                        End If
                    Case CB_INSERTSTRING
                        Dim itemText = ReadStringFromProcess(cwp.lParam)
                        If Not String.IsNullOrEmpty(itemText) Then
                            Dim insertIndex = cwp.wParam.ToInt32()
                            ' 处理插入位置超出当前列表的情况
                            If insertIndex >= OriginalItems.Count Then
                                OriginalItems.Add(itemText)
                            Else
                                OriginalItems.Insert(insertIndex, itemText)
                            End If
                        End If
                End Select
            End If
        End If
        ' 传递消息给下一个钩子
        Return CallNextHookEx(hookHandle, nCode, wParam, lParam)
    End Function

    ' 从目标进程读取Unicode字符串
    Private Function ReadStringFromProcess(strPtr As IntPtr) As String
        If strPtr = IntPtr.Zero Then Return ""
        Dim buffer As New List(Of Byte)()
        Dim bytesRead As Integer
        Dim tempBuffer(1) As Byte ' 每次读取2个字节(Unicode字符)

        Do
            If Not ReadProcessMemory(targetProcessHandle, strPtr + buffer.Count, tempBuffer, 2, bytesRead) Then Exit Do
            If bytesRead < 2 Then Exit Do
            ' 检查是否是字符串终止符(两个0字节)
            If tempBuffer(0) = 0 AndAlso tempBuffer(1) = 0 Then Exit Do
            buffer.AddRange(tempBuffer)
        Loop

        Return If(buffer.Count > 0, System.Text.Encoding.Unicode.GetString(buffer.ToArray()), "")
    End Function

    ' 窗口消息结构体定义
    <StructLayout(LayoutKind.Sequential)>
    Private Structure CWPSTRUCT
        Public lParam As IntPtr
        Public wParam As IntPtr
        Public message As Integer
        Public hWnd As IntPtr
    End Structure
End Class

使用示例

' 假设你已经获取到目标ComboBox的句柄ComboHwnd
Dim tracker As ComboBoxOriginalOrderTracker = Nothing
Try
    tracker = New ComboBoxOriginalOrderTracker(ComboHwnd)
    ' 等待目标程序完成项插入(这里示例等待5秒,实际可根据需求调整)
    System.Threading.Thread.Sleep(5000)
    ' 输出原始顺序的项
    For Each item In tracker.OriginalItems
        Debug.WriteLine($"原始插入项:{item}")
    Next
Finally
    ' 记得清理资源
    tracker?.StopTracking()
End Try

注意事项

  1. 权限要求:跨进程操作需要管理员权限,运行程序时右键选择「以管理员身份运行」。
  2. 时机问题:钩子需要在目标ComboBox插入项之前安装,否则无法捕获已经插入的项。如果目标程序启动时就完成了项插入,你需要在程序启动初期就挂载钩子。
  3. 编码兼容:如果目标程序是ANSI编码(老程序),需要把ReadStringFromProcess中的Encoding.Unicode改成Encoding.Default。

内容的提问来源于stack exchange,提问作者ezio

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.28 07:22:09