SetWindowsHookEx 在 32 位機器上的 .NET 4.0 中以“找不到模組”失敗?
我在此頁面上發現了類似的問題,但我似乎無法弄清楚如何解釋答案或弄清楚它們是否真的重複。
以下是我發現的可能重複項,並附有評論:
它似乎沒有返回 0,但我注意到它崩潰時報告的句柄(32 位上的 .NET 4.0)與它執行時報告的句柄(32 位上的 .NET 3.5)有很大不同,比如崩潰句柄 = 523727,工作句柄 = 172738378。
在 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% 確定。如果我需要更改它,我留下的問題是為什麼它在編譯為 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 項目,也可以粘貼以下兩個文件。
如果你想沿著這條路線重新創建:
- 創建一個新的控制台項目,.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); } }
是的,我想你明白髮生了什麼。SetWindowsHookEx() 需要一個有效的模組句柄,並對其進行驗證,但在設置低級掛鉤時它實際上並沒有使用它。您只需要一個有效的句柄,哪個特定的句柄都沒有關係。呼叫 LoadLibrary(“user32.dll”) 是獲取句柄的好方法,因為您 P/Invoke 它的方法,DLL 將始終被載入。它總是由 CLR 引導程序 (mscoree.dll) 載入。不要打擾呼叫 FreeLibrary(),它沒有區別。
更高版本的 Windows 不再執行此檢查。不完全確定什麼時候開始的,我想是在 Windows 7 SP1 附近的某個地方。可能意味著有幫助,但會呼叫“在我的機器上工作,而不是客戶的”失敗場景。