1

I'm trying to hook the creation of a windows in my C# app.

static IntPtr hhook = IntPtr.Zero;
static NativeMethods.HookProc hhookProc;

static void Main(string[] args)
{
    // Dummy.exe is a form with a button that opens a MessageBox when clicking on it.
    Process dummy = Process.Start(@"Dummy.exe");

    try
    {
        hhookProc = new NativeMethods.HookProc(Hook);
        IntPtr hwndMod = NativeMethods.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);
        hhook = NativeMethods.SetWindowsHookEx(HookType.WH_CBT, hhookProc, hwndMod, (uint)AppDomain.GetCurrentThreadId());

        Console.WriteLine("hhook valid? {0}", hhook != IntPtr.Zero);

        while (!dummy.HasExited)
            dummy.WaitForExit(500);                
    }
    finally
    {
        if(hhook != IntPtr.Zero)
            NativeMethods.UnhookWindowsHookEx(hhook);
    }
}

static int Hook(int nCode, IntPtr wParam, IntPtr lParam)
{
    Console.WriteLine("Hook()");
    return NativeMethods.CallNextHookEx(hhook, nCode, wParam, lParam);
}

Problem is, when clicking on my button (in Dummy.exe), I never enter in my Hook, what am I doing wrong?

Thanks.


EDIT

Program.cs

using System;
using System.Diagnostics;

namespace Hooker
{
    class Program
    {
        static IntPtr hhook = IntPtr.Zero;
        static NativeMethods.HookProc hhookProc;

        static void Main(string[] args)
        {
            Process dummy = Process.Start(@"Dummy.exe");

            try
            {
                hhookProc = new NativeMethods.HookProc(Hook);
                hhook = NativeMethods.SetWindowsHookEx(HookType.WH_CBT, hhookProc, IntPtr.Zero, 0);

                Console.WriteLine("hhook valid? {0}", hhook != IntPtr.Zero);

                while (!dummy.HasExited)
                    dummy.WaitForExit(500);                
            }
            finally
            {
                if(hhook != IntPtr.Zero)
                    NativeMethods.UnhookWindowsHookEx(hhook);
            }
        }

        static int Hook(int nCode, IntPtr wParam, IntPtr lParam)
        {
            Console.WriteLine("Hook()");
            return NativeMethods.CallNextHookEx(IntPtr.Zero, nCode, wParam, lParam);
        }
    }
}

NativeMethods.cs

namespace Hooker
{
    using System;
    using System.Runtime.InteropServices;

