Freigeben über


C# - Conseguir un Handler a la Ventana de un Proceso

Avanzado

Cuando se esta jugando con la API de Windows, especialmente con el tema de las ventanas esta función puede resultar de muchísima utilidad. Sin embargo no existe, así que hay que implementarla.

En resumen explicare los pasos necesarios para crear una función GetProcessWindowHandler, la cual devuelve un handler a la ventana principal de un proceso.

Necesitaremos recuperar el handler de la ventana (cuando la encontremos),así que creare una clase que utilizare como LPARAM a algunas funciones de la API, capaz de contener tanto el id del proceso como el handler de la ventana.

 /// <summary>Almacena el ID de proceso y el handler de una ventana</summary> 
private class AuxInfo
{
    public int processID;
    public IntPtr handler;
}

Para lograrlo debemos recurrir a la función EnumWindows, la utilizaremos para recorrer las ventanas existentes en búsqueda de una ventana cuyo id de proceso coincida con el proceso que acabamos de iniciar.

EnumWindows requiere como parámetro un delegado que se ejecutara para las ventanas enumeradas, entonces la función de búsqueda debe tener el signature de EnumWindowsProc, declarado en la API de Windows y que acá lo declararo como un delegado.

 /// <summary>
/// Delegado para hacer de callback
/// </summary>
/// <param name="hwnd" />handler de la ventana
/// <param name="lParam" />paramétro con la informacion necesaria para el proceso
/// <returns>Valor de retorno del proceso</returns>
private delegate bool EnumWindowsProc(IntPtr hwnd, AuxInfo lParam);

Y acá la definición de EnumWindows

 /// <summary>
/// Recorre las ventanas y ejecuta un proceso para cada una de ellas
/// </summary>
/// <param name="lpEnumFunc" />Delegado con el proceso a utilizar para cada ventana
/// <param name="lParam" />paramétro con la informacion necesaria para el proceso
/// <returns>Retorna true si se recorren todas las ventanas, de lo contrario false o segun determine el usuario a trabes del callback</returns>
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, AuxInfo lParam);

Para poder determinar el id de proceso de cada una de las ventanas enumeradas haré uso de GetWindowThreadProcessId:

 /// <summary>
/// Devuelve el ID del proceso al que pertenece el hilo de la ventana
/// </summary>
/// <param name="hwnd" />handler de la ventana
/// <param name="lpdwProcessId" />ID del proceso (parámetro de salida)
/// <returns>ID del Thread que creó la ventana</returns>
[DllImport("user32.dll")]
private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out int lpdwProcessId);

Ya con esta información mi función delegada para encontrar el handler de ventana ( la que se ejecutara por cada ventana hallada por EnumWindows )es esta:

 /// <summary>
/// Obtiene el handler de la ventana asociada a un proceso
/// Este procedimiento es solo de utileria para usarse con EnumWindows 
/// y no deberia ser invocado directamente
/// </summary>
/// <param name="hwnd" />handler de la ventana actual
/// <param name="info" />informacion auxiliar para el proceso
/// <returns>false si encuentra la ventana, true sino</returns>
private static bool _GetProcessWindowHandler(IntPtr hwnd, AuxInfo info)
{
    int processID;
    GetWindowThreadProcessId(hwnd, out processID);

    if (processID == info.processID)
    {
        info.handler = hwnd;
        return false;
    }
    else
    {
        info.handler = IntPtr.Zero;
        return true;
    }
}

Estando ya definida mi función de callback entonces llamaré a EnumWindows y crearé con ella una función GetProcessWindowHandler, la cual como su nombre lo indica será la que usaré para devolver el handler de la ventana del proceso.

Sin embargo el tema no es tan fácil como pareciera a simple vista.

Si utilizo la función para traer un handler a la ventana de un proceso ya abierto no tengo ningún problema, pero si el proceso recién lo estoy lanzando desde mi aplicación, por ejemplo con Process.Start(), se debe esperar a que el sistema operativo cree y muestre por primera vez la ventana, de lo contrario no habrá manera de hallarla con EnumWindows, así que debo llamar a EnumWindows hasta que se cumplan estas dos condiciones:

  • Encontró una ventana asociada al proceso
  • Dicha ventana ya ha sido mostrada por el sistema operativo

Para la primera condición, y de acuerdo a como creamos nuestra función de callback (GetProcessWindowHandler), basta con preguntar si el handler es válido y para la segunda se debe determinar si la ventana de dicho proceso ya ha sido mostrada lo cual lo hacemos con IsWindowVisible:

 /// <summary>
/// Indica si una ventana es o no visible
/// </summary>
/// <param name="hWnd" />handler de la ventana
/// <returns>Indicador de si la v entana es o no visible</returns>
[DllImport("user32.dll")]
private static extern bool IsWindowVisible(IntPtr hWnd);

Así que la función internamente debe tener un proceso iterativo para poder hallar el handler

 /// <summary>
