C++/CLI y C# VII: GetLastError en escenarios de interop

Introducción


Hay un tema que ha aparecido indirectamente en algunos mensajes del grupo de C# relativo al tema de Interop mediante atributos (que es el único que puede hacer C# y VB.NET), y que desde que estoy leyendo dicho grupo nadie ha advertido o ha tenido algún problema con él, o al menos yo no me he dado cuenta de ello.


Cuando uno hace una llamada al API de Win32, muchas funciones devuelven un estado de error a través de un handle inválido o simplemente devolviendo FALSE. Es una consecuencia de los tiempos en que ni el compilador ni Windows soportaban ningún modelo de excepción. Era tarea del programador comprobar dicho valor de retorno y luego realizar una llamada a GetLastError(), que devolvía un número y que se podía convertir a una cadena más o menos inteligible mediante una nueva llamada a otra función: FormatMessage.


Si ahora volvemos al entorno manejado, cuando queramos realizar una llamada a través de interop a cualquier función de Win32 que utiliza ese formato para retornar un error, nos encontramos con la duda de qué ocurre entre las dos llamadas, es decir, nuestra llamada al método y una segunda para obtener el error.


El Interop por atributos funciona de la siguiente forma: cuando nosotros hacemos una llamada a través de este sistema, el compilador inserta una cosa llamada thunk, que consiste en una llamada a ejecutar cierto código especial. Entre muchas otras cosas, este código se encarga de traer la DLL de Win32 a nuestro espacio de direcciones (es un decir, porque realmente no trae nada, sino que prepara el contexto para realizar la llamada), realizar la conversión de los parámetros en parámetros compatibles para Win32, ejecutar dicha función y volver a convertir todo el tema en valores que el .NET sea capaz de entender. Lento, muy lento.


Cuando volvamos a llamar a otra función, la operación se repite, por lo que una vez que se ha realizado la llamada a la función que queríamos, cuando hagamos la nueva llamada a GetLastError() repetiremos todo lo de arriba, de forma que hemos perdido el valor, ya que estamos en un nuevo contexto que nada tiene que ver con el anterior.


Si seguimos las reglas tal y como las hemos planteado, el tema tendría un fallo garrafal en cuanto a funcionalidad hacia el API Win32… Pero lo cierto es que los chicos de MS no son tontos, y han solucionado el problema de forma bastante elegante, aunque ineficaz desde un punto de vista del rendimiento, pero creo que excepto permitiendo el Interop al estilo en que se permite en C++/CLI, es la única forma posible de hacerlo.


Cómo funciona por defecto


Cuando hacemos una llamada al API Win32, el sistema de Interop hace por nosotros la llamada a GetLastError() en cada llamada, de forma que dicho resultado se guarda en el contexto. Y podemos accederlo mediante el método Marshal.GetLastWin32Error().


Es decir, cada vez que hagamos una llamada mediante Interop a una función del API de Win32, tendremos disponible el valor devuelto por el GetLastError() de Win32, ya que dicha llamada se ha hecho automáticamente.


Activarlo y desactivarlo


Este funcionamiento viene activado por defecto, y lo cierto es que añade más sobrecarga a un sistema ya sobrecargado de por sí ya que, ante cada llamada a una función de Windows, el sistema hace dos, además de guardar un valor en el TLS (Thread Local Storage) del contexto de ejecución en el que se haya realizado. Y nosotros tendremos que realizar también una nueva consulta a Marshal.GetLastWin32Error(), y si queremos enseñar un mensaje bonito, a FormatMessage() de nuevo, que no es una función sencilla de utilizar y que requiere también un buen trabajo de atributos y de interop.


Una llamada a un método de USER32.DLL podría tener esta firma:


[System.Runtime.InteropServices.DllImport(«user32.dll»)]
extern static void SampleMethod();


De esta forma, estamos indicando el funcionamiento por defecto, es decir, poder obtener el valor del error de Win32.


Pero si especificamos esta forma:


[System.Runtime.InteropServices.DllImport(«user32.dll»)], SetLastError=false]
extern static void SampleMethod();


Estamos indicando al sistema de Interop que no realice la llamada a GetLastError(), de forma que optimizamos en cierta medida los accesos a Win32 siempre y cuando no nos interese determinar si hubo error o no.


Un ejemplo podría ser una llamada a MessageBeep(). Si falla poco podemos hacer, ya que el usuario no oirá nada y punto.


Como siempre, la versión en PDF está aquí.

Visual Studio 2008 final: ¡Al ataque, y cobarde el último!

Hace apenas un rato que Microsoft ha publicado la versión final del Visual 2008.


Aquellos que tengan suscripción a la MSDN del nivel adecuado podrán bajarse la Team Suite x86 x64wow, que por el nombre está claro:



  1. Versión de 32 bits para Windows de 32 bits.

  2. Versión de 32 bits para Windows de 64 bits.

Las versiones Express también han sido liberadas, y se pueden bajar de forma gratuita desde aquí: http://www.microsoft.com/express/.


Evidentemente, son versiones en Inglés, imagino que las localizadas no llegarán hasta su presentación oficial.


Personalmente pienso que hoy es un gran día, ya que, aparte de las novedades (que no son pocas, sobre todo para el mundo manejado, las de C++ llegarán un poco más adelante), todos deseamos que esta versión sea lo que el 2005 quiso ser y no fue. Las versiones que he probado, sin haberlas usado a nivel de producción, así lo indican, pero tendré que verlo funcionar en producción, cosa que haré el jueves si no pasa nada, ya que martes y miércoles estaré fuera y no podré probar nada.


Mucho ojito con una cosa, que no sé si han solucionado en la versión final: si durante la instalación bajo Vista, Windows Update pide reiniciar… NI SE OS OCURRA HACERLO. Esperad a que se termine de instalar el Visual Studio. 


Pues nada, ¡al ataque y buena caza!

Disponible un QFE (parche) para Visual Studio 2005SP1 y C++

Acaban de publicarlo en el blog del Team de Visual C++: Intenta solucionar los enormes problemas que el Intellisense tiene en C++, así como mejorar la experiencia del usuario (joer qué bien queda) en cuanto al funcionamiento del editor en relación con el Intellisense, de forma que sea más cómodo y fácil trabajar, sobre todo cuando se trata de proyectos grandes y/o soluciones con muchos proyectos.

Esta actualización estará disponible e integrada en el no ya tan lejano Visual Studio 2008, y los autores se comprometen a seguir investigando y avanzando en mejorar lo que yo llamo la pifia del Intellisense. Entiendo que es algo muy difícil de integrar en un compilador de C++ con la tira de años y de parches, pero si nos fijamos en soluciones de terceros (como Visual Assist X de Whole Tomato, que tampoco es la panacea pero ayuda un montón -a veces casi "adivina" lo que vas a picar a continuación, y no me refiero a que después de un gcnew te coloque la clase correcta, que por cierto no hace), y que están en ello.

Lo cierto es que la entrada del blog explica muy bien -en pitinglis- qué han cambiado y cómo lo han hecho.

Bueno, finalmente, si no quieren pasar por el blog citado, aquí tienen la descarga directa, que requiere que nos identifiquemos con nuestra ID de Windows Live.