【问题标题】:Get the state of the items in a CheckListBox using SendMessage API使用 SendMessage API 获取 CheckListBox 中项目的状态
【发布时间】:2020-04-13 20:09:17
【问题描述】:

我正在使用一个软件来操作另一个软件,例如软件测试环境,目的是获取一些值并执行一些任务。为此,我使用了 SendMessage API,它在几乎所有 Windows 控件中都运行良好,除了 CheckListBox。 “从”软件是在 Delphi 中创建的,所以我不确定 CheckListBox 是否是标准的 Windows 控件,无论如何,这是来自 MS 的文档:
https://docs.microsoft.com/en-us/windows/win32/controls/list-boxes
在这个控件中,我可以获得项目的数量、文本、选择了哪一个以及许多其他信息,但我无法知道项目是否被选中。我尝试使用“Accessibility Insights for Windows”和 AutoIt 等工具,但没有结果。
关于如何获取 Delphi CheckListBox 中复选框状态的任何想法?
提前致谢。

【问题讨论】:

  • 我假设您已经尝试过LB_GETSELITEMS
  • 是的,这是关于选定的项目(突出显示的行),而不是选中的项目。
  • 我正要写一个答案how to use GetProp to access the ControlOfsXXXXXXXXYYYYYYYY property to get a pointer to the control object in memory and then use ReadProcessMemory to find what you need when我注意到 Delphi 在访问有关已检查项目的信息时有时会使用常规的“项目数据”。看来你可以在那里使用LB_GETITEMDATA
  • 我认为 Delphi 在那里存储了一个 TCheckListBoxDataWrapper 和一个属性 State。当然,这也是一个指向目标进程内存的指针,所以无论如何你都需要ReadProcessMemory 来读取实际数据。
  • Delphi 的TCheckListBox 是一个自定义控件,是标准Win32 ListBox 的包装器,使用所有者绘制的复选框,并在每个列表项的内存中存储状态信息。没有标准的 API 可以直接访问该信息,并且复选框不会暴露给 UI 自动化。即使您可以使用LB_GETITEMDATA 来获取每个列表项的内部TCheckListBoxDataWrapper 指针,您仍然需要使用ReadProcessMemory() 来读取其State 数据,是的。

标签: windows delphi winapi sendmessage


【解决方案1】:

Delphi 将检查的信息存储在TCheckListBoxDataWrapper 对象中。指向该对象的指针存储在复选框项目的常规“项目数据”中。然后,该对象有一个布尔属性 State,您可以在偏移量 8 处找到它。

注意:如果您的程序的特定 Delphi 版本的偏移量与 8 不同,请尝试附近的一些东西 - 4, 12, ... - 它不会走得太远...

要确定某个项目是否被选中,您需要:

  1. 获取指向该项目的TCheckListBoxDataWrapper 的指针。这可以通过发送LB_GETITEMDATA 消息来完成。如果你得到零回来,它也算作未检查。
  2. 由于此指针指向其他进程中的内存而不是您的内存,因此您不能简单地取消引用它。相反,您需要使用ReadProcessMemory。知道State 属性在偏移量8 处,您可以调用ReadProcessMemory(hProcess, itemData + 8, &checked, 1, NULL) 将1 个字节读入变量checked。 (首先需要使用OpenProcess打开目标进程。)

然后您将在checked 中拥有该项目的选中状态! 1 表示选中,0 表示未选中。


如果您以后还需要访问其他内部状态,另一个提示:有一个窗口属性ControlOfsXXXXXXXXYYYYYYYY,其中X 是窗口所有者的HINSTANCE(基地址)(通常是00400000 ) 和Y 是窗口所有者的(十六进制)线程ID(您可以使用GetWindowThreadProcessId 来获取它)。您可以使用GetProp 来获取该属性的值,该属性将是指向控件对象的指针(在此示例中为TCheckListBox 本身)。然后,您可以使用ReadProcessMemory 查看需要的其他数据。您将需要知道偏移量(但您可以使用调试器尝试找出它们)。用相同的 Delphi 版本编译一个测试程序并在那里执行访问这些属性的函数是非常有帮助的,然后您可以调试自己的测试程序并更容易地计算出偏移量。

一种更高级的方法涉及将自定义 DLL(以相同的 Delphi 版本编写)注入目标进程,从而可以更直接地访问(读取和写入)此类数据。我很久以前写过an article about that

