Manejar Eventos del Sistema Utilizando Código Administrado (C#, Vb.NET)

Introducción

Manejar eventos del sistema (hooks) sin una librería o una librería nativa (C ó C++) es problemático. ¿Cómo hacerlo desde código administrado sin ninguna librería? En resumen utilizando SetWinEventHook (no SetWinEventHookEx) con el flag WINEVENT_OUTOFCONTEXT.

Entorno

Un hook tradicional requiere crear una función o un método. Adicionalmente este método o función debe “ser mapeada en el espacio de direcciones del proceso que genera el evento”. Lo que significa que el método debe ser un método residente en una biblioteca nativa (DLL en C++). La razón de esto es que el sistema cargará y mapeará la ubicación de la DLL y requiere esto para encontrar el puntero de la función de tu método.

Entonces ¿cómo podemos hacer si parece que no es posible hacerlo con código administrado? Bien, hay una trampa en el escenario descrito. Cuando se utiliza el término “Hook” aplica a lo que significa el “hook”. Pero hay un tipo de hook que ha llegado a ser sinónimo de todos los hook. Espero entiendas, es algo confuso. Ese tipo de hook es llamado un "In-Context Hook" (hook en contexto).

Microsoft dice que la siguientes características describen los aspectos clave en las funciones de in-context hook:

  • Las funciones in-context hooks deben estar localizadas en una librería de vínculos dinámicos (DLL) que el sistema mapee en el espacio de direcciones del servidor.
  • Las funciones in-context hook comparten el espacio de direcciones con el servidor.
  • Cuando el servidor dispara un evento, el sistema llama a una función hook sin realizar lo que se llama la serialización.
  • Las funciones in-context hook tienden a ser muy rápidas y recibir notificaciones de eventos´de forma síncrona porque no hay serialización.
  • Algunos eventos pueden ser entregados out-of-process (fuera del proceso), incluso a pesar que tu solicites que sean entregados in-process (utilizando el flag WINEVENT_INCONTEXT). Puedes ver este comportamiento con casos de interoperabilidad de aplicación de 64-bit y 32-bit y con eventos de consola de Windows.
  • Entonces, ya sabemos que hay varios tipos de hooks. Nosotros no podemos simplemente y eficientemente crear callabacks por hooks in-context utilizando lenguajes administrados. ¿Cuál podría ser tus opciones? Ahí están las funciones de "Out-of-Context Hook".

    Microsoft dice que las siguientes características son los aspectos clave de las funciones out-of-context hook:

  • Las funciones hook Out-of-context están localizadas en el espacio de direcciones del cliente, a pesar que su código esté o no en una DLL.
  • Las funciones hook Out-of-context no están mapeadas en el espacio de direcciones del servidor.
  • Cuando un evento es disparado, los parámetros para las funciones hook son serializados a través de procesos.
  • Las funciones hook Out-of-context son notablemente más lentos en comparación con funciones hook in-context debido a la serialización.
  • El sistema pone en cola las notificaciones de los eventos, de modo que ellos llegan asíoncronamente debido al tiempo que se requiere para la serialización.
  • Como puedes ver que los hooks Out-of-Context son muy diferentes de los hooks tradicionales. Nota la parte que dice que las funciones hooks "Out-of-context” son notablemente más lentas debido a la serialización. Esto llega a ser menos relevante en estos días. Código más lento pero confiable es mejor que código rápido no confiable; las soluciones administradas son el futuro. Como puedes ver no es necesario algo especial para implementar un hook Out-of-Context.  La única cosa que se necesita es hacerlo.

    El Código

    Considera la siguiente clase:

    C#:

    public enum SystemEvents : uint
    {
    EVENT_SYSTEM_FOREGROUND = 3, //Active Foreground Window
    EVENT_SYSTEM_CAPTURESTART = 8, //Active Foreground Window Mouse Capture
    EVENT_OBJECT_CREATE = 32768, //An object has been created. The system sends this event for the following user interface elements: caret, header control, list-view control, tab control, toolbar control, tree view control, and window object.
    EVENT_OBJECT_DESTROY = 32769, //An object has been destroyed. The system sends this event for the following user interface elements: caret, header control, list-view control, tab control, toolbar control, tree view control, and window object.
    EVENT_OBJECT_FOCUS = 32773 //An object has received the keyboard focus. The system sends this event for the following user interface elements: list-view control, menu bar, pop-up menu, switch window, tab control, tree view control, and window object.
    }


    public class SystemEvent
    {
    private const uint WINEVENT_OUTOFCONTEXT = 0;
    [System.Runtime.InteropServices.DllImport("user32.dll")]
    private static extern System.IntPtr SetWinEventHook(uint eventMin, uint eventMax, System.IntPtr hmodWinEventProc, WinEventDelegate lpfnWinEventProc, uint idProcess, uint idThread, uint dwFlags);

    private delegate void WinEventDelegate(System.IntPtr hWinEventHook, uint eventType, System.IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
    public event SystemEventEventHandler SystemEventHandler;
    public delegate void SystemEventEventHandler(System.IntPtr hWinEventHook, uint eventType, System.IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime);
    private uint m_event = 0;
    private WinEventDelegate m_delegate = null;

    private System.IntPtr m_foregroundHwnd = System.IntPtr.Zero;
    public SystemEvent(SystemEvents SystemEvent)
    {
    m_event =System.Convert.ToUInt32(SystemEvent);
    m_delegate = new WinEventDelegate(WinEventProc);
    try {
    SetWinEventHook(m_event, m_event, System.IntPtr.Zero, m_delegate,System.Convert.ToUInt32(0),System.Convert.ToUInt32(0), WINEVENT_OUTOFCONTEXT);
    } catch (System.Exception ex) {
    System.Diagnostics.Debug.WriteLine(ex.ToString());
    }
    }

    public void WinEventProc(System.IntPtr hWinEventHook, uint eventType, System.IntPtr hwnd, int idObject, int idChild, uint dwEventThread, uint dwmsEventTime)
    {
    if ((((SystemEventHandler != null)) && (SystemEventHandler.GetInvocationList().Length > 0))) {
    m_foregroundHwnd = hwnd;
    if (SystemEventHandler != null) {
    SystemEventHandler(hWinEventHook, eventType, hwnd, idObject, idChild, dwEventThread, dwmsEventTime);
    }
    }
    }

    public System.IntPtr Hwnd
    {
    get { return m_foregroundHwnd; }
    }
    }
    Vb.NET:
    Public Enum SystemEvents As UInteger
    EVENT_SYSTEM_FOREGROUND = 3 'Active Foreground Window
    EVENT_SYSTEM_CAPTURESTART = 8 'Active Foreground Window Mouse Capture
    EVENT_OBJECT_CREATE = 32768 'An object has been created. The system sends this event for the following user interface elements: caret, header control, list-view control, tab control, toolbar control, tree view control, and window object.
    EVENT_OBJECT_DESTROY = 32769 'An object has been destroyed. The system sends this event for the following user interface elements: caret, header control, list-view control, tab control, toolbar control, tree view control, and window object.
    EVENT_OBJECT_FOCUS = 32773 'An object has received the keyboard focus. The system sends this event for the following user interface elements: list-view control, menu bar, pop-up menu, switch window, tab control, tree view control, and window object.
    End Enum


    Public Class SystemEvent
    Private Const WINEVENT_OUTOFCONTEXT As UInteger = 0
    <System.Runtime.InteropServices.DllImport("user32.dll")> _
    Private Shared Sub SetWinEventHook(ByVal eventMin As UInteger, ByVal eventMax As UInteger, ByVal hmodWinEventProc As IntPtr, ByVal lpfnWinEventProc As WinEventDelegate, ByVal idProcess As UInteger, ByVal idThread As UInteger, ByVal dwFlags As UInteger)
    End Sub
    Private Delegate Sub WinEventDelegate(ByVal hWinEventHook As IntPtr, ByVal eventType As UInteger, ByVal hwnd As IntPtr, ByVal idObject As Integer, ByVal idChild As Integer, ByVal dwEventThread As UInteger, ByVal dwmsEventTime As UInteger)
    Public Event SystemEvent(ByVal hWinEventHook As IntPtr, ByVal eventType As UInteger, ByVal hwnd As IntPtr, ByVal idObject As Integer, ByVal idChild As Integer, ByVal dwEventThread As UInteger, ByVal dwmsEventTime As UInteger)
    Private m_event As UInteger = 0
    Private m_delegate As WinEventDelegate = Nothing
    Private m_foregroundHwnd As IntPtr = IntPtr.Zero

    Public Sub New(ByVal SystemEvent As SystemEvents)
    m_event = CUInt(SystemEvent)
    m_delegate = New WinEventDelegate(AddressOf WinEventProc)
    Try
    SetWinEventHook(m_event, m_event, IntPtr.Zero, m_delegate, CUInt(0), CUInt(0), WINEVENT_OUTOFCONTEXT)
    Catch ex As Exception
    Debug.WriteLine(ex.ToString)
    End Try
    End Sub

    Public Sub WinEventProc(ByVal hWinEventHook As IntPtr, ByVal eventType As UInteger, ByVal hwnd As IntPtr, ByVal idObject As Integer, ByVal idChild As Integer, ByVal dwEventThread As UInteger, ByVal dwmsEventTime As UInteger)
    If ((Not SystemEventEvent Is Nothing) AndAlso (SystemEventEvent.GetInvocationList.Length > 0)) Then
    m_foregroundHwnd = hwnd
    RaiseEvent SystemEvent(hWinEventHook, eventType, hwnd, idObject, idChild, dwEventThread, dwmsEventTime)
    End If
    End Sub

    Public ReadOnly Property Hwnd() As IntPtr
    Get
    Return m_foregroundHwnd
    End Get
    End Property
    End Class
     
    Para utilizar esas clases, en Vb.NET:
    Dim KeyboardFocus As New SystemEvent(SystemEvents.EVENT_OBJECT_FOCUS)
    Dim ForegroundWindowWhoHasKeyboardFocus As IntPtr = KeyboardFocus.Hwnd
    Para utilizar esas clases, en C#:
    SystemEvent KeyboardFocus = new SystemEvent(SystemEvents.EVENT_OBJECT_FOCUS);
    System.IntPtr  ForegroundWindowWhoHasKeyboardFocus = KeyboardFocus.Hwnd; 
    En vez de llamar a GetForegroundWindow cada vez que quieres saber cual aplicación tiene el foco del teclado. Simplemente lo lees desde KeyboardFocus.Hwnd.
    ¿Hay una lista sustancial de EVENT_OBJECT_? Puedes encontrar la lista haciendo una búsqueda para "EVENT_OBJECT_FOCUS = 32773", en la clase sólo hay unos ejemplos. 

    Puntos de Interés


    Sería grandioso ver otros hooks Out-of-Context de ustedes. Si conoces algunos, deja un comentario.

    0 comentarios: