sábado, 12 de diciembre de 2009

Cómo obtener lista de ventanas mediante API´s

Para obtener los manejadores o handles delas ventanas abiertas se puede hacer llamando a la función GetWindow, pero es mas seguro hacerlo mediante la función EnumWindows ya que GetWindow puede refenciar a un handle de una ventana ya destruida o quedarse en un loop`o ciclo infinito, es fácil darse cuenta de esto último mediante el debugger.
La funcion EnumWindows esta contenida en la librería user32.dll y su sintaxis es la siguiente:
BOOL EnumWindows(
WNDENUMPROC lpEnumFunc,
LPARAM lParam
);


El primer parámetro es un puntero a una función callback.
El segundo parámetro de la función es un valor definido por la aplicación; es decir que puedo mandar cualquier parámetro que me sea necesario usar en la función callback.


Para el primer parámetro debemos declarar un delegado. Antes de hacerlo veamos como debe ser la función callback(EnumWindowsProc):
BOOL CALLBACK EnumWindowsProc(
HWND hwnd,
LPARAM lParam
);

El primer parámetro es el handle de la ventana, lo recibirá de su llamador,la función EnumWindows.
El segundo parámetro es un valor definido por la aplicación,lo recibirá de su llamador,la función EnumWindows.
Debe retornar TRUE para seguir enumerando, de lo contrario no lo hará.

Antes es necesario saber que para poder acceder a las API's es necesario agregar la directiva:
using System.Runtime.InteropServices;

Entonces declaramos el delegado y de una vez la función EnumWindows:

//declaro el delegado para la función callback
private delegate bool EnumWindowsCallback(IntPtr hwnd, IntPtr lParam);

//Declaro la función EnumWindows
private static extern bool EnumWindows(EnumWindowsCallback Callback, IntPtr lParam);


Ahora creamos el método al que apuntará el delegado EnumWindowsCallback y El método público que llamará a la función EnumWindows :



//Metodo al que apunta el delegado EnumWinodwsCallback
private static bool Callback(IntPtr hwnd, IntPtr lParam)
{
hwndList.Add(hwnd);

return true; //continua la enumeración
}

//aqui se guradara la lista de handle's
private static List<IntPtr> hwndList;

public static List<IntPtr> GetWindowsList()
{
hwndList = new List<IntPtr>();

EnumWindowsCallback MyCallback = Callback;

bool val = EnumWindows(MyCallback, IntPtr.Zero);

/*retorno la lista de handle's*/
return hwndList;

}

Se recuperará todos los handle's de todas las ventanas abiertas, que son muchas.Si lo que se quiere es recuperar sólo las ventanas con borde y visibles -que en su mayoría son las que tienen los programas o aplicaciones mas comunes y de uso diario- se debe llamar a la función GetWindowLong para saber los estilos de la ventana; es decir, si tiene borde y si es visible, entre otros valores que se pueden consultar.
La función GetWindowLong recupera información de una ventana especificada.
Su sintaxis:
LONG GetWindowLong(
HWND hWnd,
int nIndex

);

El primer parámetro es el handle una ventana especificada, de la que se quiere saber su estilo.
El segundo parámetro sera el valor de GWL_STYLE, pues para este ejemplo es necesario.
En esta página se pueden ver las los valores de sus constantes.
El msdn dice que para que nuestro código sea compatible con 32bits y 64bits debemos llamar a la función GetWindowLongPtr que es prácticamente lo mismo que GetWindowLong -en su declaración-.Sin embargo, en Windows Vista Ultimate de 32bits la función GetWindowLonPtr no funciona, no existe.En cambio GetWindowLongA corre sin problemas en el S.O mencionado.Acerca de ésta dice el msdn que provee un comportamiento mas consistente entre los S.O's Windows. Así que para este ejemplo se llamara a la funcion GetWindowLongA, por lo que la función callback quedará:


//El valor standard de GWL_STYLE es -16
static readonly int GWL_STYLE = -16;
static readonly ulong WS_VISIBLE = 0x10000000;
static readonly ulong WS_BORDER = 0x00800000;
static readonly ulong TARGETWINDOW = WS_BORDER | WS_VISIBLE;

//Declaro la funcion GetWindowLongA
[DllImport("user32.dll")]
private static extern ulong GetWindowLongA(IntPtr hWnd, int nIndex);

//Metodo al que apunta el delegado EnumWinodwsCallback
private static bool Callback(IntPtr hwnd, IntPtr lParam)
{
//agrego ventanas visibles con borde
if ((GetWindowLongA(hwnd, GWL_STYLE) & TARGETWINDOW) == TARGETWINDOW)
{

hwndList.Add(hwnd);
}

return true; //continua la enumeración
}


En
if ((GetWindowLongA(hwnd, GWL_STYLE) & TARGETWINDOW) == TARGETWINDOW)
{

hwndList.Add(hwnd);
}

le decimos si la ventana tiene bordes y es visible que agregue a la lista genérica hwndList.

Para hacer mas intersante la aplicación podemos recuperar también los titulos de las ventanas cuyos handle's hemos recuperado. Esto lo hacemos mediante la función GetWindowText contenida en la librería user32.dll :


/*Declaro la función GetWindowText*/

/// <summary>
/// Recupera el título de un
a ventana(de la barra de título), si es que tiene.
/// </summary>
/// <param name="hWnd">Handle de la ventana de la que se quiere recuperar el título.</param>
/// <param name="lpString">[out]Puntero al buffer que recibe el texto.Si el texto es
/// más largo que el buffer será truncado y terminado con caracter NULL.</param>
/// <param name="nMaxCount">Especifica el maximo´número de caracteres a escribir en
/// el buffer</param>
[DllImport("user32.dll")]
private static extern void GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);


Para que se vea mas ordenado en una clase estática pondré las llamadas a las API's y mostraré en un listbox(WPF) los titulos de ventanas obtenidos.
Clase APICalls.cs :


using System;
using System.Collections.Generic;
using System.Text;

//Requerido para las API´s:
using System.Runtime.InteropServices;

namespace GettingWindows
{
public static class ApiCalls
{
//aqui se guradara la lista de handle´s
private static List<IntPtr> hwndList;

//El valor standard de GWL_STYLE
static readonly int GWL_STYLE = -16;
static readonly ulong WS_VISIBLE = 0x10000000;
static readonly ulong WS_BORDER = 0x00800000;
static readonly ulong TARGETWINDOW = WS_BORDER | WS_VISIBLE;


/// <summary>
/// Enumera todas las ventanas de la pantalla.Para eso llama a la función
/// Callback.La funcion continua llamandose hasta
/// que EnumWindowsCallback retorne false.
/// </summary>
/// <param name="lpEnumFunc">Puntero a la funcion EnumWindowsCallback</param>
/// <param name="lParam"></param>
/// <returns></returns>
[DllImport("user32.dll")]
private static extern bool EnumWindows(EnumWindowsCallback Callback, IntPtr lParam);
/// <summary>
/// Defino EnumWindowsCallback a traves de un delegado.
/// Ver el metodo privado estatico Callback.
/// </summary>
/// <param name="hwnd">Handle de la ventana top-level que esta enumerando </param>
/// <param name="lParam"></param>
/// <returns>Para continuar enumeando debe retornar TRUE</returns>
private delegate bool EnumWindowsCallback(IntPtr hwnd, IntPtr lParam);

/// <summary>
/// Recupera la información de una ventana especifica
/// </summary>
/// <param name="hWnd">Handle a la ventana y-indirectamente- a la clase a la cual
/// pertenece la ventana.</param>
/// <param name="nIndex"></param>
/// <returns>retorna cero(0) si la funcion falla</returns>
[DllImport("user32.dll")]
private static extern ulong GetWindowLongA(IntPtr hWnd, int nIndex);

/***/
/// <summary>
/// Recupera el título de una ventana(de la barra de título), si es que tiene.
/// </summary>
/// <param name="hWnd">Handle de la ventana de la que se quiere recuperar el título.</param>
/// <param name="lpString">[out]Puntero al buffer que recibe el texto.Si el texto es
/// más largo que el buffer será truncado y terminado con caracter NULL.</param>
/// <param name="nMaxCount">Especifica el maximo número de caracteres a escribir en
/// el buffer</param>
[DllImport("user32.dll")]
private static extern void GetWindowText(IntPtr hWnd, StringBuilder lpString, int nMaxCount);


/**************************************************************************************/

//Metodo al que apunta el delegado EnumWinodwsCallback
private static bool Callback(IntPtr hwnd, IntPtr lParam)
{
//agrego ventanas visibles con borde
if ((GetWindowLongA(hwnd, GWL_STYLE) & TARGETWINDOW) == TARGETWINDOW)
{

hwndList.Add(hwnd);
}

return true; //continua la enumeración
}



public static List<string> GetWindowsList()
{
List<string> wndTitles = new List<string>() ;
StringBuilder sb = new StringBuilder(256);

hwndList = new List<IntPtr>();

EnumWindowsCallback MyCallback = Callback;

EnumWindows(MyCallback, IntPtr.Zero);

/*ya llena la lista de handle´s obtengo el titulo de cada ventana*/
foreach (IntPtr h in hwndList)
{
GetWindowText(h, sb, sb.Capacity);

wndTitles.Add(sb.ToString());
}

/*retorno la lista de titulos de ventanas*/
return wndTitles;

}
}
}

Código de la ventana MainWindow.xaml(en XAML):


<Window x:Class="GettingWindows.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="363" Width="322">
<Grid>
<Button Height="27" Margin="26,29,133,0" Name="btnGetList" VerticalAlignment="Top" Click="btnGetList_Click">Mostrar lista de ventanas</Button>
<ListBox Margin="26,80,19,18" Name="lbxList" />
</Grid>
</Window>

Código de MainWindow.cs :

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Data;
using System.Windows.Documents;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Imaging;
using System.Windows.Shapes;

namespace GettingWindows
{
/// <summary>
/// Interaction logic for MainWindow.xaml
/// </summary>
public partial class MainWindow : Window
{
public MainWindow()
{
InitializeComponent();
}

private void btnGetList_Click(object sender, RoutedEventArgs e)
{
lbxList.ItemsSource = ApiCalls.GetWindowsList();

}
}
}


Una captura:

No hay comentarios:

Publicar un comentario