    internal static class NativeMethods
    {
        public delegate int HookProc(int code, IntPtr wParam, IntPtr lParam);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern IntPtr SetWindowsHookEx(HookType hookType, HookProc lpfn, IntPtr hMod, int dwThreadId);
        [DllImport("user32.dll")]
        public static extern int CallNextHookEx(IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
        [DllImport("user32.dll", SetLastError = true)]
        [return: MarshalAs(UnmanagedType.Bool)]
        public static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", SetLastError = true)]
        public static extern int GetWindowThreadProcessId(IntPtr hwnd, ref int pid);

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

For dummy, do a new Form with :

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("CONTENT", "TITLE");
    }
Deanna
  • 23,876
  • 7
  • 71
  • 156
Arnaud F.
  • 8,252
  • 11
  • 53
  • 102

5 Answers5

7

Like most SetWindowsHookEx hooks, WH_CBT hooks require that the hook callback lives in a separate Win32 DLL that will get loaded into the target process. This essentially requires that the hook is written in C/C++, C# won't work here.

(Low-level mouse and keyboard hooks are two exceptions to this rule. It might also be possible to use other hooks in C# but only if you are hooking one of your own threads, so dwThreadId is the id of a thread in the current process, not 0. I haven't confirmed this, though. And you need to make sure you're using the Win32 threadid, so using GetCurrentThreadId is probably the best bet here.)

If you want to watch for new windows appearing from another app, an alternative C#-friendly approach is to use the SetWinEventHook API instead, specify WINEVENT_OUTOFCONTEXT (which is the magic flag that gets the events delivered to your own process, removing the need for a DLL and making C# usable here) and hook for the EVENT_OBJECT_CREATE and EVENT_OBJECT_SHOW events. You can listen to either your own process/thread's events, or to all processes/threads on the current desktop.

This will get you all sorts of "create" and show notifications, including those for child HWNDs within dialog, and even items within listboxes and similar; so you'll need to filter to extract only those for top-level HWNDs: eg. check that idObject==OBJID_WINDOW and idChild==0; that hwnd is visible (IsVisible()) and is top-level.

Note that using WinEvents requires that the thread that calls SetWinEventHook is pumping messages - which is typically the case anyway if it's a thread with UI. If not, you may need to add a message loop (GetMessage/TranslateMessage) manually. And you'll also want to use GC.KeepAlive() with the callback here also to prevent it from getting collected until after you call UnhookWinEvents.

BrendanMcK
  • 14,252
  • 45
  • 54
2

One issue with your code is that hhookProc can be garbage collected while your native code still needs it. Use GC.KeepAlive or put in in a static variable.

The hMod param should probably be null, since you specified a thread in your own process:

hMod [in]

Type: HINSTANCE

A handle to the DLL containing the hook procedure pointed to by the lpfn parameter. The hMod parameter must be set to NULL if the dwThreadId parameter specifies a thread created by the current process and if the hook procedure is within the code associated with the current process.


But I think this only works for windows on the thread you specify.

In theory you can specify threads in other applications or even a global hook. The specified callback then gets called on the corresponding thread, even if that thread is in another process, in which case your dll gets injected into that process(that's the reason you need to specify the module handle in the first place).

But I believe that's not possible with .net code, because the mechanism for injecting into other processes and calling the hook method over there doesn't work with JIT compiled code.

Community
  • 1
  • 1
CodesInChaos
  • 106,488
  • 23
  • 218
  • 262
  • @Arnaud Your new code is still broken. You either need to specify a `0` hModule and a thread inside your own process. In which case you can only observe windows in your own process. Or you specify `0` or an external thread and a non null `hModule`. But as I wrote, the second approach is likely impossible in .net. – CodesInChaos Feb 01 '12 at 21:56
  • Actually, I don't known which value I should pass to SetWindowsHookEx to hook an application I launched from inside the current application... – Arnaud F. Feb 01 '12 at 22:02
  • @Arnaud You would need to pass your current dll `hModule` as `hMod` and the thread associated with the created window as `threadId`. Then once an event happens, your dll gets injected into the target process and your hook method gets called. But the hook method must be at a constant offset from your dll, i.e. it can't be JIT compiled. – CodesInChaos Feb 01 '12 at 22:04
  • Thanks, actually I read this : http://www.klocwork.com/products/documentation/current/How_kwinject_works, in this article it is written `On Windows, kwinject starts the user process in debug mode and listens to CREATE_PROCESS debug events.`, how is this done, this can be done with SetWindowsHook, right? – Arnaud F. Feb 01 '12 at 22:09
  • Unlikely. According to the text it acts as a debugger for the process it wants to monitor. – CodesInChaos Feb 01 '12 at 22:11
  • Ok, never mind, will delete the discussion then because was the aim of my question. Thanks for patience and explanations. – Arnaud F. Feb 01 '12 at 22:16
  • The real answer is hidden at the bottom. You might want to move it to the top. You can't install a `WH_CBT` hook from a C# application. You must have the hook procedure live in an unmanaged DLL. – Cody Gray - on strike Feb 02 '12 at 04:32
1

This won't work in C#

Scope: Thread

If the application installs a hook procedure for a thread of a different application, the procedure must be in a DLL.

(Documentation of SetWindowsHookEx)

Scope: Global

To install a global hook, a hook must have a native DLL export to inject itself in another process that requires a valid, consistent function to call into. This behavior requires a DLL export. The .NET Framework does not support DLL exports.

(Source)

Community
  • 1
  • 1
ordag
  • 2,497
  • 5
  • 26
  • 35
  • The only possible windows hooks implemented in C# are _global low level hooks_ and _local thread hooks_. – ordag Feb 01 '12 at 22:30
0

I see it is a console application , so console application doesn't enter a windows messages loop.

simple solution is to include system.windows.forms

and simply type application.start() in your main and things will be fine :)

user2533527
  • 256
  • 2
  • 10
0

I'm not familiar with the NativeMethod class you are referencing, but I'll make some assumptions and try to make some ground. My guess this has to do with what handle you are hooking. The

dummy.MainWindowHandle

represents the front most window's handle, which is usually what you are looking for. However, in this case, you are opening a MessageBox.Show() which likely has a different handle than the one you have hooked to.

I'd assume that

IntPtr hwndMod = NativeMethods.GetModuleHandle(Process.GetCurrentProcess().MainModule.ModuleName);

is probably going to return the same result as

dummy.Refresh();
IntPtr hwndMod = dummy.MainWindowHandle;

So I think it's safe to say that they might be giving you the handle you aren't looking for.

Perhaps try to make a test WinForm application. That way you can grab the proper handle. Just make sure to use

dummy.WaitForInputIdle();
dummy.Refresh(); 

before grabbing the handle to make sure you are grabbing the right handle at launch time.

corylulu
  • 3,449
  • 1
  • 19
  • 35
  • @ArnaudF. Well I would need more code regarding the NativeMethod stuff or a copy of the project to test further. Not enough for me to work with. – corylulu Feb 01 '12 at 21:38
  • At what point are you getting the dummys handle? From the looks of it (if I'm following correctly) you are giving it the current applications handle only. – corylulu Feb 01 '12 at 21:55
  • A hModule isn't a window handle. – CodesInChaos Feb 01 '12 at 21:58
  • Yes, the dummy handle comes from a Process launched inside the current application. – Arnaud F. Feb 01 '12 at 22:01
  • Okay, but from this code, the dummy application seems irrelevant. You could launch it externally and produce the same results. The Dummy's information is never used outside of it being started. This hook seems to be attempting a global hook. But listen to CodeInChaos's response, he seems to know more about this topic. – corylulu Feb 01 '12 at 22:11