Colección de citas famosas - Slogan de motivación - C Cómo desactivar el mouse con gancho y cómo escribir

C Cómo desactivar el mouse con gancho y cómo escribir

1. Introducción

Este artículo analizará los enlaces del sistema global. Aplicaciones NET. Con este fin, desarrollé una biblioteca de clases reutilizable y creé el programa de muestra correspondiente (ver imagen a continuación).

Es posible que hayas notado otro artículo sobre el uso de enlaces del sistema. Este artículo es similar, pero con diferencias importantes. Este artículo discutirá los ganchos del sistema global. NET, mientras que los otros artículos sólo analizan los enlaces del sistema local. Estas ideas son similares, pero los requisitos de implementación son diferentes.

En segundo lugar, antecedentes

Si no estás familiarizado con el concepto de ganchos del sistema de Windows, déjame describirlo brevemente:

? Los enlaces del sistema le permiten insertar una función de devolución de llamada, que intercepta ciertos mensajes de Windows (por ejemplo, mensajes relacionados con el mouse).

? Un enlace del sistema local es un enlace del sistema: solo se llama cuando el mensaje especificado es procesado por un hilo separado.

? Un enlace del sistema global es un enlace del sistema: se llama cuando cualquier aplicación en todo el sistema maneja un mensaje específico.

Hay varios buenos artículos que presentan el concepto de ganchos de sistema. No quiero recopilar esta información introductoria aquí, solo remito a los lectores al artículo de antecedentes sobre enlaces del sistema a continuación. Si está familiarizado con el concepto de ganchos del sistema, puede obtener todo lo que pueda en este artículo.

? Conocimiento de la biblioteca MSDN sobre ganchos.

? La ventana delantera de Dino Esposito se engancha. NET marco.

? Aplicación de Hook en C# de Don Kackman.

Lo que discutiremos en este artículo es ampliar esta información para crear un enlace de sistema global, que puede ser. clase NETA. Desarrollaremos una biblioteca de clases usando C# y una DLL y C no administrado; juntos lograrán este objetivo.

Tercero, uso del código

Antes de profundizar en esta biblioteca, echemos un vistazo rápido a nuestros objetivos. En este artículo, desarrollaremos una biblioteca de clases que instala enlaces de sistema globales y expone estos eventos manejados por los enlaces como eventos .NET para nuestras clases de enlaces. Para ilustrar el uso de esta clase de enlace del sistema, crearemos un enlace de evento de mouse y un enlace de evento de teclado en una aplicación de Windows Forms escrita en C#.

Estas bibliotecas se pueden utilizar para crear cualquier tipo de enlace del sistema, incluidos dos enlaces precompilados: enlace del mouse y enlace del teclado. También hemos incluido versiones específicas de estas clases, llamadas MouseHookExt y KeyboardHookExt. Según el modelo establecido por estas clases, puede crear fácilmente enlaces de sistema para cualquiera de los 15 tipos de eventos de enlace en la API de Win32. Además, hay un archivo de ayuda HTML compilado en esta biblioteca de clases completa que archiva estas clases. Si decide utilizar esta biblioteca en su aplicación, asegúrese de leer este archivo de ayuda.

El uso y ciclo de vida de la clase MouseHook es muy simple. Primero, creamos una instancia de la clase MouseHook.

mouseHook = new mouseHook(); //mouseHook es una variable miembro.

A continuación, vinculamos el evento MouseEvent a un método de nivel de clase.

Gancho para ratón. evento de mouse = nuevo gancho de mouse.

controlador de eventos del mouse (gancho del mouse _ evento del mouse);

// ...

