在没有找到模块的32位机器上,.NET 4.0中的SetWindowsHookEx失败了吗?

ang*_*son 14 64-bit 32-bit .net-4.0 .net-3.5 setwindowshookex

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

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

Hans Passant对最后一个删除答案的评论如下:

你在使用.NET 4.0吗?它的CLR改变了程序集的加载方式,不再有LoadLibrary调用,也没有模块句柄.使用GetEntryAssembly()代替另一种修复. - 汉斯帕斯特5月5日19:43

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

我很确定我不需要这样做,但显然我不是百分百肯定.如果我需要更改它,我留下的问题是,为什么它适用于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中.

如果需要,可以下载包含Visual Studio 2010项目的.ZIP文件,也可以粘贴以下两个文件.

要重新创建是否要沿着该路线前进:

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

然后构建并运行,测试各种配置.

Program.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");
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

Hook.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);
    }
}
Run Code Online (Sandbox Code Playgroud)

Han*_*ant 17

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

更高版本的Windows不再执行此检查.不完全确定何时开始,我认为在Windows 7 SP1附近.可能意味着有用,但调用"在我的机器上工作,而不是客户的"故障情形.