¿Falló SetWindowsHookEx en .NET 4.0 en una máquina de 32 bits con “módulo no encontrado”?

He encontrado preguntas similares en esta página, pero parece que no entiendo cómo interpretar las respuestas o averiguar si realmente son duplicados.

Aquí están los posibles duplicados que he encontrado, con comentarios:

  • SetWindowsHookEx devuelve 0 al comstackr para el marco .NET 4.0 en máquinas de 32 bits

    Parece que no devuelve 0 en el mío, pero me di cuenta de que el identificador informado cuando se bloquea (.NET 4.0 en 32 bits) es muy diferente del manejador reportado cuando se ejecuta (.NET 3.5 en 32 bits), como el identificador de locking = 523727 y el identificador de trabajo = 172738378.

  • Llamar a SetWindowsHookEx dentro del depurador VS2008 siempre devuelve NULL

    Puedo reproducir mi problema cuando se ejecuta fuera de Visual Studio

  • Módulo no encontrado

    Esto parece más prometedor, excepto que los comentarios a la respuesta eliminada mencionan que debo usar LoadLibrary y GetProcAddress para cargar user32.dll en .NET 4.0 ya que algo sobre cargar ensamblados cambió. Sin embargo, estoy bastante seguro de que es mi propio módulo que no puede encontrar, pero no sé si esto se aplica.

Los comentarios en cuestión sobre la respuesta eliminada a esa última, por Hans Passant, dice:

¿Estás usando .NET 4.0? Su CLR cambió la forma en que se cargan los ensamblados, ya no hay una llamada LoadLibrary, no habrá un identificador de módulo para ellos. Usar GetEntryAssembly () en cambio sería otra solución. – Hans Passant, 5 de mayo a las 19:43

Entonces, ¿cuál es la palabra aquí? ¿Estás usando .NET 4.0? ¿Intentó usar LoadLibrary (“user32.dll”) para obtener un manejador de DLL utilizable? – Hans Passant, 6 de mayo a las 15:43

Estoy bastante seguro de que no necesito hacer esto, pero obviamente no estoy 100% seguro. La pregunta que me queda si necesito cambiar esto, es por qué funciona en el sistema operativo de 64 bits, cuando se comstack para Any CPU , pero no funciona en 32 bits, en ninguna configuración.

Si, de hecho, algo ha cambiado con respecto a la carga de ensamblados .NET, de modo que no obtengo un identificador adecuado para la biblioteca de clases, tengo las siguientes preguntas:

  • ¿Hay alguna manera de engañar para que haga lo que quiero, sin tener que cambiar a .NET 3.5 o cambiar la biblioteca de enlaces a no administrada?
  • ¿Por qué funciona cuando se ejecuta en sistema operativo de 64 bits, pero no en 32 bits?

Fondo

He creado un progtwig, en .NET 4.0, que usa SetWindowsHookEx con el tipo de enlace WH_KEYBOARD_LL para capturar las pulsaciones de teclas. Esto funciona muy bien en mi Windows 7 de 64 bits, pero se bloquea con un “módulo no encontrado” cuando el gancho del teclado está instalado en Windows 7 de 32 bits.

Esto es lo que he intentado:

  • Comstackr para x86, ejecutar en sistema operativo de 64 bits, se bloquea con “módulo no encontrado”
  • Comstackr para x86, ejecutar en sistema operativo de 32 bits, lockings
  • Comstackr para cualquier CPU, ejecutar en sistema operativo de 64 bits, se ejecuta muy bien
  • Comstackr para cualquier CPU, ejecutar en sistema operativo de 32 bits, se bloquea
  • Cambie a .NET 3.5 y repita los cuatro casos anteriores, todos funcionan

Prefiero no cambiar mi código a .NET 3.5, ya que estoy usando algunas de mis bibliotecas de clase para facilitar el trabajo, y el último código está solo en .NET 4.0.

Puede descargar un archivo .ZIP con todo como un proyecto de Visual Studio 2010 si lo desea, o puede pegar los siguientes dos archivos.

Para recrear si quieres ir por esa ruta:

  1. Crear un nuevo proyecto de consola, .NET 4.0
  2. Agregue otro proyecto de biblioteca de clases, también .NET 4.0
  3. Agregue una referencia al proyecto de biblioteca de clase desde el proyecto de consola-progtwig
  4. Pegue el contenido de Program.cs a continuación en el archivo Program.cs que tiene en el proyecto de la consola
  5. Pegue el contenido de Hook.cs a continuación en un archivo en el proyecto de la biblioteca de clase. Puede pegarlo en el archivo predeterminado Class1.cs o agregar otro archivo. No puedes poner esto en el proyecto de la consola

Luego crea y ejecuta, prueba varias configuraciones.

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); } } 

Sí, creo que entiendes lo que está pasando. SetWindowsHookEx () requiere un identificador de módulo válido y lo verifica, pero en realidad no lo usa cuando configura un gancho de bajo nivel. Solo necesita un identificador válido, no importa cuál sea específico. Llamar a LoadLibrary (“user32.dll”) es una buena forma de obtener un identificador, esa DLL siempre se cargará de todos modos ya que P / Invoca sus métodos. Y siempre lo carga el CLR bootstrapper (mscoree.dll). No se moleste en llamar a FreeLibrary (), no hace ninguna diferencia.

Las versiones posteriores de Windows ya no realizan esta comprobación. No estoy seguro de cuándo comenzó eso, en algún lugar alrededor de Windows 7 SP1, creo. Probablemente fue útil, pero invoca el escenario de falla “funciona en mi máquina, no en el del cliente”.

Aquí está mi solución que funciona tanto en .net 2 como en 4. hInstance es 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; } } 

En .Net 4.0 para que funcione este código, tuve que reemplazar la llamada:

SetWindowsHookEx (WH_KEYBOARD_LL, _Hook, moduleHandle, 0);

con:

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

esto solucionó el problema. Esto funciona cuando la llamada se realiza desde el mismo módulo.

Tengo esto de aquí