Введение

Всем привет! В рамках разработки приложения на C# под Windows в узкоспециализированных задачах нам может потребоваться отслеживание активного окна и его process Id, так давайте напишем его!

Ингредиенты

Начнем с того, что нам нужно написать нашу утилиту не через консольное приложение, а именно в том, где есть цикл сообщений, т.е WinForm

The client thread that calls **SetWinEventHook** must have a message loop in order to receive events.

Самой главной функцией будет SetWinEventHook. Вкратце он устанавливает глобальный хук события, который позволяет приложению получать уведомления о различных событиях системы, таких как активация окон.

Вызовем ее с помощью технологии Platform Invocation Services (P/Invoke)

Поскольку нам нужно мониторить все окна, из всех потоков и процессов, то мы должны указать idProcess и idThread как 0.

Если параметр _idProcess_ не равен нулю, а _idThread_ равен нулю, функция перехватчика получает указанные события из всех потоков в этом процессе. Если параметр _idProcess_ равен нулю, а _idThread_ — ненулевому, функция перехватчика получает указанные события только из потока, заданного _idThread_. Если оба значения равны нулю, функция-перехватчик получает указанные события из всех потоков и процессов.

Рассмотрим внимательнее данный раздел, в котором описаны события, генерируемые операционной системой и серверными приложениями. Из массы констант нам потребуется 0x0003(т.е 3) означающая EVENT_SYSTEM_FOREGROUND

Окно переднего плана изменилось. Система отправляет это событие, даже если окно переднего плана изменилось на другое окно в том же потоке. Серверные приложения никогда не отправляют данное событие.

Так же мы должны указать что наш перехватчик должен быть ВНЕ контекста, а не В,подробнее

Следовательно должны выбрать WINEVENT_OUTOFCONTEXT.

Значение константы в справочнике я не смог найти, однако наткнулся на нее в интересном посте со схожей тематикой

Благодарен вам. Тык. Личный блог ManHunter'а

Поскольку у нас WINEVENT_OUTOFCONTEXT, следовательно, hmodWinEventProc должен быть IntPtr.Zero

Последнее pfnWinEventProc является ссылкой на делегат напишем его по образу и подобию

public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

Все что нам остается получить process id и имя процесса с помощью этой функции

Теперь напишем логику обработки события когда окно поменялось

public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        uint pid;
        GetWindowThreadProcessId(hwnd, out pid);

        string processName = Process.GetProcessById((int)pid).ProcessName;

        OnActiveWindowChanged(hwnd, (int)pid, processName);
    }
    
protected virtual void OnActiveWindowChanged(IntPtr hwnd, int pid, string processName)
    {
        ActiveWindowChanged?.Invoke(this, new ActiveWindowChangedEventArgs { Hwnd = hwnd, ProcessId = pid, ProcessName = processName });
    }
    

сделаем публичное событие

public event EventHandler<ActiveWindowChangedEventArgs> ActiveWindowChanged;

Оформим в отдельный класс

public class ActiveWindowChangedEventArgs : EventArgs
{
    public IntPtr Hwnd { get; set; }
    public int ProcessId { get; set; }
    public string ProcessName { get; set; }
}

public class ActiveWindowTracker
{
    [DllImport("user32.dll")]
    public static extern IntPtr SetWinEventHook(uint eventMin, uint eventMax, IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    [DllImport("user32.dll", SetLastError = true)]
    public static extern uint GetWindowThreadProcessId(IntPtr hWnd, out uint lpdwProcessId);

    private const uint WINEVENT_OUTOFCONTEXT = 0;
    private const uint EVENT_SYSTEM_FOREGROUND = 3;

    public delegate void WinEventDelegate(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);

    public event EventHandler<ActiveWindowChangedEventArgs> ActiveWindowChanged;

    // Сохраняем ссылку на делегат, чтобы предотвратить его удаление сборщиком мусора
    private WinEventDelegate dele;
    private readonly Thread _threadSafe;

    public ActiveWindowTracker()
    {
        dele = new WinEventDelegate(WinEventProc);
        // Запускаем SetWinEventHook в отдельном потоке
        var newDele = new Thread(() =>
         {
             SetWinEventHook(EVENT_SYSTEM_FOREGROUND, EVENT_SYSTEM_FOREGROUND, IntPtr.Zero, dele, 0, 0, WINEVENT_OUTOFCONTEXT);
             
             // Запускаем цикл обработки сообщений, чтобы предотвратить завершение потока
             System.Windows.Forms.Application.Run();

         });
        newDele.Start();
        _threadSafe = newDele;
    }

    public void WinEventProc(IntPtr hWinEventHook, uint eventType, IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
        uint pid;
        GetWindowThreadProcessId(hwnd, out pid);

        string processName = Process.GetProcessById((int)pid).ProcessName;

        OnActiveWindowChanged(hwnd, (int)pid, processName);
    }

    protected virtual void OnActiveWindowChanged(IntPtr hwnd, int pid, string processName)
    {
        ActiveWindowChanged?.Invoke(this, new ActiveWindowChangedEventArgs { Hwnd = hwnd, ProcessId = pid, ProcessName = processName });
    }
}

Последний штрих, вызовем наш класс, запустив задачу.

internal static class Program
    {
        /// <summary>
        /// The main entry point for the application.
        /// </summary>
        [STAThread]
        static void Main()
        {
            Task.Run(() => {
                ActiveWindowTracker tracker = new ActiveWindowTracker();
                tracker.ActiveWindowChanged += (sender, e) => Console.WriteLine($"Thread: {System.Threading.Thread.CurrentThread}Window activated: {e.Hwnd}, PID: {e.ProcessId}, Process Name: {e.ProcessName}");
            });
            
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            //Application.Run(new Form1());
        }
    }

Давайте запустим и оценим наш результат. Надеюсь, пост был полезен и познавательным для вас. А на сегодня все, до новых встреч!