Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Create a C# project that successfully creates a WH_SHELL hook to a single application

Tags:

c#

I am trying to create a project that can detect when a taskbar icon is flashing. You typically do this by creating a hook into the application and checking for the appropriate message. In this case, the message can be detected by creating a WH_SHELL hook into the application, waiting for a HSHELL_REDRAW message and checking the lParam variable for TRUE.

According to the documents, if the lParam value is true, then the window referenced by the window handle in the wParam variable is flashing. Great.

The problem is I cannot, for the life of me, figure out how to actually create a WH_SHELL hook. The code to create a hook seems fairly straightforward:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Windows.Forms;
using System.Runtime.InteropServices;
using System.Diagnostics;

class Flashy {
  [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
  protected static extern IntPtr SetWindowsHookEx(int code, HookProc func, IntPtr hInstance, int threadID);

  [DllImport("user32.dll", CharSet = CharSet.Auto, CallingConvention = CallingConvention.StdCall)]
  static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

  [DllImport("kernel32.dll", CharSet = CharSet.Auto, SetLastError = true)]
  public static extern IntPtr GetModuleHandle(string lpModuleName);

  private int WH_SHELL = 10;
  static IntPtr hHook;

  public void doStuff() {
    using (Process process = Process.GetCurrentProcess())
    using (ProcessModule module = process.MainModule)
    {
      IntPtr hModule = GetModuleHandle(module.ModuleName);
      MyDLL.Class1.hHook = SetWindowsHookEx(WH_SHELL, MyDLL.Class1.MyHookProc, hModule,     0);
    }
  }
}

Now, from what I've read, the WH_SHELL requires a dll. So mine looks like:

using System;
using System.Collections.Generic;
using System.Text;
using System.Runtime.InteropServices;
using System.Diagnostics;

namespace FlashyDLL
{
  public class Class1
  {

    //This is the Import for the CallNextHookEx function.
    //Use this function to pass the hook information to the next hook procedure in chain.
    [DllImport("user32.dll", CharSet = CharSet.Auto,
     CallingConvention = CallingConvention.StdCall)]
    static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);

    public static IntPtr hHook;

    public static int MyHookProc(int nCode, IntPtr wParam, IntPtr lParam)
    {
      if (nCode < 0)
        return CallNextHookEx(hHook, nCode, wParam, lParam);

      switch (nCode)
      {
        case 6: //HSHELL_REDRAW
          break;
      }
      return CallNextHookEx(hHook, nCode, wParam, lParam);
    }
  }
}

So what am I doing wrong? Thanks!

*Edit:

Here's the final bits of code that worked. It's basically the code from the accepted answer with a few minor additions. I'm posting it this way because I hate trying to piece together bits of code from various portions of the thread and I figure other people do, too.

Note that this is a global shell hook, not an application specific one, so you have to check the lParam handle against the handle you're looking for to ensure the flash message is about the correct window.

Step 1: window to pick up messages:

using System;
using System.Windows.Forms;

public class TestAlertForm : Form 
{
    private IntPtr myWindow;
    private int uMsgNotify = 0;
    readonly IntPtr HSHELL_FLASH = new IntPtr(0x8006);

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == uMsgNotify)
        {
            if (m.WParam == HSHELL_FLASH)
            {
                if (m.LParam == myWindow)
                {
                    MessageBox.Show("FLASH!");
                }
            }
        }
        base.WndProc(ref m);
    }
}

Step 2: PInvokes:

public class Win32 {
    [DllImport("user32.dll", EntryPoint = "RegisterWindowMessageA", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    private static extern int RegisterWindowMessage(string lpString);

    [DllImport("user32.dll", CharSet = CharSet.Ansi, SetLastError = true, ExactSpelling = true)]
    private static extern int RegisterShellHookWindow(IntPtr hWnd);

    [DllImport("user32.dll", EntryPoint = "FindWindow", SetLastError = true)]
    static extern System.IntPtr FindWindowByCaption(int ZeroOnly, string lpWindowName);
}

Step 3: Activate the hook:

var taf = new TestAlertForm();
myWindow = FindWindowByCaption(0, "My Window Title");
uMsgNotify = RegisterWindowMessage("SHELLHOOK");
Win32.RegisterShellHookWindow(taf.Handle);
like image 580
jpreed00 Avatar asked Feb 28 '14 05:02

jpreed00


People also ask

What is C used to create?

C programming language is a machine-independent programming language that is mainly used to create many types of applications and operating systems such as Windows, and other complicated programs such as the Oracle database, Git, Python interpreter, and games and is considered a programming foundation in the process of ...

How do you create AC project?

Open Visual Studio, and choose Create a new project in the Start window. In the Create a new project window, select All languages, and then choose C# from the dropdown list. Choose Windows from the All platforms list, and choose Console from the All project types list.

Is C created by Microsoft?

The C# programming language was designed by Anders Hejlsberg from Microsoft in 2000 and was later approved as an international standard by Ecma (ECMA-334) in 2002 and ISO/IEC (ISO/IEC 23270) in 2003. Microsoft introduced C# along with .NET Framework and Visual Studio, both of which were closed-source.


1 Answers

OK, turns out this is a bit tricky in .Net -- but it is possible. You can use SetWindowsHookEx to hook directly to a single thread by ID, but this way is a bit simpler and easier to match to a window.

Caveat: This relies on an internal USER32.dll call.

Step 1: window to pick up messages:

using System;
using System.Windows.Forms;

public class TestAlertForm : Form 
{
    readonly IntPtr HSHELL_FLASH = new IntPtr(0x8006);

    protected override void WndProc(ref Message m)
    {
        if (m.WParam == HSHELL_FLASH)
        {
            // TODO: DO WORK HERE
            // m.LParam <-- A handle to the window that is 'flashing'
            //              You should be able to match this to your target.
            MessageBox.Show("FLASH!");
        }
        base.WndProc(ref m);
    }
}

Step 2: PInvokes:

public class Win32 {
    [DllImport("user32.dll")]
    public static extern bool RegisterShellHookWindow(IntPtr handle);
}

Step 3: Activate the hook:

var taf = new TestAlertForm();
Win32.RegisterShellHookWindow(taf.Handle);

After all that, you should see a message box for every flash. This is only a quick hack and I haven't tested it much.

Good luck!

like image 177
Iain Ballard Avatar answered Oct 02 '22 09:10

Iain Ballard