Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / VB

Directly Hook to System Events using managed code (C#,VB.Net)

0.00/5 (No votes)
8 Mar 2013CPOL3 min read 31.4K  
Hook to System Events without libraries or anything complicated.

Introduction

     Hooking without a library and without a native library at that?  Rubish, correct?  So how can it be done from managed code without any library at all?  In a nutshell SetWinEventHook (not SetWinEventHookEx) using the flag WINEVENT_OUTOFCONTEXT.   

Background 

     A traditional hook requires you to create a callback function (a method).  Additionally this method must be "mapped into address space of the process that generates the event".  What that means is the callback method must be a method residing in a native (C++, etc) library (DLL).   The reason for this is that the system will load and map the location of the DLL and requires this to be the case in order to find the function pointer of your callback (method). 

     Okay, so now that I have stated it is not possible; how do we do it?  Well there is a gotcha to the scenario I just described.  Although when we here the term "Hook" the scenario above generally applies as the meaning to what a hook is.  However, what I have described is a specific type of hook that has become so common that it is synonymous with the term Hook.  It is actually an "In-Context Hook". 

Microsoft says the following list outlines the key aspects of in-context hook functions:  

  • In-context hooks functions must be located in a dynamic-link library (DLL) that the system maps into the server's address space.
  • In-context hook functions share the address space with the server. 
  • When the server triggers an event, the system calls a hook function without marshaling (packaging and sending interface parameters across process boundaries).
  • In-context hook functions tend to be very fast and receive event notifications synchronously because there is no marshaling.
  • Some events may be delivered out-of-process, even though you request that they be delivered in-process (using the WINEVENT_INCONTEXT flag). You might see this situation with 64-bit and 32-bit application interoperability issues and with Windows console events. 

      Okay, so now we know there are various types of hooks.  And that we can not simple and efficiently create callbacks for In-Context Hooks using managed languages.  What might our options be?  Well there are "Out-of-Context Hook Functions". 

Microsoft says the following list outlines the key aspects of out-of-context hook functions: 

  • Out-of-context hook functions are located in the client's address space, whether it is in the code body or in a DLL. 
  • Out-of-context hook functions are not mapped into the server's address space.
  • When an event is triggered, the parameters for the hook function are marshaled across process boundaries. 
  • Out-of-context hook functions are noticeably slower than in-context hook functions due to marshaling.
  • The system queues the event notifications so that they arrive asynchronously (because of the time required to perform marshaling). 

     As you can see; Out-of-Context Hooks are very different from traditional hooks.  Notice the part that says "Out-of-context hook functions are noticeably slower than in-context hook functions due to marshaling."  That is becoming less relevant these days.  Slower reliable code is better than fast unreliable code; admittedly fast and reliable would be nice but that shipped has sailed already.  Managed solutions are the future.   As you can see we are not required to do anything special to implement an Out-of-Context Hook.  The only thing left is getting it done. 

Using the code 

     Consider the following class... 

C#:

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: 

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 

To use the class above in VB.Net: 

VB.NET
Dim KeyboardFocus As New SystemEvent(SystemEvents.EVENT_OBJECT_FOCUS) 
VB.NET
Dim ForegroundWindowWhoHasKeyboardFocus As IntPtr = KeyboardFocus.Hwnd 

To use the class above in C#: 

C#
SystemEvent KeyboardFocus = new SystemEvent(SystemEvents.EVENT_OBJECT_FOCUS);
C#
System.IntPtr  ForegroundWindowWhoHasKeyboardFocus = KeyboardFocus.Hwnd; 

     Instead of polling GetForegroundWindow every time you want to know which application has keyboard focus,  You can simple read it from KeyboardFocus.Hwnd      

     There is a substantial list of EVENT_OBJECT_????? you can find the list by doing a search for "EVENT_OBJECT_FOCUS = 32773" I only provide a few in the example class. 

Points of Interest 

     It would be great to see other Out-of-Context Hooks from people.  If you know of any please let me know by leaving a comment.     

History

     I will post revisions based on people's requests in comments.  I know certain things may need clarification.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)