Управление другим приложением из своей программы

Под управлением сторонним приложением я подразумеваю некие действия своей программы, эмулирующие действия живого человека. Например, нажатия на кнопки в диалоговых окнах. Попробуем сделать это на примере лоадера для популярного файлового менеджера Total Commander. Кто пользуется им, тот знает, что единственное различие между полной и незарегистрированной версией в том, что триалка при запуске показывает наг-скрин с предложением купить программу или нажать для продолжения одну из трех кнопок с цифрами. Конечно, кнопку можно нажать самостоятельно, но можно доверить это лоадеру.


Наг-скрин Total Commander

Чтобы работать с содержимым окна, сперва надо узнать его хэндл. Проще всего воспользоваться функцией FindWindow, указав в качестве параметра наименование класса наг-скрина. Его можно посмотреть при помощи моей программы WinDowzer или любой другой аналогичной программы. Противное окно имеет название класса TNASTYNAGSCREEN, первый шаг сделан. Теперь нам надо узнать хэндл нужной кнопки, для этого нужно изучить все связи родительских и дочерних объектов окна.

Иерархический список хэндлов всех элементов нужного окна можно получить при помощи последовательного вызова функции FindWindowEx или же рекурсивного вызова функции EnumChildWindows. Вот что у меня получилось:
TNASTYNAGSCREEN - Total Commander
  TNotebook
    TPage - NagPage
      TPanel
        TPanel - 3
        TButton - &3
        TButton - &2
        TButton - &1
      TPanel - 3
      TButton - &3
      TButton - &2
      TButton - &1
      TButton - &Registration info
      TButton - Program &information
    TPanel
      TPanel - 3
      TButton - &3
      TButton - &2
      TButton - &1
    TPanel - 3
    TButton - &3
    TButton - &2
    TButton - &1
    TButton - &Registration info
    TButton - Program &information
    TPage - StartupPage
      TPanel
    TPanel
    TPage - UserPage
      TButton - &Update Information
    TButton - &Update Information
  TPage - NagPage
    TPanel
      TPanel - 3
      TButton - &3
      TButton - &2
      TButton - &1
    TPanel - 3
    TButton - &3
    TButton - &2
    TButton - &1
    TButton - &Registration info
    TButton - Program &information
  TPanel
    TPanel - 3
    TButton - &3
    TButton - &2
    TButton - &1
  TPanel - 3
  TButton - &3
  TButton - &2
  TButton - &1
  TButton - &Registration info
  TButton - Program &information
  TPage - StartupPage
    TPanel
  TPanel
  TPage - UserPage
    TButton - &Update Information
  TButton - &Update Information

В списке для удобства я записал название класса и текст дочерних окон. Казалось бы, в момент перебора надо просто "нажать" нужную кнопку. Но здесь не все так просто, так как каждый раз выбирается случайный номер кнопки, и если нажать неправильную, то Total Commander просто завершит работу. Значит нам еще надо узнать правильный номер кнопки, прежде чем "нажать" на нее. Обратите внимание, что номер кнопки повторяется в форме несколько раз, и длина строки (то есть цифры) - всегда один символ. Облегчим себе задачу и рекурсивно переберем все дочерние элементы наг-скрина, а строку длиной один символ будем считать правильным номером.
; Секция данных
button   db '&',0,0
buff     rb 3
 
; Секция кода
        ...
        invoke  EnumChildWindows,[nag],FindNastyNumber,NULL
        ...
 
proc FindNastyNumber hwnd:dword,lParam:dword
        mov     [buff],0
        invoke  GetWindowText,[hwnd],buff,100
        invoke  lstrlen,buff
        cmp     eax,1
        jne     @f
        invoke  lstrcpy,button+1,buff
@@:
        invoke  EnumChildWindows,[hwnd],FindNastyNumber,NULL
 
        mov     eax,1
        ret
endp

Нужный нам номер в виде строки теперь записан в переменную button, причем именно так, как он написан на кнопке, то есть "&3". Нам осталось найти хэндл самой кнопки и "нажать" на нее. Воспользуемся точно таким же трюком, рекурсивно пройдемся по всем дочерним элементам и кликнем по всем найденным элементам, текст на которых совпадает с искомым номером кнопки.

Лирическое отступление. Наиболее правильная эмуляция клика на каком-либо элементе окна выглядит следующим образом:
        invoke  SendMessage,[hwnd],WM_SETFOCUS,NULL,NULL
        invoke  SendMessage,[hwnd],WM_LBUTTONDOWN,MK_LBUTTON,NULL
        invoke  SendMessage,[hwnd],WM_LBUTTONUP,MK_LBUTTON,NULL

Возвращаемся к перебору дочерних элементов наг-скрина.
        ...
        invoke  EnumChildWindows,[nag],ClickNastyButton,NULL
        ...
 
; Рекурсивная функция перебора всех дочерних элементов
proc ClickNastyButton hwnd:dword,lParam:dword
        mov     [buff],0
        ; Прочитать текст дочернего элемента
        invoke  GetWindowText,[hwnd],buff,3
        ; Это текст искомой кнопки?
        invoke  lstrcmp,buff,button
        or      eax,eax
        jnz     @f
 
        ; Эмулировать нажатие на кнопку
        invoke  SendMessage,[hwnd],WM_SETFOCUS,NULL,NULL
        invoke  SendMessage,[hwnd],WM_LBUTTONDOWN,MK_LBUTTON,NULL
        invoke  SendMessage,[hwnd],WM_LBUTTONUP,MK_LBUTTON,NULL
@@:
        invoke  EnumChildWindows,[hwnd],ClickNastyButton,NULL
 
        mov     eax,1
        ret
endp

Вот и все. Осталось приделать к лоадеру функцию запуска программы, чтобы наг-скрин закрывался сразу же после старта Total Commander, но это уже к делу не относится. В приложении пример программы, находящей и закрывающей наг-скрин уже запущенного Total Commander.
Добавлено: 28 Сентября 2013 10:07:12 Добавил: Андрей Ковальчук Нравится 0
Добавить
Комментарии:
Нету комментариев для вывода...