【问题标题】:How to hook an application?如何挂钩应用程序?
【发布时间】:2012-02-01 20:28:02
【问题描述】:

我正在尝试在我的 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");
    }

【问题讨论】:

  • 为什么要使用键盘挂钩?我想检测一个exe(没有GUI)何时调用另一个exe(有GUI)。键盘的链接是什么?
  • 我记错了 WH_CBT 的意思。
  • 为什么在模块句柄前加上hwnd?它不是窗口句柄。
  • 用非托管代码编写。

标签: c# winapi setwindowshookex


【解决方案1】:

与大多数 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 之前收集它。

【讨论】:

    【解决方案2】:

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

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

    hMod [输入]

    类型:HINSTANCE

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


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

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

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

    【讨论】:

    • @Arnaud 您的新代码仍然损坏。您要么需要在自己的进程中指定0 hModule 和线程。在这种情况下,您只能在自己的进程中观察窗口。或者您指定0 或一个外螺纹和一个非空hModule。但正如我所写,第二种方法在 .net 中可能是不可能的。
    • 实际上,我不知道应该将哪个值传递给 SetWindowsHookEx 来挂钩我从当前应用程序内部启动的应用程序...
    • @Arnaud 您需要将当前的 dll hModule 传递为 hMod,并将与创建的窗口关联的线程传递为 threadId。然后,一旦发生事件,您的 dll 就会被注入目标进程并调用您的钩子方法。但是钩子方法必须与你的 dll 保持恒定的偏移量,即它不能被 JIT 编译。
    • 谢谢,其实我看了这个:klocwork.com/products/documentation/current/How_kwinject_works,这篇文章里写的是On Windows, kwinject starts the user process in debug mode and listens to CREATE_PROCESS debug events.,这是怎么做到的,这个可以用SetWindowsHook来做吧?
    • 不太可能。根据文本,它充当要监视的进程的调试器。
    【解决方案3】:

    这在 C# 中不起作用

    作用域:线程

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

    SetWindowsHookEx 的文档)

    范围:全球

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

    (Source)

    【讨论】:

    • 在 C# 中实现的唯一可能的 Windows 挂钩是 全局低级挂钩本地线程挂钩
    【解决方案4】:

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

    dummy.MainWindowHandle

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

    我认为

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

    可能会返回与

    相同的结果
    dummy.Refresh();
    IntPtr hwndMod = dummy.MainWindowHandle;
    

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

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

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

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

    【讨论】:

    • @ArnaudF。好吧,我需要更多关于 NativeMethod 东西的代码或项目的副本来进一步测试。不够我工作。
    • 你在什么时候得到假人句柄?从外观上看(如果我没看错的话)你只是给它当前的应用程序句柄。
    • hModule 不是窗口句柄。
    • 是的,虚拟句柄来自当前应用程序内部启动的进程。
    • 好的,但是从这段代码来看,虚拟应用程序似乎无关紧要。您可以在外部启动它并产生相同的结果。假人的信息永远不会在它启动之外使用。这个钩子似乎正在尝试一个全局钩子。不过听 CodeInChaos 的回复,他似乎对这个话题了解得更多。
    【解决方案5】:

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

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

    然后在你的 main 中输入 application.start() 一切都会好起来的:)

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-05-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多