SetWindowsHookEx 在 32 位计算机上的 .NET 4.0 中失败并显示“未找到模块”?
我在此页面上发现了类似的问题,但我似乎无法弄清楚如何解释答案或弄清楚它们是否真正重复。
以下是我发现的可能重复的内容,并附有评论:
在 32 位计算机中编译 .NET 4.0 框架时,SetWindowsHookEx 返回 0
我的似乎没有返回 0,但我注意到崩溃时报告的句柄(32 位上的 .NET 4.0)与运行时报告的句柄(32 位上的 .NET 3.5)有很大不同位),例如崩溃句柄 = 523727,工作句柄 = 172738378。
在 VS2008 调试器内调用 SetWindowsHookEx 始终返回 NULL
在 Visual Studio 之外运行时我可以重现我的问题
这似乎是最有希望的,除了对已删除答案的评论提到我应该使用 LoadLibrary 和 GetProcAddress 在 .NET 4.0 中加载 user32.dll,因为有关加载程序集的某些内容发生了变化。然而,我很确定它找不到我自己的模块,但我不知道这是否适用。
汉斯·帕桑特 (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% 确定。如果我需要更改此设置,我剩下的问题是为什么它在为 任何 CPU
编译时可以在 64 位操作系统上工作,但在任何配置下都不能在 32 位操作系统上工作。
如果有关 .NET 程序集的加载确实发生了一些变化,因此我无法获得类库的正确句柄,那么我有以下问题:
- 有什么方法可以欺骗它执行我想要的操作,而不必这样做降级到 .NET 3.5 或将挂钩库更改为非托管?
- 为什么在 64 位操作系统上可以运行,但在 32 位操作系统上却不行?
背景
我在.NET 4.0 中构建了一个程序,它使用 SetWindowsHookEx 和 WH_KEYBOARD_LL 挂钩类型来捕获按键操作。这在我的 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 项目 ,或者您可以粘贴以下两个文件。
如果您想沿着该路线重新创建,请执行以下操作:
- 创建一个新的控制台项目,.NET 4.0
- 添加另一个类库项目,也是 .NET 4.0
- 从控制台程序项目添加对类库项目的引用
- 粘贴到将下面的 Program.cs 内容粘贴到控制台项目中的 Program.cs 文件中
- 将下面的 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);
}
}
I have found similar questions on this page, but I can't seem to figure out how to interpret the answers or figure out if they are truly duplicates.
Here are the possible duplicates I've found, with comments:
SetWindowsHookEx returns 0 when compiling for the .NET 4.0 framework in 32bit machines
It doesn't seem to return 0 on mine, but I noticed that the handle reported when it crashes (.NET 4.0 on 32-bit) is way different from the handle reported when it runs (.NET 3.5 on 32-bit), like crash handle = 523727, and working handle = 172738378.
Calling SetWindowsHookEx inside VS2008 debugger always returns NULL
I can repro my problem when running outside of Visual Studio
This seems most promising, except that the comments to the deleted answer mentions I should use LoadLibrary and GetProcAddress to load user32.dll in .NET 4.0 since something about loading assemblies changed. I'm pretty sure, however, that it is my own module it can't find, but I don't know if this applies.
The comments in question on the deleted answer to that last one, by Hans Passant, reads:
Are you using .NET 4.0? Its CLR changed the way assemblies are loaded, there is no longer a LoadLibrary call, there won't be module handle for them. Using GetEntryAssembly() instead would be another fix. – Hans Passant May 5 at 19:43
So, what's the word here? Are you using .NET 4.0? Did you try using LoadLibrary("user32.dll") to get a usable DLL handle? – Hans Passant May 6 at 15:43
I'm pretty sure I don't need to do this, but obviously I'm not 100% sure. The question I'm left with if I need to change this, is why it works on 64-bit OS, when compiled for Any CPU
, but doesn't work on 32-bit, in any configuration.
If indeed something has changed regarding loading of .NET assemblies, so that I don't get a proper handle for the class library, I have the following questions:
- Is there any way I can trick this into doing what I want, without having to downgrade to .NET 3.5 or change the hook library to unmanaged?
- Why does it work when running on 64-bit OS, but not on 32-bit?
Background
I have built a program, in .NET 4.0, that uses SetWindowsHookEx with the WH_KEYBOARD_LL hook type to capture key presses. This runs nicely on my 64-bit Windows 7, but crashes with a "module not found" when the keyboard hook is installed on 32-bit Windows 7.
Here's what I've tried:
- Compile for x86, run on 64-bit OS, crashes with "module not found"
- Compile for x86, run on 32-bit OS, crashes
- Compile for Any CPU, run on 64-bit OS, runs nicely
- Compile for Any CPU, run on 32-bit OS, crashes
- Switch to .NET 3.5 and repeat the above four cases, they all work
I'd rather not switch my code to .NET 3.5, since I'm using a few of my class libraries to ease the work, and the latest code is only in .NET 4.0.
You can download a .ZIP-file with everything as a Visual Studio 2010 project if you want, or you can paste in the following two files.
To recreate if you want to go down that route:
- Create a new console-project, .NET 4.0
- Add another class-library project, also .NET 4.0
- Add a reference to the class-library project from the console-program project
- Paste in the Program.cs content below into the Program.cs file you have in the console project
- Paste in the Hook.cs content below into a file in the class-library project. You can paste it into the Class1.cs default file, or add another file. You can not put this into the console project
Then build and run, test various configurations.
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");
}
}
}
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);
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(3)
是的,我想你明白发生了什么事。 SetWindowsHookEx() 需要一个有效的模块句柄,并验证它,但当您设置低级挂钩时,它实际上并不使用它。您只需要一个有效的句柄,具体是哪个句柄并不重要。调用 LoadLibrary("user32.dll") 是获取句柄的好方法,自从您 P/Invoke 其方法后,该 DLL 始终会被加载。它始终由 CLR 引导程序 (mscoree.dll) 加载。不要费心调用 FreeLibrary(),这没有什么区别。
更高版本的 Windows 不再执行此检查。不确切确定它是什么时候开始的,我想是在 Windows 7 SP1 左右的某个地方。可能是为了提供帮助,但会调用“在我的机器上运行,而不是在客户的机器上运行”的故障场景。
Yup, I think you understand what's going on. SetWindowsHookEx() requires a valid module handle, and verifies it, but it doesn't actually use it when you set a low-level hook. You just need a valid handle, it doesn't matter which specific one. Calling LoadLibrary("user32.dll") is a good way to get a handle, that DLL will always be loaded anyway since you P/Invoke its methods. And it is always loaded by the CLR bootstrapper (mscoree.dll). Don't bother calling FreeLibrary(), it makes no difference.
Later versions of Windows no longer perform this check. Not exactly sure when that started, somewhere around Windows 7 SP1 I think. Probably meant to be helpful but invokes the "works on my machine, not the customer's" failure scenario.
这是我的解决方案,适用于 .net 2 和 4。
hInstance 是 ProcessModule.BaseAddress。
Here is my solution that works both in .net 2 and 4.
hInstance is ProcessModule.BaseAddress.
在 .Net 4.0 中,要使此代码正常工作,我必须替换调用:
SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, moduleHandle, 0);
其中:
SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, IntPtr.Zero, 0);
这解决了问题
,当从同一模块进行调用时,这有效。
我从 得到这个这里
In .Net 4.0 for this code to work I had to replace the call:
SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, moduleHandle, 0);
with:
SetWindowsHookEx(WH_KEYBOARD_LL, _Hook, IntPtr.Zero, 0);
this fixed the problem
,This works when the call is made from the same module.
I got this from here