enganche del mouse vacío privado _ evento del mouse (eventos del mouse evento me, int x, int y){

Stringsmsg = string.format("Evento del mouse: (,).", mEvent.ToString(), x, y

agregar texto(msg); Agregar mensaje

}

Para comenzar a recibir eventos del mouse, simplemente instale el enlace a continuación.

Gancho para ratón. install hook();

Para dejar de recibir eventos, simplemente desinstale este gancho.

Gancho para ratón. uninstall hook();

También puedes llamar a Dispose para desinstalar el gancho.

Es importante desinstalar este gancho cuando se cierra la aplicación. Mantener los ganchos del sistema instalados ralentiza el procesamiento de mensajes para todas las aplicaciones del sistema. Incluso puede hacer que uno o más procesos sean muy inestables. Por lo tanto, asegúrese de quitar el gancho del sistema cuando termine de usarlo. Nos aseguramos de que el enlace del sistema se eliminara en nuestra aplicación de muestra agregando una llamada Dispose al método Dispose del formulario.

Anulación protegida void Dispose(bool disposing) {

if (dispose){

if (mouseHook!= null) {

Gancho para ratón. disponer();

mouseHook = null

}

// ...

}

}

Este es el caso usando esta biblioteca de clases. Hay dos clases de enlace del sistema en esta biblioteca de clases, que son fáciles de ampliar.

Cuarto, crear una biblioteca

Esta biblioteca tiene dos componentes principales. La primera parte es una biblioteca de C#; puede usarla directamente en su aplicación. A su vez, la biblioteca de clases utiliza internamente una DLL C no administrada para administrar los enlaces del sistema directamente. Primero discutiremos el desarrollo de esta parte C. A continuación, analizaremos cómo usar esta biblioteca en C# para crear una clase de enlace genérica. Mientras analizamos la interacción C/C#, prestaremos especial atención a cómo se asignan los métodos y tipos de datos de C a . NET métodos y tipos de datos.

Quizás te preguntes por qué necesitamos dos bibliotecas, especialmente una DLL C no administrada. También puede observar que los dos artículos de referencia mencionados en la sección de antecedentes de este artículo no utilizan ningún código no administrado. A eso, mi respuesta es: "¡Sí! Por eso escribí este artículo". Nuestra necesidad de código no administrado es muy importante cuando se piensa en cómo los enlaces del sistema implementan realmente su funcionalidad. Para que funcionen los enlaces globales del sistema, Windows inserta su DLL en el espacio de proceso de cada proceso en ejecución. Porque la mayoría de los procesos no lo son. NET, no se pueden ejecutar directamente. Montaje NETO. Necesitamos un agente de código no administrado, algo que Windows pueda insertar en todos los procesos que se engancharán.

El primero es proporcionar un mecanismo para pasar el proxy .NET a nuestra biblioteca C. De esta manera, definimos la siguiente función (SetUserHookCallback) y el puntero de función (HookProc) en lenguaje C.

int setuserhoockcallback(hook proc usuario proc, UINT hookID)

typedef void(CALLBACK * gancho proc)(código int, WPARAM w, LPARAM l)

El segundo argumento de SetUserHookCallback es el tipo de enlace: este puntero de función lo utilizará. Ahora, tenemos que usar C# para definir los métodos y servidores proxy apropiados para usar este código. Así es como lo asignamos a C#.

SetCallBackResults externo estático privado

setuserhoockcallback(HookProcessedHandler hook callback, HookTypes hookType)

Delegado protegido void HookProcessedHandler(int code, UIntPtr wparam, IntPtr lparam)

Tipo de enlace de enumeración pública {

JournalRecord = 0,

JournalPlayback = 1

// ...

Teclado LL = 13,

MouseLL = 14

};

Primero, usamos el atributo DllImport para importar la función SetUserHookCallback como nuestro gancho base abstracto Estático externo método de la clase SystemHook. Para hacer esto tenemos que mapear algunos tipos de datos externos. Primero, debemos crear un proxy que sirva como nuestro puntero de función. Esto se logra definiendo HookProcessHandler arriba. Necesitamos una función con firma C (int, WPARAM, LPARAM). en Visual Studio. NET C, int es el mismo que en C#. Es decir, en C y C#, int es Int32. Este no es siempre el caso. Algunos compiladores tratan a C int como Int16. Nos quedamos con Visual Studio. NET C para implementar este proyecto, por lo que no tenemos que preocuparnos por otra definición provocada por las diferencias del compilador.

A continuación, debemos pasar los valores WPARAM y LPARAM en C#. En realidad, estos son indicadores de los valores C UINT y LONG respectivamente. En C# son punteros a uints e ints. Si no está seguro de qué es WPARAM, puede hacer clic derecho en el código C y seleccionar "Ir a definición" para consultar. Esto le llevará a la definición en windef.h

//desde windef.h:

typedef UINT _ PTR WPARAM

typedef LONG _ PTR LPARAM

Por tanto, elegimos el sistema. UIntPtr y sistema. IntPtr como nuestro tipo de variable: corresponden a los tipos WPARAM y LPARAM respectivamente, cuando se usan en C#.

Ahora, veamos cómo la clase base de enlace utiliza estos métodos importados para pasar funciones de devolución de llamada (proxy) a C: permite que la biblioteca de C llame directamente a instancias de las clases de enlace de su sistema. Primero, en el constructor, la clase SystemHook crea un proxy para el método privado InternalHookCallback, que coincide con la firma del proxy de HookProcessedHandler.

Luego pasa el proxy y su HookType a la biblioteca C para registrar una función de devolución de llamada utilizando el método SetUserHookCallback como se describe anteriormente. La siguiente es su implementación de código:

Enganche del sistema público (tipo HookTypes) {

_ tipo = tipo

_ controlador de proceso = new HookProcessedHandler(InternalHookCallback);

setuserhoockcallback(_ controlador de proceso, _ tipo);

}

La implementación de InternalHookCallback es muy simple. InternalHookCallback lo envuelve con un bloque try/catch general, pasándolo solo a las llamadas al método abstracto HookCallback. Esto simplifica la implementación en clases derivadas y protege el código C. Recuerde, este gancho C llamará a este método directamente una vez que todo esté listo.

[MethodImpl(MethodImplOptions.NoInlining)]

private void InternalHookCallback(int code, UIntPtr wparam, IntPtr lparam){

Probar

grab{}

}

Agregamos un atributo de implementación de método: le indica al compilador que no incluya este método. Esto no es opcional. Al menos lo necesito antes de agregar try/catch. Parece que por alguna razón el compilador está intentando incorporar este método, lo que puede causar todo tipo de problemas al proxy que lo encapsula. Luego, la capa C volverá a llamar y la aplicación fallará.

Ahora, veamos cómo una clase derivada puede usar un HookType específico para recibir y manejar eventos de enlace. La siguiente es la implementación del método HookCallback de la clase virtual MouseHook:

devolución de llamada de gancho nulo de anulación protegida (código int, UIntPtr wparam, IntPtr lparam){

if (MouseEvent == null)

int x = 0, y = 0;

eventos del mouse mEvent = (eventos del mouse)wparam. touint 32();

Switch(mEvent) {

Evento del mouse en caso. Botón izquierdo presionado:

GetMousePosition(wparam, lparam, ref x, ref y);

Break;

// ...

}

evento del mouse(me event, new Point(x, y));

}

Primero, tenga en cuenta que esta clase define un evento del mouse evento: esta clase activa este evento cuando se recibe un evento de enlace. Esta clase convierte datos de los tipos WPARAM y LPARAM en datos significativos de eventos del mouse. NET antes del evento que lo disparó. Esto permitirá que los consumidores de la clase no tengan que preocuparse por interpretar estas estructuras de datos. Esta clase convierte estos valores utilizando la función GetMousePosition importada, que definimos en la DLL de C. Para ello, consulte la discusión en los siguientes párrafos.

En este método, verificamos si alguien está escuchando este evento. En caso contrario, no es necesario continuar procesando el evento.

Luego, convertimos WPARAM al tipo de enumeración MouseEvents. Construimos cuidadosamente la enumeración MouseEvents para que coincida exactamente con sus constantes correspondientes en C. Esto nos permite convertir simplemente el valor del puntero a un tipo de enumeración. Sin embargo, tenga en cuenta que esta conversión se realizará correctamente incluso si el valor de WPARAM no coincide con el valor de enumeración. El valor de mEvent solo será indefinido (no nulo, simplemente no estará dentro del rango de valores de enumeración). Para hacer esto, se requiere un análisis del sistema. La enumeración es un método de definición detallada.

A continuación, después de determinar el tipo de evento que recibimos, esta clase activa el evento y notifica al consumidor el tipo de evento del mouse y la posición del mouse en el evento.

Finalmente, nota sobre la conversión de valores WPARAM y LPARAM: los valores y significados de estas variables son diferentes para cada tipo de evento. Por tanto, en cada tipo de gancho tenemos que interpretar estos valores de forma diferente. Elegí C para implementar esta conversión, en lugar de intentar imitar estructuras y punteros complejos de C en C#. Por ejemplo, la clase anterior usa una función C llamada GetMousePosition. El siguiente es el método en la DLL de C:

bool get mouse position(WPARAM WPARAM, LPARAM lparam, int ampx, int ampy) {

MOUSEHOOKSTRUCT * pMouseStruct = (MOUSEHOOKSTRUCT *) lparam;

x = pMouseStruct- gt; pt.x

y = pMouseStruct- gt.y

Devuelve verdadero

}

En lugar de intentar asignar el puntero de estructura MOUSEHOOKSTRUCT a C#, simplemente lo enviamos de regreso a la capa C temporalmente para extraer el valor que necesitamos. Tenga en cuenta que debido a que necesitamos devolver algún valor de esta llamada, pasamos el número entero como variable de referencia. Esto se asigna directamente a int* en C#. Sin embargo, podemos anular este comportamiento e importar este método seleccionando la firma correcta.

Bool externo estático privado InternalGetMousePosition(UIntPtr wparam, IntPtr lparam, ref int x, ref int y)

Al definir el parámetro entero como ref int conseguimos pasarlo por referencia en C Danos el valor. También podemos usar int si queremos.

Limitaciones del verbo (abreviatura de verbo)

Algunos tipos de ganchos no son adecuados para implementar ganchos globales. Actualmente estoy considerando una solución: permitir el uso de tipos de ganchos restringidos. Por ahora, no vuelva a agregar estos tipos a la biblioteca, ya que provocarán que la aplicación falle (generalmente una falla catastrófica en todo el sistema). La siguiente sección se centrará en las razones y soluciones detrás de estas limitaciones.

Tipo gancho. Procedimiento de ventana de llamada

Tipo de gancho. CallWindowProret

Tipo de gancho. Entrenamiento por ordenador

Tipo gancho. Depuración

Tipo de gancho. Primer plano inactivo

Tipo de gancho. Registro

Tipo de gancho. Registrar reproducción

Tipo de gancho. GetMessage

Tipo de gancho. Filtro de mensajes del sistema

6. Dos tipos de ganchos

En esta sección, intentaré explicar por qué algunos tipos de ganchos están limitados a una determinada categoría, mientras que otros no. Perdóneme si mi terminología es un poco parcial. No encontré ninguna documentación sobre este subtema, así que creé mi propio glosario. Además, si crees que estoy equivocado, dímelo.

Cuando Windows llama a la función de devolución de llamada pasada a SetWindowsHookEx(), el método de llamada será diferente debido a los diferentes tipos de ganchos. Básicamente, existen dos casos: ganchos que cambian el contexto de ejecución y ganchos que no cambian el contexto de ejecución. En otras palabras, es la situación en la que se ejecuta la función de devolución de llamada de enlace en el espacio de proceso de la aplicación donde se coloca el enlace y la situación en la que la función de devolución de llamada de enlace se ejecuta en el espacio de proceso de la aplicación enlazada.

Los tipos de ganchos, como los ganchos de mouse y teclado, cambian de contexto antes de que Windows los llame. El proceso completo es aproximadamente el siguiente:

1. La aplicación X obtiene el foco y se ejecuta.

2. El usuario presiona una tecla.

3.Windows toma el contexto de la aplicación X y cambia el contexto de ejecución a la aplicación enlazada.

4. Windows llama a la función de devolución de llamada del gancho con el parámetro de mensaje clave en el espacio del proceso de la aplicación donde se coloca el gancho.

5.Windows toma el contexto de la aplicación enganchada y cambia el contexto de ejecución nuevamente a la aplicación X.

6.Windows coloca el mensaje en la cola de mensajes de la aplicación X.

7. Después de un tiempo, cuando se ejecuta la aplicación X, toma el mensaje de su propia cola de mensajes y llama a su controlador de botón interno (o suelta o presiona).

8. La aplicación X continúa su ejecución. ...

Por ejemplo, los ganchos CBT (creación de ventanas, etc.) no cambian de contexto. Para este tipo de ganchos, el proceso es más o menos así:

1. La aplicación X se enfoca y se ejecuta.

2. La aplicación X crea una ventana.

3. Windows llama a la función de devolución de llamada de gancho con el parámetro de mensaje de evento CBT en el espacio de proceso de la aplicación X.

4. ...

Esto debería explicar por qué algunos tipos de ganchos funcionan con esta estructura de biblioteca y otros no. Recuerde, esto es exactamente para lo que están las bibliotecas. Después de los pasos 4 y 3 anteriores, inserte los siguientes pasos:

1. Windows llama a la función de devolución de llamada.

2. La función de devolución de llamada de destino se ejecuta en una DLL no administrada.

3. La función de devolución de llamada de destino encuentra su agente de llamada administrado correspondiente.

4. Ejecute el agente administrado con los parámetros adecuados.

5. La función de devolución de llamada de destino regresa y ejecuta el procesamiento de enlace correspondiente al mensaje especificado.

Los pasos tres y cuatro están condenados al fracaso porque no cambian el tipo de gancho. El tercer paso fallará porque la función de devolución de llamada administrada correspondiente no estará configurada para la aplicación. Recuerde, esta DLL utiliza variables globales para realizar un seguimiento de estos agentes administrados y la DLL de enlace se carga en cada espacio de proceso. Sin embargo, este valor solo se establece en el espacio del proceso de la aplicación donde se coloca el gancho. En todos los demás casos, están vacíos.

Tim Sylvester señala en su artículo Otros tipos de ganchos que usar la parte de memoria compartida resolverá este problema. Esto es cierto, pero como señaló Tim, esas direcciones proxy alojadas no tienen significado para ningún proceso excepto para la aplicación que se está enganchando. Esto significa que no tienen sentido y no se pueden llamar durante la ejecución de la función de devolución de llamada. Eso puede causarle problemas.

Entonces, para utilizar estas funciones de devolución de llamada con tipos de enlace que no realizan cambios de contexto, necesita algún tipo de comunicación entre procesos.

Probé esta idea: usar objetos COM fuera de proceso en la función de devolución de llamada de enlace DLL no administrado de IPC. Si puedes hacer que esto funcione, me encantaría saberlo. En cuanto a mis intentos, los resultados fueron insatisfactorios. La razón básica es que es difícil inicializar correctamente la unidad COM (CoInitialize(NULL)) para varios procesos y sus subprocesos. Este es un requisito básico antes de utilizar objetos COM.

No dudo que haya una manera de solucionar este problema.

Pero aún no los he probado porque los encuentro de uso limitado. Por ejemplo, el gancho CBT le permite cancelar la creación de ventanas si lo desea. Puedes imaginar lo que pasaría para que esto funcione.

1. La función de devolución de llamada de gancho comienza a ejecutarse.

2. Llame a la función de devolución de llamada del enlace correspondiente en la DLL del enlace no administrado.

3. La ejecución debe enrutarse de regreso a la aplicación de enlace principal.

4. La aplicación deberá decidir si permite esta creación.

5. La llamada debe redirigirse a la función de devolución de llamada del enlace que aún se está ejecutando.

6. La función de devolución de llamada del enlace en la DLL del enlace no administrado recibe la operación que se realizará desde la aplicación del enlace principal.

7. La función de devolución de llamada del enlace en la DLL del enlace no administrado toma la acción adecuada en la llamada del enlace CBT.

8. Complete la ejecución de la función de devolución de llamada del gancho.

Esto no es imposible, pero no es bueno. Espero que esto aclare el misterio que rodea a los tipos de ganchos permitidos y restringidos en esta biblioteca.

Siete. ¿Otros

? Documentación de la biblioteca: hemos incluido documentación de código relativamente completa para la biblioteca de clases ManagedHooks. Visual Studio.NET convierte esto en XML de ayuda estándar al compilar en la configuración de compilación "Documentación". Finalmente, usamos NDoc para convertirlo en Ayuda HTML compilada (CHM). Puede leer este archivo de ayuda haciendo clic en el archivo Hooks.chm en el navegador de soluciones de Scheme o buscando el archivo ZIP descargable asociado con este artículo.

? IntelliSense mejorado: si no está familiarizado con cómo Visual Studio.NET utiliza archivos XML compilados (salida anterior a NDoc) para proyectos de biblioteca de referencia para mejorar IntelliSense, permítame explicarle brevemente. Si decide utilizar la biblioteca en su aplicación, considere copiar una compilación estable de la biblioteca en la ubicación donde desea hacer referencia a ella. Al mismo tiempo, copie el archivo del documento XML (enlaces del sistema\enganches administrados\Bin\Debug\Kennedy.ManagedHooks.xml) y muévalo a la misma ubicación. Cuando agrega una referencia a la biblioteca, Visual Studio.NET leerá automáticamente este archivo y lo usará para agregar documentación de IntelliSense. Esto es muy útil, especialmente con bibliotecas de terceros como ésta.

? Pruebas unitarias: creo que todas las bibliotecas deberían tener pruebas unitarias correspondientes. Como soy socio e ingeniero de software en una empresa (principalmente responsable de las pruebas unitarias de software en el entorno .NET), nadie debería sorprenderse. Por lo tanto, encontrará un proyecto de prueba unitaria en la solución llamado ManagedHooksTests. Para ejecutar esta prueba unitaria, debe descargarlo e instalarlo. Esta descarga es una versión de prueba gratuita de nuestro software de prueba unitaria comercial. En esta prueba unitaria, presté especial atención a esto: aquí, los parámetros no válidos del método pueden causar una excepción en la memoria C. Aunque la biblioteca es bastante simple, esta prueba unitaria realmente me ayuda a detectar algunos errores en situaciones más sutiles.

? Depuración administrada/no administrada: una de las cosas más complicadas de las soluciones mixtas (como el código administrado y no administrado en este artículo) es la depuración. Si desea recorrer el código C o establecer puntos de interrupción en el código C, debe habilitar la depuración no administrada. Esta es la configuración del proyecto en Visual Studio.NET. Tenga en cuenta que puede depurar niveles administrados y no administrados en un solo paso sin problemas, pero la depuración no administrada ralentiza considerablemente el tiempo de carga y la velocidad de ejecución de su aplicación durante la depuración.

8. Advertencia final

Obviamente, los ganchos del sistema son bastante poderosos, sin embargo, este poder debe usarse de manera responsable; Cuando los enlaces del sistema fallan, no sólo bloquean la aplicación. Pueden bloquear todas las aplicaciones que se ejecutan actualmente en el sistema. Pero la posibilidad de alcanzar este nivel es generalmente muy pequeña.

Sin embargo, cuando utilice enlaces del sistema, debe volver a verificar su código.

Descubrí una técnica útil para desarrollar aplicaciones: utiliza enlaces del sistema para instalar una copia de su sistema operativo de desarrollo favorito y Visual Studio.NET en una PC virtual de Microsoft. Luego podrá desarrollar su aplicación en este entorno virtual. De esta manera, cuando ocurre un error en sus aplicaciones enganchadas, solo saldrán de la instancia virtual de su sistema operativo, no de su sistema operativo real. Cuando este sistema operativo virtual falló debido a un enlace defectuoso, tuve que reiniciar mi sistema operativo real, pero eso no sucedió muy a menudo.