1

Тема: AHK: Заголовки колонок ListView из другого приложения

Заголовочный контрол в ListView — это окно класса SysHeader32. Для получения информации о заголовках он поддерживает сообщение HDM_GETITEM. Однако в этом сообщении требуется указать адрес, куда он должен поместить запрошенную информацию, и так как контрол находится в другом процессе по отношению к скрипту, подсовывать ему адреса переменных скрипта бессмысленно. Подробнее см. в теме AHK: Доступ к памяти других процессов.

Пример ниже читает текст заголовков на вкладке "Процессы" в окне Диспетчера задач. С помощью API-функции VirtualAllocEx выделяется память в адресном пространстве процесса под структуру HDITEM и строку заголовка. Потом строка читается оттуда через ReadProcessMemory. HDITEM сначала создаётся в самом скрипте, поскольку нужно заполнить некоторые её поля и я счёл, что проще сделать это в скрипте и потом скопировать структуру в память процесса одним вызовом WriteProcessMemory, нежели чем писать туда каждое поле по-отдельности.

If not WinExist("Диспетчер задач Windows") {
  Run, Taskmgr.exe
  WinWait, Диспетчер задач Windows
}

; Переключиться на вкладку "Процессы" (взято из справки).
SendMessage, 0x1330, 1,, SysTabControl321  ; 0x1330 is TCM_SETCURFOCUS.
Sleep, 0
SendMessage, 0x130C, 1,, SysTabControl321  ; 0x130C is TCM_SETCURSEL.

ControlGet, hwndHeader, Hwnd,, SysHeader321   ; Хэндл заголовочного контрола.

Headers := HeaderGetText(hwndHeader)  ; Список заголовков.

MsgBox, % Headers


; ======== Функция, возвращающая список заголовков =======

HeaderGetText(hWnd) 
{
  MaxName := 100     ; Максимальная длина заголовка.
  Delimiter := "`n"  ; Разделитель заголовков в списке.

  PROCESS_VM_OPERATION := 0x8, PROCESS_VM_READ := 0x10
  PROCESS_VM_WRITE := 0x20,    MEM_COMMIT := 0x1000
  MEM_DECOMMIT := 0x4000,      PAGE_READWRITE = 0x4
  HDI_TEXT := 0x2,             HDM_GETITEMCOUNT := 0x1200
  HDM_GETITEMA := 0x1203

  VarSetCapacity(Buf, MaxName, 0)  ; Буфер под заголовок.
  VarSetCapacity(hdi, 48, 0)       ; Структура HDITEM, её размер 48 байт.
                                   ; В неё будет скидываться информация о каждом
                                   ; заголовке в ответ на сообщение HDM_GETITEM.

  ; PID процесса, создавшего заголовочный контрол.
  DllCall("GetWindowThreadProcessId", "uint",   hWnd
                                    , "uint *", PID)

  hProcess := DllCall("OpenProcess", "uint", PROCESS_VM_READ
                                           | PROCESS_VM_WRITE
                                           | PROCESS_VM_OPERATION
                                   , "int", FALSE
                                   , "uint", PID)
  If (hProcess = 0) {
    MsgBox, Не удалось открыть процесс.
    Return
  }

  ; Выделение памяти в адресном пространстве процесса
  ; под структуру HDITEM и строку заголовка.
  phdi := DllCall("VirtualAllocEx", "uint", hProcess
                                  , "uint", 0
                                  , "uint", MaxName + 48
                                  , "uint", MEM_COMMIT
                                  , "uint", PAGE_READWRITE)
  If (phdi = 0) {
    MsgBox, Ошибка выделения памяти в процессе.
    Goto, Close
  }

  ; Запись нужных значений в структуру.
  NumPut(HDI_TEXT, hdi), NumPut(phdi+48, hdi, 8), NumPut(MaxName, hdi, 16)

  ; Структура копируется в память процесса.
  Ret := DllCall("WriteProcessMemory", "uint", hProcess
                                     , "uint", phdi
                                     , "uint", &hdi
                                     , "uint", 48
                                     , "uint", 0)
  If (Ret = 0) {
    MsgBox, Ошибка записи в память процесса.
    Goto, Free
  }

  ; Запрос количества заголовков.
  Count := DllCall("SendMessageA", "uint", hWnd
                                 , "uint", HDM_GETITEMCOUNT
                                 , "uint", 0
                                 , "uint", 0)
  If (Count < 0) {
    MsgBox, Ошибка определения количества заголовков.
    Goto, Free
  }

  Loop, % Count  ; Запрос информации о каждом заголовке.
  {
    DllCall("SendMessageA", "uint", hWnd
                          , "uint", HDM_GETITEMA
                          , "uint", A_Index - 1
                          , "uint", phdi)

    ; Заголовок копируется из памяти процесса в местный буфер.
    Ret := DllCall("ReadProcessMemory", "uint", hProcess
                                      , "uint", phdi+48
                                      , "uint", &Buf
                                      , "uint", MaxName
                                      , "uint", 0)
    If (Ret = 0) {
      MsgBox, Ошибка чтения из памяти процесса.
      Goto, Free
    }

    VarSetCapacity(Buf, -1)  ; Обновление переменной.
    Headers .= Buf . (A_Index != Count ? Delimiter : "")
  }

  Free:  ; Освобождение памяти в процессе.
  DllCall("VirtualFreeEx", "uint", hProcess
                         , "uint", phdi
                         , "uint", MaxName+48
                         , "uint", MEM_DECOMMIT)

  Close:  ; Освобождение хэндла процесса.
  DllCall("CloseHandle", "uint", hProcess)

  Return Headers
}