sábado, mayo 12, 2007

El caso de la extensión "rebelde" del menú contextual

Es posible que en alguna ocasión haya experimentado este problema: Al abrir una fotografía en Visor de imágenes y fax y pulsar sobre el icono con la leyenda "Cierra este programa y abre la imagen para editarla (Ctrl+E)", el visor desaparece, pero no se abre Paint o su programa preferido de edición de imágenes.

Probablemente piense que el problema pueda estar en la ruta del Registro que referencie el programa de edición, pero lamentablemente el problema es algo más complicado de detectar: Se trata de alguna extensión del menú contextual con un error de programación muy común. Hace un tiempo identifiqué una versión de WhoLockMe que producía el problema, así que me dispuse a mostrar la causa interna de tan extraño comportamiento.

En primer lugar, describo brevemente lo que ocurre cuando el usuario hace clic sobre el botón del Visor de imágenes y fax para editar la fotografía: En primer lugar se decide qué verbo invocar para abrir el fichero. Éste puede ser "open" o "edit", dependiendo de la configuración de cada sistema. En el Registro dicho verbo está asociado, por defecto, a la ruta "%SystemRoot%\system32\mspaint.exe %1", lo que hace que se abra Paint con la correspondiente imagen cargada. La ruta en cuestión junto al verbo apropiado se incluyen en una llamada a la función ShellExecuteEx, de la que hablé un poquito en un artículo anterior. Esta función realiza, entre otras cosas, una llamada al menú contextual para "preguntarle" a cada manejador: "¿Oye, manejas el verbo Z?", a lo que cada manejador le da una respuesta según su implementación del método InvokeCommand, que obviamente también es llamado cuando el usuario selecciona la opción de la extensión de shell en el menú contextual.


Aquí se le pregunta a la extensión de shell encargada de anclar elementos al inicio: ¿Puedes manejar este verbo y abrir la fotografía?


Poniendo un breakpoint en el retorno de la función y observando el registro EAX (en x86 es común que las funciones devuelvan sus resultados en ese registro), observamos el código 0x80070057, que se traduce por E_INVALIDARG, o lo que es lo mismo, "esto no iba para mí".


"No, lo siento, sólo entiendo de 'pin' y 'unpin', nada más"

A continuación el shell sigue invocando elementos del menú contextual hasta que llega la extensión problemática, la perteneciente a WhoLockMe:

"¿Tú podrías manejar el verbo que te paso y abrir la fotografía?"

A pesar de que no se disponen de símbolos para el módulo "WhoLockMe.dll", es fácil deducir que se trata de la implementación del método InvokeCommand.

Veamos qué nos encontramos al retornar de la función:

¡Adjudicado! La extensión WhoLockMe se ha hecho cargo del verbo.

Como ve, se devuelve S_OK, lo que le hace indicar al shell de Windows que esa extensión va a encargarse apropiadamente del verbo en cuestión, algo que obviamente es un error. Tras ello, el shell deja de consultar elementos del menú contextual, se cierra el visor de imágenes, pero Paint no se abre puesto que el módulo "WhoLockMe" se ha hecho cargo, incorrectamente, de un verbo que no implementa.

También es común que, si añade las opciones "Copiar a" y "Mover a" al menú contextual, tal y como describen muchas páginas en Internet, observe que aparece su cuadro de diálogo asociado en los momentos más imprevistos: Al hacer clic sobre "Reproducir todo" -tras seleccionar un conjunto de canciones-, al abrir un adjunto en Outlook, etc. El motivo es prácticamente el mismo: Esas extensiones están "respondiendo" a más peticiones del shell de las que deberían, ya que sólo están diseñadas para "responder" cuando el usuario haga clic sobre ellas, esto es, deberían estar posicionadas en una barra de herramientas, no en el menú contextual.

La moraleja de todo esto, si hay algún programador de extensiones del menú contextual leyendo esta bitácora :-P, es que si tu extensión no maneja ciertos verbos canónicos ("open", "edit", "print", etc.), el método InvokeCommand que implemente la extensión debe devolver E_FAIL, E_INVALIDARG, para hacerle saber al shell: "Yo no manejo este verbo, sigue preguntando por ahí". En el sitio web de MSDN dispone de información precisa sobre cómo crear manejadores del menú contextual, por ejemplo aquí.

Espero que este artículo les ayude a detectar problemas producidos por extensiones de shell, aprender un poco a depurarlas con WinDbg, por ejemplo, y saber qué es lo que no hay que hacer si en algún momento se dispone a programar alguna.