14

我在此页面上发现了类似的问题,但我似乎无法弄清楚如何解释答案或弄清楚它们是否真的重复。

以下是我发现的可能重复项,并附有评论:

汉斯·帕桑特(Hans Passant)对已删除的最后一个答案的评论如下:

您使用的是 .NET 4.0 吗?它的 CLR 改变了程序集的加载方式,不再有 LoadLibrary 调用,也不会有它们的模块句柄。改用 GetEntryAssembly() 将是另一种解决方法。– Hans Passant 5 月 5 日 19:43

那么,这里的词是什么?您使用的是 .NET 4.0 吗?您是否尝试使用 LoadLibrary("user32.dll") 来获取可用的 DLL 句柄?– Hans Passant 5 月 6 日 15:43

我很确定我不需要这样做,但显然我不是 100% 确定。如果我需要更改它,我留下的问题是为什么它在编译为 64 位操作系统时可以在 64 位操作系统上运行Any CPU,但在任何配置中都不能在 32 位操作系统上运行。

如果在加载 .NET 程序集方面确实发生了一些变化,以至于我无法正确处理类库,那么我有以下问题:

  • 有什么办法可以欺骗它做我想做的事,而不必降级到 .NET 3.5 或将钩子库更改为非托管?
  • 为什么在 64 位操作系统上运行时它可以工作,但在 32 位操作系统上却不行?

背景

我在 .NET 4.0 中构建了一个程序,它使用带有 WH_KEYBOARD_LL 钩子类型的 SetWindowsHookEx 来捕获按键。这在我的 64 位 Windows 7 上运行良好,但在 32 位 Windows 7 上安装键盘挂钩时会因“找不到模块”而崩溃。

这是我尝试过的:

  • 为 x86 编译,在 64 位操作系统上运行,因“找不到模块”而崩溃
  • 为 x86 编译,在 32 位操作系统上运行,崩溃
  • 为任何 CPU 编译,在 64 位操作系统上运行,运行良好
  • 为任何 CPU 编译,在 32 位操作系统上运行,崩溃
  • 切换到.NET 3.5,重复以上四种情况,都行

我宁愿不将我的代码切换到 .NET 3.5,因为我正在使用我的一些类库来简化工作,而最新的代码仅在 .NET 4.0 中。

如果需要,您可以下载包含所有内容的 .ZIP 文件作为 Visual Studio 2010 项目,也可以粘贴以下两个文件。

如果你想沿着这条路线重新创建:

  1. 创建一个新的控制台项目,.NET 4.0
  2. 添加另一个类库项目,也是 .NET 4.0
  3. 从控制台程序项目中添加对类库项目的引用
  4. 将下面的 Program.cs 内容粘贴到控制台项目中的 Program.cs 文件中
  5. 将下面的 Hook.cs 内容粘贴到类库项目中的文件中。您可以将其粘贴到 Class1.cs 默认文件中,或添加另一个文件。你不能把它放到控制台项目中

然后编译运行,测试各种配置。

程序.cs

using System;
using HookLib;

namespace HookTest
{
    class Program
    {
        static void Main()
        {
            var hook = new Hook();

            Console.Out.WriteLine("hooking");
            hook.Enable();
            Console.Out.WriteLine("hooked");

            Console.Out.WriteLine("unhooking");
            hook.Disable();
            Console.Out.WriteLine("unhooked");
        }
    }
}

钩子.cs

using System;
using System.ComponentModel;
using System.Reflection;
using System.Runtime.InteropServices;

namespace HookLib
{
    public class Hook
    {
        private IntPtr _Handle;
        private HookProcDelegate _Hook;

        public void Enable()
        {
            Module module = Assembly.GetExecutingAssembly().GetModules()[0];
            if (module != null)
                Console.Out.WriteLine("found module");
            IntPtr moduleHandle = Marshal.GetHINSTANCE(module);
            if (moduleHandle != IntPtr.Zero)
                Console.Out.WriteLine("got module handle: " +
                    moduleHandle.ToString());
            _Hook = HookProc;
            _Handle = SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, moduleHandle, 0);
            if (_Handle == IntPtr.Zero)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        public void Disable()
        {
            bool ok = UnhookWindowsHookEx(_Handle);
            _Handle = IntPtr.Zero;
            if (!ok)
                throw new Win32Exception(Marshal.GetLastWin32Error());
        }

        private delegate int HookProcDelegate(
            int code, IntPtr wParam, IntPtr lParam);

        private int HookProc(int code, IntPtr wParam, IntPtr lParam)
        {
            return CallNextHookEx(_Handle, code, wParam, lParam);
        }

        private const int WH_KEYBOARD_LL = 13;

        [DllImport("user32.dll", SetLastError = true)]
        private static extern IntPtr SetWindowsHookEx(
            int hookType, HookProcDelegate lpfn, IntPtr hMod, uint dwThreadId);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern bool UnhookWindowsHookEx(IntPtr hhk);

        [DllImport("user32.dll", SetLastError = true)]
        private static extern int CallNextHookEx(
            IntPtr hhk, int nCode, IntPtr wParam, IntPtr lParam);
    }
}
4

3 回答 3

17

是的,我想你明白发生了什么。SetWindowsHookEx() 需要一个有效的模块句柄,并对其进行验证,但在设置低级挂钩时它实际上并没有使用它。您只需要一个有效的句柄,哪个特定的句柄都没有关系。调用 LoadLibrary("user32.dll") 是获取句柄的好方法,因为您 P/Invoke 它的方法,DLL 将始终被加载。它总是由 CLR 引导程序 (mscoree.dll) 加载。不要打扰调用 FreeLibrary(),它没有区别。

更高版本的 Windows 不再执行此检查。不完全确定什么时候开始的,我想是在 Windows 7 SP1 附近的某个地方。可能意味着有帮助,但会调用“在我的机器上工作,而不是客户的”失败场景。

于 2010-09-10T11:37:25.613 回答
2

这是我在 .net 2 和 4 中都适用的解决方案。hInstance 是 ProcessModule.BaseAddress。

public static class ModuleHelper
    {
        public static ProcessModule GetCurrentModule()
        {
            // need instance handle to module to create a system-wide hook
            Module[] list = System.Reflection.Assembly.GetExecutingAssembly().GetModules();
            System.Diagnostics.Debug.Assert(list != null && list.Length > 0);

            var currentProcess = Process.GetCurrentProcess();
            var modules = currentProcess.Modules;
            ProcessModule mod = null;
            foreach (ProcessModule m in modules)
                            //for .net 2 we will find module here
                if (m.ModuleName == list[0].Name)
                {
                    mod = m;
                    break;
                }

                    //for .net 4 take current module
            if (mod == null)
                mod = Process.GetCurrentProcess().MainModule;

            return mod;
        }
    }
于 2010-11-22T19:56:45.533 回答
2

在 .Net 4.0 中,要使此代码正常工作,我必须替换调用:

SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, moduleHandle, 0);

和:

SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, IntPtr.Zero, 0);

这解决了问题,这在从同一模块进行调用时有效。

我从这里得到这个

于 2012-03-06T11:55:27.470 回答