КОТ++
Прикладное программирование
Системное программирование
Программирование микроконтроллеров

Для некоторых людей программирование является такой же внутренней потребностью, подобно тому, как коровы дают молоко, или писатели стремятся писать. (с) Николай Безруков

Поиск по тегам

Опубликовано
Комментарии 0

Разрабатывая очередную программу, столкнулся с рядом проблем, которые таит в себе WinApi. Передо мной стояла задача написать программу, которая будет выполнять действия в окнах за юзера в определённый момент. Программа должна научиться кликать мышкой и крутить колёсиком в чужих окнах. В этом посте постараюсь изложить то, как я решил задачу.

Я не буду здесь выкладывать сигнатуры всех используемых мной функций, т.к. это можно найти на msdn, наоборот я покажу какие функции, в какой ситуации лучше использовать и как это лучше сделать. Возможно, кому-нибудь пригодится данный опыт.

События мыши это по сути сообщения, которые пользователь посылает в систему. Эти события можно сымитировать программно. Для этого есть две замечательные функции из WinApi SendMessage и PostMessage. Задача у них одна – отправить сообщение в целевое окно. Различие в том, что первая ждёт пока целевое окно обработает отправленное сообщение, вторая не дожидается этого, она просто отсылает это сообщение больше ни о чём не беспокоясь. Разберём теперь как же найти это целевое окно. Я считаю, что хороший способ – найти окно по его заголовку. Для такого поиска есть функция WinApi FindWindow. Для написания программы я использовал c#, и как оказалось в нём достаточно функционала найти хэндл окна по заголовку.
//получаем массив всех процессов системы
Process[] procs = Process.GetProcesses();
//перебираем полученный массив
foreach (Process p in procs)
{
        //если заголовок окна содержит строку поиска
        if (p.MainWindowTitle.IndexOf(TITLE_SEARCH) != -1)
        {
                //то запоминаем хэндл окна
                IntPtr handle = p.MainWindowHandle;
        }
}
Зная хэндл можем отправить клик мыши в это окно. Здесь уже средствами .NET не обойтись, поэтому начинаем юзать WinApi функции. Чтобы подключить функцию SendMessage к c# программе нужно в код прописать следующее:
[DllImport("User32.dll")]
public static extern Int32 SendMessage(IntPtr hWnd, int Msg, IntPtr wParam, IntPtr lParam);
hWnd – хэндл окна, который мы нашли выше,
Msg – сообщение (нажатие ЛКМ = 0x201, отпускание ЛКМ = 0x202, вращение колёсика = 0x020A,
wParam – 16-битный параметр функции (word параметр), будем использовать для передачи значения на сколько сдвинулось колёсико мыши,
lParam – 32-битный параметр функции (long параметр), будем использовать для передачи координат мыши.
Но как же затолкать несколько значений всего в два параметра? Для таких действий есть такое извращение как помещение разных значений в старшие и в младшие биты. Для таких фокусов можно использовать эту функцию:
public static IntPtr MakeParam(int low, int hight)
{
       return (IntPtr)((low & 0xFFFF) | (hight << 16));
}
Теперь мы можем вызвать эту функцию и отправить сообщение клика окну. Клик мыши – это наведение курсора, нажатие клавиши мыши и отпускание. Поэтому надо отправить три сообщения.
SendMessage(handle, WM_MOUSEMOVE, (IntPtr)0, MakeParam(x, y));
SendMessage(handle, WM_LBUTTONDOWN, (IntPtr)0, MakeParam(x, y));                    
SendMessage(handle, WM_LBUTTONUP, (IntPtr)0, MakeParam(x, y));
Для прокрутки колёсика:
SendMessage(handle, WM_MOUSEMOVE, (IntPtr)0, MakeParam(x, y));
SendMessage(handle, WM_MOUSEWHEEL, MakeParam(0, delta), MakeParam(x, y));
Где delta значение на которое должно быть прокручено колёсико.
Теперь программа должна уметь кликать в чужие окна и нажимать там кнопки. НО программа кликает, а вот кнопки то не нажимаются! Может ошибка в координатах? Нет, ошибка в том, что сообщение было отправлено не туда. Кнопка это не элемент главного окна, это его часть и в системе она числится как дочернее окно. По сути все элементы окна это дочерние окна, если, конечно, можно так выразиться.

Элементы окна можно посмотреть с помощью программы Spy++. Запустить её можно через Visual Studio, Сервис -> Spy++, хочу обратить внимание, что там присутствуют две программы (Spy++ и Spy++ (x64)), так вот даже в 64 битной системе следует запускать Spy++, т.к. Spy++ (x64) почему-то ограничен в функционале (возможно такой глюк присутствует только у меня). Вот пример работы программы для поиска окон (для поиска нужно нажать на мишень и передвигать её по экрану).


Скриншот показывающий, что контролы окна это отдельные элементы и тоже имеют свой хэндл.

Также с помощью этой программы можно просматривать все сообщения, которые получает то или иное окно. Для этого нужно перейти в запись сообщений (ctrl+M). Кстати не в любом окне каждый контрол является отдельным элементом. Например, если графика отрисована с помощью OpenGL, то само собой кнопки и другие контролы не могут быть отдельными элементами, т.к. просто отрисованы в графическом окне средствами GPU и не имеют своего хэндла. Всё это можно проверить через Spy++. В заключение хочу сказать, что WinApi и Spy++ хорошие помощники в написании программы для автоматизации каких-либо действий пользователя в конкретных программах.

Теги , , ,
Автор