1

我正在尝试在我的 C# 应用程序中创建一个窗口。

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);
}

问题是,当点击我的按钮(在 Dummy.exe 中)时,我从来没有进入我的 Hook,我做错了什么?

谢谢。


编辑

程序.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);
    }
}

对于假人,使用以下方法创建一个新表单:

    public Form1()
    {
        InitializeComponent();
    }

    private void button1_Click(object sender, EventArgs e)
    {
        MessageBox.Show("CONTENT", "TITLE");
    }
4

5 回答 5

7

与大多数SetWindowsHookEx挂钩一样,WH_CBT挂钩要求挂钩回调位于一个单独的 Win32 DLL 中,该 DLL 将被加载到目标进程中。这本质上要求钩子是用 C/C++ 编写的,C# 在这里不起作用。

(低级鼠标和键盘挂钩是此规则的两个例外。在 C# 中也可以使用其他挂钩,但前提是您要挂钩自己的线程之一,因此 dwThreadId 是当前进程中线程的 id ,而不是 0。不过,我还没有证实这一点。而且您需要确保您使用的是 Win32 线程 ID,因此使用 GetCurrentThreadId 可能是这里最好的选择。)

如果您想观察另一个应用程序中出现的新窗口,另一种 C# 友好的方法是使用SetWinEventHook API,指定 WINEVENT_OUTOFCONTEXT(这是将事件传递到您自己的进程的魔术标志,无需DLL 并使 C# 在此处可用)并挂钩EVENT_OBJECT_CREATEEVENT_OBJECT_SHOW事件。您可以收听自己的进程/线程的事件,也可以收听当前桌面上的所有进程/线程。

这将为您提供各种“创建”和显示通知,包括对话框中子 HWND 的通知,甚至列表框内的项目等;因此您需要过滤以仅提取顶级 HWND 的内容:例如。检查 idObject==OBJID_WINDOW 和 idChild==0; hwnd 是可见的 ( IsVisible()) 并且是顶级的。

请注意,使用 WinEvents 要求调用 SetWinEventHook 的线程正在泵送消息 - 如果它是带有 UI 的线程,则通常都是这种情况。如果没有,您可能需要手动添加消息循环(GetMessage/TranslateMessage)。您还需要在此处将 GC.KeepAlive() 与回调一起使用,以防止在调用 UnhookWinEvents 之前收集它。

于 2012-02-01T22:26:45.980 回答
2

您的代码的一个问题是,hhookProc当您的本机代码仍然需要它时,它可能会被垃圾收集。使用GC.KeepAlive或放入静态变量。

参数可能应该为空,因为您在hMod自己的进程中指定了一个线程:

hMod [输入]

类型:HINSTANCE

包含 lpfn 参数指向的钩子过程的 DLL 句柄。如果 dwThreadId 参数指定由当前进程创建的线程并且挂钩过程在与当前进程关联的代码内,则 hMod 参数必须设置为 NULL。


但我认为这仅适用于您指定的线程上的窗口。

理论上,您可以在其他应用程序甚至全局挂钩中指定线程。然后在相应的线程上调用指定的回调,即使该线程在另一个进程中,在这种情况下,您的 dll 将被注入该进程(这就是您需要首先指定模块句柄的原因)。

但我相信 .net 代码不可能做到这一点,因为注入其他进程和调用钩子方法的机制不适用于 JIT 编译代码。

于 2012-02-01T21:37:22.303 回答
1

这在 C# 中不起作用

范围:线程

如果应用程序为不同应用程序的线程安装挂钩过程,则该过程必须在 DLL中。

(文档SetWindowsHookEx

范围:全球

要安装全局挂钩,挂钩必须具有本机 DLL 导出,以将自身注入另一个需要有效、一致的函数才能调用的进程。此行为需要 DLL 导出。.NET Framework 不支持 DLL 导出。

来源

于 2012-02-01T22:28:07.393 回答
0

我不熟悉NativeMethod您所引用的课程,但我会做出一些假设并尝试建立一些基础。我猜这与你钩住的手柄有关。这

dummy.MainWindowHandle

代表最前面窗口的句柄,通常是您要查找的。但是,在这种情况下,您正在打开一个 MessageBox.Show(),它的句柄可能与您所连接的句柄不同。

我会假设

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

可能会返回与

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

所以我认为可以肯定地说他们可能会给你你不想要的把手。

也许尝试制作一个测试 WinForm 应用程序。这样你就可以抓住正确的把手。只要确保使用

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

在抓住手柄之前,确保您在启动时抓住了正确的手柄。

于 2012-02-01T21:22:47.273 回答
0

我看到它是一个控制台应用程序,所以控制台应用程序不会进入 Windows 消息循环。

简单的解决方案是包含 system.windows.forms

只需在 main 中键入 application.start() 就可以了:)

于 2013-10-06T23:36:01.753 回答