/// Devuelve el handler de la ventana asociada al proceso
/// </summary>
/// <param name="pid" />Id del proceso
/// <returns>handler de la ventana</returns>
public static IntPtr GetProcessWindowHandler(int pid)
{
    //Delegado con el proceso auxiliar de búsqueda
    EnumWindowsProc getHandlerVentana = new EnumWindowsProc(_GetProcessWindowHandler);
    //Informacion auxiliar
    AuxInfo informacion = new AuxInfo();
    informacion.processID = pid;

    /*Repetir bucle hasta que este presente la ventana del proceso
     *(puede que la enumeracion se realice y windows  aún no haya creado 
     *la primera ventana del proceso o bien no la haya hecho visible, 
     *por lo cual se debe repetir el bucle hasta encontrala)*/
    do
    {
        /*Enumerar las ventanas buscando la que coincida con
         *el id de proceso contenido en informacion */
        EnumWindows(getHandlerVentana, informacion);
    } while (informacion.handler == IntPtr.Zero || !IsWindowVisible(informacion.handler));

    return informacion.handler;
}

Bien, he encapsulado la funcionalidad en la clase Win32APITools y el método GetProcessWindowHandler es el único método expuesto, asi que la implementación completa queda así:

 using System;
using System.Runtime.InteropServices;

class Win32APITools
{
    /// <summary>Almacena el ID de proceso y el handler de una ventana</summary> 
    private class AuxInfo
    {
        public int processID;
        public IntPtr handler;
    }

    /// <summary>
    /// Delegado para hacer de callback
    /// </summary>
    /// <param name="hwnd" />handler de la ventana
    /// <param name="lParam" />paramétro con la informacion necesaria para el proceso
    /// <returns>Valor de retorno del proceso</returns>
    private delegate bool EnumWindowsProc(IntPtr hwnd, AuxInfo lParam);

    /// <summary>
    /// Recorre las ventanas y ejecuta un proceso para cada una de ellas
    /// </summary>
    /// <param name="lpEnumFunc" />Delegado con el proceso a utilizar para cada ventana
    /// <param name="lParam" />paramétro con la informacion necesaria para el proceso
    /// <returns>Retorna true si se recorren todas las ventanas, de lo contrario false o segun determine el usuario a trabes del callback</returns>
    [DllImport("user32.dll")]
    private static extern bool EnumWindows(EnumWindowsProc lpEnumFunc, AuxInfo lParam);

    /// <summary>
    /// Devuelve el ID del proceso al que pertenece el hilo de la ventana
    /// </summary>
    /// <param name="hwnd" />handler de la ventana
    /// <param name="lpdwProcessId" />ID del proceso (parámetro de salida)
    /// <returns>ID del Thread que creó la ventana</returns>
    [DllImport("user32.dll")]
    private static extern uint GetWindowThreadProcessId(IntPtr hwnd, out int lpdwProcessId);

    /// <summary>
    /// Indica si una ventana es o no visible
    /// </summary>
    /// <param name="hWnd" />handler de la ventana
    /// <returns>Indicador de si la v entana es o no visible</returns>
    [DllImport("user32.dll")]
    private static extern bool IsWindowVisible(IntPtr hWnd);

    /// <summary>
    /// Obtiene el handler de la ventana asociada a un proceso
    /// Este procedimiento es solo de utileria para usarse con EnumWindows 
    /// y no deberia ser invocado directamente
    /// </summary>
    /// <param name="hwnd" />handler de la ventana actual
    /// <param name="info" />informacion auxiliar para el proceso
    /// <returns>false si encuentra la ventana, true sino</returns>
    private static bool _GetProcessWindowHandler(IntPtr hwnd, AuxInfo info)
    {
        int processID;
        GetWindowThreadProcessId(hwnd, out processID);

        if (processID == info.processID)
        {
            info.handler = hwnd;
            return false;
        }
        else
        {
            info.handler = IntPtr.Zero;
            return true;
        }
    }

    /// <summary>
    /// Devuelve el handler de la ventana asociada al proceso
    /// </summary>
    /// <param name="pid" />Id del proceso
    /// <returns>handler de la ventana</returns>
    public static IntPtr GetProcessWindowHandler(int pid)
    {
        //Delegado con el proceso auxiliar de búsqueda
        EnumWindowsProc getHandlerVentana = new EnumWindowsProc(_GetProcessWindowHandler);
        //Informacion auxiliar
        AuxInfo informacion = new AuxInfo();
        informacion.processID = pid;

        /*Repetir bucle hasta que este presente la ventana del proceso
         *(puede que la enumeracion se realice y windows  aún no haya creado 
         *la primera ventana del proceso o bien no la haya hecho visible, 
         *por lo cual se debe repetir el bucle hasta encontrala)*/
        do
        {
            /*Enumerar las ventanas buscando la que coincida con
             *el id de proceso contenido en informacion */
            EnumWindows(getHandlerVentana, informacion);
        } while (informacion.handler == IntPtr.Zero || !IsWindowVisible(informacion.handler));

        return informacion.handler;
    }
}

Y este es un ejemplo de uso:

 using System;
using System.Diagnostics;

namespace GetProcessWindowHandler
{
    class Program
    {
        static void Main(string[] args)
        {
            Process proc = new Process();
            ProcessStartInfo psi = new ProcessStartInfo("calc.exe");
            proc.StartInfo = psi;

            proc.Start();

            IntPtr handler = Win32APITools.GetProcessWindowHandler(proc.Id);  

            Console.WriteLine("El Handler obtenido para la ventana de este proceso es: {0}", handler);
            Console.ReadLine();
        }
    }
}

Hasta Pronto.