【讨论】:

  • 哇,非常有帮助和精确的答案!这正是让它工作的方法!非常感谢!我会将我的代码放在下面以帮助其他人。
  • 嘿@CherryDT,这另一个问题和这个类似,你能检查一下吗? stackoverflow.com/questions/60960254/…
  • 这需要我几个小时才能回答。您必须更深入地了解这一点 - 要么使用调试器找出内存中的结构(基于从 ControlOfs... 属性获得的指针),要么在相同的 Delphi 版本中编写一个 DLL 并注入它然后使用 RTTI 访问相关属性。 (不过,您还必须附加相同的内存管理器,正如我的文章中所解释的那样。)当然,也有一些商业解决方案,例如参见 this
  • 嘿@CherryDT,你是怎么知道偏移量8的?你在哪里找到的?有类似的东西,我可以在其中寻找有关 TcxGrid 控件的线索?
  • 通过谷歌搜索,您可以找到源代码here(在此文件中搜索TCheckListBoxDataWrapper)。任何类实例中的第一条数据都是指向 VTable(偏移量 0)的指针。根据我刚刚链接的代码,之后的下一个字段将是FData,位于偏移量4。下一个字段FState 是我们所追求的。 FData 是 32 位(4 字节)长,因此下一个位置是偏移量 8。FState 本身是一个 1 字节长的枚举,不需要特殊对齐,所以它会偏移 8。
【解决方案2】:

正如@CherryDT 所发布的,这是访问 CheckListBox 状态的方法。我使用 AutoIt 来测试提示,因为此时这是我必须测试它的最快方法。
代码:

;------------------------------------------------------------------------------
;   Retuns the state of the indicated item in the CheckListBox control.
;   Parameter:
;       $iPID: process ID (PID)
;       $hWnd: the handle of the CheckListBox control
;       $iIndex: index of the item in the list (0 based)
;------------------------------------------------------------------------------
Func CtrlListBox_GetState($iPID, $hWnd, $iIndex)
   Local $hProc                             ; Handle of the process.
   Local $pItem                             ; Pointer to the item.
   Local $pData = DllStructCreate("byte")   ; Data structure.
   Local $iQty                              ; Size of data read.
   Local Const $LB_STATE_SHIFT = 8          ; State position in the memory.

   $hProc = _WinAPI_OpenProcess(0x1F0FFF, False, $iPID)     ; 0x1F0FFF = PROCESS_ALL_ACCESS
   $pItem = _GUICtrlListBox_GetItemData($hWnd, $iIndex) + $LB_STATE_SHIFT
   _WinAPI_ReadProcessMemory($hProc, $pItem, DllStructGetPtr($pData), DllStructGetSize($pData), $iQty)

   Return DllStructGetData($pData, 1)
EndFunc

;------------------------------------------------------------------------------
;   Retuns the state of the indicated item in the CheckListBox control.
;   Parameter:
;       $iPID: process ID (PID)
;       $hWnd: the handle of the CheckListBox control
;       $iIndex: index of the item in the list (0 based)
;       $bState: state deseired (true or false)
;------------------------------------------------------------------------------
Func CtrlListBox_SetState($iPID, $hWnd, $iIndex, $bState)
   Local $hProc                             ; Handle of the process.
   Local $pItem                             ; Pointer to the item.
   Local $pData = DllStructCreate("byte")   ; Data structure.
   Local $iQty                              ; Size of data read.
   Local Const $LB_STATE_SHIFT = 8          ; State position in the memory.

   if($bState <> 0) Then
      DllStructSetData($pData, 1, True)
   Else
      DllStructSetData($pData, 1, False)
   EndIf

   $hProc = _WinAPI_OpenProcess(0x1F0FFF, False, $iPID)     ; 0x1F0FFF = PROCESS_ALL_ACCESS
   $pItem = _GUICtrlListBox_GetItemData($hWnd, $iIndex) + $LB_STATE_SHIFT
   _WinAPI_WriteProcessMemory($hProc, $pItem, DllStructGetPtr($pData), DllStructGetSize($pData), $iQty)

   Return DllStructGetData($pData, 1)
EndFunc

【讨论】:

    【解决方案3】:

    我在尝试获取用 VB.Net 开发的 UI 的 CheckedListBox 中的复选框状态时遇到了类似的问题。我想获取状态(检查/取消检查),以便我可以在 Python + Winium 中从我的测试自动化框架开发人员那里执行所需的事件。这尤其需要不要意外取消选中已选中的项目。

    在研究过程中,我了解到使用 Winium 并不容易,因此我在 UI 上添加了一个按钮,用于在检查所需复选框之前重置所有复选框。

    希望对您有所帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-03-09
      • 1970-01-01
      • 2017-07-07
      • 2020-05-16
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多