El pan nuestro de cada día…

… o el padre de todos los bugs (de momento).


Una aclaración (añadido posterior)
Por la serie de comentarios recibidos pudiera parecer que mi aplicativo no está funcionando correctamente, y lo cierto es que tras releer detenidamente la entrada completa me he dado cuenta de que efectivamente así lo parece. Quizás haya pecado de ser demasiado obtuso y/o abstruso a la hora de contar el tema, pero la aplicación YA ESTÁ FUNCIONANDO correctamente sin ningún problema. Diciéndolo en pocas palabras, yo estaba cometiendo un montón de errores en los constructores, errores que generaban excepciones que no se lanzaban donde debían, con el apoteósico final del tema del Orcas. Y todo el comentario viene por eso: parece ser que la máquina virtual .NET no es capaz de lanzar bien las excepciones en esos lugares, y nada más, cosa que al final he terminado confirmando al compilarlo con el Orcas, me ha saltado la excepción que faltaba en el lugar exacto, he visto el fallo cometido por mi, y santas pascuas. 🙂


Y ahora el post original:
Voy a intentar explicar mi última aventura dentro del C++/CLI. Y digo última porque abandono el lenguaje y de momento siempre que pueda haré las cosas en C#, y cuando necesite interop IJW, lo meteré todo en un ensamblado hecho en C++/CLI y lo usaré desde C#. Espero así tener menos problemas, que el IDE se pueda manejar sin que se arrastre mucho, y creo que dejaré de padecer problemas como el que voy a contar ahora. Si el C# fuera tan buggy como el C++/CLI, lo más seguro es que abandone el .NET definitivamente para mis proyectos profesionales, aunque todavía no lo tengo decidido del todo.


La aplicación
Mi último proyecto es una aplicación que irá en un quiosco, completamente autónoma. Aunque no es muy grande, sí que es compleja, pues controla varios dispositivos que yo llamo exóticos (aunque para mí un dispositivo exótico sea un servidor de bases de datos) a través de USB y de canales serie, emplea sockets para conectarse al mundo exterior, y procesa unos 100 megas en elementos gráficos y sonidos.


Este tipo de desarrollos requieren variables globales, pero por desgracia el .NET no las permite, y realmente no entiendo por qué, puesto que tampoco es un entorno Orientado a Objetos puro, y menos aún con las novedades que trae el C# 3.0. Por lo tanto tengo que utilizar clases globales estáticas para realizar el mismo trabajo, pero este tipo de clases tienen una contrapartida, y es que no sabes cuándo se van a instanciar realmente, y en teoría deberían estarlo justo antes de entrar en main, aunque el C++/CLI retarda a veces dicha creación hasta que se llama a cualquier método de la misma, y entonces ejecuta el constructor estático, cosa que para un sistema en tiempo real es inaceptable, por lo que al final lo he tenido que agrupar todo en una clase global que he llamado, ya lo habrán adivinado, Globals. Dentro de esta clase instancio objetos miembro estáticos, que llamo en tiempo de ejecución mediante propiedades:



if(Globals::Devices->InPowerFail) …


Otros detalles de implementación
Una de las cosas que hace la aplicación es consultar periódicamente si tiene actualizaciones para bajárselas e instalarlas, al más puro estilo Windows Update. Todo ese código está en una pareja de ficheros llamada UpgradeEngine.h/UpgradeEngine.cpp. Ahí dentro hay clases para consultar el estado del sistema remoto, para bajarse lo que quiera que tenga que bajarse y para realizar la descompresión y la pertinente instalación, todo ello cuelga de un método global que llamo dentro de main antes de instanciar la ficha principal. La implementación del método global está al final del fichero, y todas las clases por encima de él, y este método global abre un fichero local, mira si le toca y sólo entonces instancia los objetos pertinentes.


Hay muchas más clases que son globales y estáticas, algunas de ellas crean hilos que van a monitorizar los periféricos, que deben estar disponibles lo antes posible para evitar efectos indeseados, otras simplemente precargan parte de los gráficos y de los sonidos, y otras son meros elementos de soporte.


El primer problema
Todos los programadores somos humanos y nos equivocamos. Partiendo de ese principio, y teniendo en cuenta que aproximadamente el 20% de la aplicación está en los constructores globales estáticos, vamos a tener un 20% del total de los bugs en dichos constructores. Y esos bugs, a veces, van a lanzar excepciones, excepciones que deberían ser manejadas primero por un controlador local si lo hubiera y luego por uno global, terminando en todo caso en el de la máquina virtual.


Eso si no hay bugs dentro de esos controladores. Porque si los hay, podría ocurrirte lo que a mi. Haces una pifia, lanzas la aplicación y ¡tachán!, excepción al canto. Bien, en la primera línea del primer método que haya en UpgradeEngine.cpp. Pero si esa clase ni siquiera se está instanciando ahora, y ahí no he tocado nada, y ni siquiera se ha llamado al código que decide si se instancia o no esa clase (recordemos, estamos en un constructor estático global, antes de entrar en main).


Miras el código que has modificado y descubres la pifia, pero te sigue rondando por la cabeza el hecho de que no se haya disparado ahí la excepción. Pero sigues picando código, y vuelves a cagarla. Nueva excepción, que termina en el mismo sitio que antes, excepción generada en otro constructor estático en la otra punta de la aplicación. La cosa ya te mosquea un poco.


Cambias el orden de las clases en UpgradeEngine.cpp, y resulta que las excepciones se siguen capturando en la primera línea del primer método de la primera clase que haya en dicho fichero, se produzcan en el constructor estático que sea. Y entonces empieza a subir la presión sanguínea.


Ya puestos a probar cosas, haces una construcción en release que sabes va a lanzar una excepción en algún lugar, y si ejecutas desde el IDE el comportamiento es el mismo, pero si lanzas la aplicación por sí sola, peta la máquina virtual entera y se cierra todo. No es que te diga que se ha generado la excepción tal o cual, no, sino que lo que se rompe es la máquina virtual completa, el programa nativo que es el propio .NET.


Y entonces decides visitar ese gimnasio que pagas pero que casi nunca usas, y que ahorita mismo te viene al pelo.


El segundo problema
Ya repuesto de lo anterior, estás terminando el aplicativo, sólo te quedan un par de cosas y que te lo revisen y te saquen bugs. Estás contento con el trabajo bien hecho. Te traen el quiosco para que metas tu aplicación en él.


Y comienza el baile de nuevo.


La máquina virtual se cae sola. En tu PC todo va bien, en la placa final ni siquiera obtienes excepciones, sino que la propia VM .NET se cae miserablemente. Bueno, son cosas que pasan. Intentas depurar de forma remota y no puedes porque el ordenador remoto es un XP Home… Los compis que están haciendo la integración en la cadena de montaje también te vienen con pegas, en algunas instalaciones (todas ellas clonadas), la VM salta con excepciones, en otras simplemente se cae. Me traen una que genera casi siempre excepciones y las excepciones tienen el mismo sentido que cuando me pasaba lo que he explicado en el punto anterior, o sea, ninguno. Y de diez máquinas idénticas en pruebas, sólo dos generan excepciones, y las ocho restantes simplemente se caen. Y no son problemas de hardware, porque intercambias componentes y no tienen relación alguna con los fallos…


El intermedio
Hora del kitkat. O de mandarlo todo a tomar por c*l*. Se ha bajado la máquina virtual del Orcas, y tras los problemas con la clave comentados en otro post, me pongo a jugar con él. Una de las pruebas es recompilar mi proyecto C++/CLI actual con este entorno, que por cierto es idéntico al Visual Studio 2005.


Lanzo la aplicación y… y… y… ¡Joder, una excepción en pleno código del UpgradeEngine.cpp! ¡Pero no en la primera línea del primer método de la primera clase, si no en otro sitio! Y hay una traza a un fichero que ni existe ni tiene una ruta válida si no es en mi máquina de desarollo, y ocurre siempre que la máquina NO tenga que actualizar…


¡Alabados sean los chicos-chapuza de Microsoft!


Las preguntas sin resolver
Está claro que el problema estaba en esa línea, pero el asunto no queda lo suficientemente explicado, y aunque en un principio la culpa sea mía por olvidar quitar esa traza, el compilador del Visual Studio 2005 (versión 14.00.50727.762) debería haberse comportado como el del Orcas (versión 15.00.11209).


Otra pregunta sin resolver es por qué no saltaba la excepción en esa línea en mi placa de pruebas (diferente de todas la demás, es una VIA EPOC de esas tiny).


Otra más es por qué en release la máquina virtual .NET se cae entera.


Otra es por qué en ciertas máquinas daba una excepción sin sentido, en otras no y en otras más se caía la VM siendo estas máquinas completamente idénticas.


Más: ¿Por qué cuando hacía una pifia en otro lugar, en lugar de saltar la excepción en ese lugar y con el tipo concreto, saltaba la otra y encima sin sentido alguno?


Posible explicación
Voy a intentar dar una explicación del comportamiento observado, ya que explicar todos los pasos dados antes de compilar la aplicación con el Orcas sería demasiado largo, y encima está el hecho de que al poner trazas interfería en la secuencia, pero creo que mis conclusiones son correctas.


Una aplicación .NET tiene al menos 8 hilos aunque nosotros no creemos ninguno. Supongo que esos hilos están ahí para el recolector de basura, el precompilador, el scheduler de código, el optimizador, el intérprete de la máquina virtual y otros menesteres similares.


Por lo tanto, mientras se está ejecutando código por un lado, seguro que por el otro se está precompilando, optimizando o simplemente realizando otras tareas de mantenimiento, por lo que aunque pensemos en una máquina de von Newman secuencial, realmente no es así (bueno, sí que es así, pero a efectos prácticos no).


Imagino el siguiente escenario: por un lado un hilo está ejecutando los constructores estáticos, y por el otro está preparando el entorno para entrar en main, por un lado se produce una excepción en un constructor estático y por el otro en el código con la traza mal puesta. Entran en acción los manejadores de excepciones, mientras uno busca un manejador, el otro también lo hace, o es entonces cuando mientras una parte de la vm está buscando un manejador la otra ejectua el código que genera una nueva excepción, de forma que se arma la picha un lío y sale por donde puede.


Como en debug hay más código oculto que en release, los tiempos son diferentes, así como las comprobaciones, y dado que la máquina virtual .NET es algo vivo, en determinadas condiciones se produce ciertas secuencias que llevan a su total caída, en otras a salir por donde puede, y finalmente en alguna más a poder lanzar una excepción más o menos válida.


Creo no equivocarme, pero simplemente toda esta explicación podría ser una mera paja mental, así que lo único que puedo afirmar y reafirmar es que


Sigo pensando que hay algo podrido dentro de la máquina virtual .NET

La clave de acceso para el Orcas CTP de enero no funciona (y solución)

Estaba yo más emocionado que una virgen pueblerina el día de su primera boda, esperando pacientemente que se bajaran los tropecientos mil megas de la CTP, tanto, que dejé el ordenador encendido toda la noche porque me faltaba un fichero y ya no podía más con el sueño…


Esta mañana, inocente yo, descomprimo los casi 15 Gigas de máquina virtual (casi nada, espero que esta CTP lleve una espuerta de Gallinfantes de regalo), me pongo al tajo, y ¡oh despiste!, descubro que no anoté la clave que venía por defecto… Bueno, eso tiene solución, vuelvo a la página de descarga y descubro que el usario clave es



Administrator/P2ssword.


Pruebo y joer, no entra. Ni siquiera copiando y pegando el texto en un Virtual PC 2007. Entonces me vino a las mientes otra CTP que no pude probar porque a los chicos de Microsoft se les olvidó que los teclados no americanos tienen algunas letras en otros sitios, ya que, muy c00l ellos, habían puesto una arroba en ella; vamos, para no ser menos y seguir cagándola.


Pues esta vez, armado de mi cabezonería habitual, me he dicho: «aunque tenga que juakear el 2003 Server este, editar a mano el disco duro virtual, reventar a golpes la máquina virtual, por mis O-O, entro y veo el nuevo Orcas.»


Pero al final no ha sido para tanto el asunto, sólo he tenido que buscar un poco por internet, porque como ya es habitual en los chicos pifiosos de Microsoft, la han vuelto a cagar, ya que la clave no es la que viene en la página de descarga, sino:



P2ssw0rd


Vamos, que la ‘o’ es un cero.


Otra cosa pendiente es ver qué hay dentro de esa máquina virtual, pero como decía una pareja de hecho hace unos años, «la próxima semana, hablaremos del gobierno».

Comentarios al "Rationale" de Sutter

Hace unos meses, con la inestimable colaboración de Octavio Hernandez, traduje (tradujimos) el Rationale de Sutter. Fue toda una odisea por lo críptico que escribe ese señor, y por lo críptico del tema en sí. El que todavía no se haya enterado de que puede leer el Rationale en castellano, lo puede obtener de aquí.


Pues bien, este mes, en la MSDN Magazine de Microsoft, Paul DiLascia comenta el Rationale, de hecho lo parafrasea con bastante buen tino y acierto, en muy pocas y entendibles palabras, cubre casi todos los aspectos del documento original.


Si alguien ha intentado leer el Rationale en inglés y se ha hecho la picha un lío por lo difícil, o quizás lo haya intentado con nuestra traducción al castellano con el mismo efecto, ahora puede atisbar su contenido, también en castellano, sin grandes dificultades gracias Paul, que, por desgracia, deja de escribir para la revista.


Lo único achacable al documento es la traducción al castellano, que como comprobará quien la lea, queda un tanto «peculiar».

Solución a "Obtener el estado de la impresora a través de .NET", y más bugs (y II)

Visto el éxito obtenido, que nadie ha sido capaz de detectar el bug, ya sea porque no me leen, o porque ya ni se acuerden de Win32, voy a poner aquí la soución al mismo.


También debo dar las gracias a Bartomeu (ver los comentarios a la entrada equivalente al post anerior en mi otro blog, aquí) por haberme hecho notar cierto problema del que no me había dado cuenta; no es que fuera peligroso, pero sí un despiste.


El bug, que clama al cielo, es no liberar la memoria asignada con GlobalAlloc, por lo que cada vez que uno entra en la función, va asignando memoria que nunca llega a liberar. Lo que me hace  notar Bartomeu es que no se necesita ni un solo goto para realizar un código mucho más limpio (cosa que ya había implementado yo en mi propio código), amén de que no debo cerrar el handle si falla la llamada OpenPrinter, ya que no existe ningún manejador válido.


El código definitivo es el siguiente:



bool IsPrinterOk(String ^printerName)
{
    bool bResult=false;
    HANDLE hPrinter;



    IntPtr pPrinterName=Marshal::StringToHGlobalUni(printerName);
    WCHAR *pChar=(WCHAR *)pPrinterName.ToPointer();


    if(::OpenPrinter(pChar,&hPrinter,NULL)==0)
        return false;


    DWORD bytesNeeded;
    ::GetPrinter(hPrinter,2,0,0,&bytesNeeded);
    PRINTER_INFO_2 *pf=(PRINTER_INFO_2 *)GlobalAlloc(GPTR,bytesNeeded);


    if(::GetPrinter(hPrinter,2,(LPBYTE)pf,bytesNeeded,&bytesNeeded)!=0)
        if(pf->cJobs==0 && pf->Status==0)
            bResult=true;


    GlobalFree(pf);


    ClosePrinter(hPrinter);


    return bResult;
}


Observamos cómo ahora sí que se libera el puntero para albergar a PRINTER_STATUS_2.


Y ahora, más bugs
Es curioso esto del .NET, muy curioso, y conforme va pasando el tiempo le voy teniendo mucho más miedo. En un próximo post lo explicaré, y explicaré el que considero ya el summum de todos los bugs habidos y por haber…


Si recuerdan, en la anterior entrada, la función era estática. Pero si es estática, GlobalFree genera una excepción y tumba la aplicación por completo. No sé a qué pueda deberse, y perfiero no investigarlo.


¿Solución? Definirla como un método normal y corriente. Pero entonces, oh maravilla de todas las maravillas, el compilador me dice que el cuerpo de la función está redefinido. Bien. Si es estática no puedo ponerla en un CPP, si no lo es, tengo que ponerla en uno.


¿Y por qué me dice que el cuerpo de la función está redefinido? El tema es muy sencillo, y realmente podríamos catalogarlo como «feature» más que como bug, pero lo cierto es que el asunto nos da una idea bastante pobre de las interioridades del compilador.


El proceso de parseo de un proyecto C# es sencillo: se parsean todos los ficheros del proyecto uno detrás de otro como si fueran uno solo sobre una misma tabla de símbolos, y luego, en la siguiente pasada se resuelven los símbolos y se pasa todo a código objeto, que el enlazador juntará con poco más.


Esto en C++/CLI no se puede hacer así, ya que violamos el concepto de compilación separada. En C++ primero se parsean las parejas de ficheros cabecera/cuerpos, y luego se juntan y se enlazan. C++/CLI adopta una filosofía intermedia entre C# y el código nativo: primero se compilan las parejas, se juntan todas las tablas de símbolos de todas las parejas, y entonces se vuelven a parsear como si de C# se tratara. Por eso, en otros sitios, he dicho que el código generado con  C++/CLI es de mejor calidad que el de, por ejemplo, C#.


Lo que ocurre es, pues, que se obtiene dos veces un método IsPrinterOk, la primera cuando se parsea su propio fichero y la segunda cuando se junta todo. Y el compilador protesta en lugar de mirar si se trata del mismo código. Y también ese es el motivo por el cual algunos errores y warnings aparecen duplicados en un proyecto, aquellos que sólo se producen en la segunda pasada aparecen una sóla vez, y dos en los que se generan tanto en la primera como en la segunda.


Y eso nos da una ligera idea primero de los esfuerzos y de la complejidad del compilador de C++/CLI, y segundo de que no todo está claro y que el compilador es bastante chapucero.

Obtener el estado de la impresora a través de .NET

Entre la ingente cantidad de carencias que adolece el .NET Framework, una de las más hirienes tanto para los desarrolladores de sistema/escritorio como casi para cualquier otro desarrollador es poder obtener el estado de una impresora para evitar enviar inútilmente un trabajo a su cola o, simplemente, para informar de manera elegante de que no tiene papel, éste está atascado o simplemente que el dispositivo se encuentra en estado de «Off Line».


Partimos del punto de que mediante Win32 es muy fácil obtener dicho estado (desde el punto de vista del desarrollo Win32, claro); no será, por tanto, demasiado complicado poder obtenerlo mediante Interop, y menos aún desde C++/CLI. Veamos primero el ejemplo y estudiémoslo después (por favor, que los puristas del C++ se armen de valor y no desmayen al ver el código mostrado hasta leer las explicaciones. No me hago responsable de taquicardias ni de ingresos por síncopes.)



using namespace System;
using namespace System::Runtime::InteropServices;


static bool IsPrinterOk(String ^printerName)
{
    bool bResult=false;
    HANDLE hPrinter;
    IntPtr pPrinterName=Marshal::StringToHGlobalUni(printerName);
    WCHAR *pChar=(WCHAR *)pPrinterName.ToPointer();
    if(::OpenPrinter(pChar,&hPrinter,NULL)==0)
        goto fin;


    //PUNTO A
    DWORD bytesNeeded;
    ::GetPrinter(hPrinter,2,0,0,&bytesNeeded);
    PRINTER_INFO_2 *pf=(PRINTER_INFO_2 *)GlobalAlloc(GPTR, bytesNeeded);
    if(::GetPrinter(hPrinter,2,(LPBYTE)pf,bytesNeeded,&bytesNeeded)==0)
        goto fin;


    if(pf->cJobs!=0)
        goto fin;



    if(pf->Status!=0)
        goto fin;


    bResult=true;


fin:
    ClosePrinter(hPrinter);
   
return bResult;
}


Lo primero que llama la atención es que se trata de un método suelto nativo insertado en cualquier sitio de nuestro proyecto (En mi caso, PrinterStatus.h). Ventajas del Interop del C++/CLI.


Lo segundo son los gotos. Ya dije que los puristas se tomaran esto con calma. ¿Qué hemos ahorrado con añadir los goto? Pues tener que poner llaves en los if y tener que cerrar la impresora hasta cuatro veces, así como salir del programa por cinco sitios. (Aquí podríamos detallar una guerra purista sobre si es coherente salir de un método por un solo sitio o por varios, y qué ventajas tendría sobre una implementación con goto o con cadenas de if anidados). También ahorramos tiempo de ejecución. todos esos if se solcionan en ensamblador con un salto condicional corto, del estilo JNE o JE.


Además, cualquier otro código escrito aquí con la misma funcionalidad, si el compilador es bueno, terminaría ejecutando algo como lo hemos escrito, de forma que nosotros le ahorramos algo de trabajo al optimizador.


La segunda cuestión interesante está en el PUNTO A. Quizás escandalicen esas dos llamadas a GetPrinter, pero es una técnica habitual en Win32 cuando no se conoce el tamaño de un buffer. Personalmente no es que esté de acuerdo con ella, pero así es como lo han pensado los gurús de MS, y tampoco es que sea una mala idea, aunque poco ortodoxa. Quizás hubiera sido de desar que la llamada a GetPrinter asignara un bloque estático que luego el usuario debiera liberar (ya sé que esto es bastante peligroso), o simplemente devolver un buffer de sólo lectura y que el usuario lo copie si lo va a modificar.


El código se explica por sí solo. Primero convertimos el String pasado a un puntero nativo, luego abrimos la impresora. Si no podemos hacerlo es que o no tenemos permisos o falla algo, de modo que tampoco podremos imprimir ni hacer nada más. El segundo bloque solicita datos del tipo PRINTER_INFO_2, que es donde se encuentra almacenada la información que nos interesa. Quien desee profundizar en este tema, que se vaya a la MSDN y mire dichas estructuras y la llamada a función GetPrinter. Los últimos pasos comprueban los campos que nos interesan (en mi caso es lo único que necesito) y devolvemos cierto si la impresora no tiene trabajos pendientes y su estado es el correcto.


Otra cosa es que el driver de la impresora esté bien hecho y la información devuelta sea la correcta, lo que no siempre acontece, o más bien casi nunca.


Más bugs para el C++/CLI
Como es habitual y estoy acostumbrado ya, con cada cosa nueva que pruebo con C++/CLI, me suelo encontrar a veces no con un bug, sino con varios. En este caso, y sólo en el trozo de código anteror, he tenido la enorme suerte de encontrarme con una cosa muy curiosa.


El código de arriba está situado en un fichero h. ¿Por qué? La primera versión tenía la definición de la función global IsPrinterOk en un h y la implementación en su correspondiente cpp, pero el compilador se veía incapaz de encontrar la implementación al método citado. Pero si redefinía un cuerpo tanto en el fichero cabecera como en el cpp, el compilador me decía que la función tenía dos implementaciones idénticas.


Luego se me quejan si empiezo a evaluar seriamente pasarme al lado oscuro mundo Open Source. Allí al menos sé que no voy a tener soporte, que me voy a encontrar cantidad de bugs que nadie va a solucionar, y a que todo está manga por hombro. Pero al menos parto desde el punto de vista de que ya sé qué me voy a encontrar, y no como aquí, en el que me encuentro en la misma situación… pero sin saberlo de antemano.


Y siempre me quedará, claro está, la opción de intentar solucionármelo yo mismo, cosa que, siendo casi una falacia evidente pero una posible realidad, dentro del mundo propietario no lo es de partida.


Y uno para mi
El código de arriba contiene al menos un bug muy pero que muy serio. ¿Quién lo va a adivinar? Un gallinfante de regalo. 🙂

wxWidgets con Visual Studio 2005

Buscando por ahí frameworks alternativos a .NET y MFC, he encontrado por ahí dos candidatos muy interesantes (bueno, realmente no ha sido así, sino que ya sabía de ellos, pero queda más bonito decirlo de la otra forma). Es muy posible que el lector ya los conozca, y quizás los esté usando.


La diferencia más importante respeco a .NET consiste en que estos sí que son multiplataforma de verdad, es decir, sus aplicaciones se pueden ejecutar con una simple recompilación tanto en Windows, MAC o Linux, y a veces en otras plataformas más. Ya sé que el .NET no necesita recompilación, pero a la experiencia me remito: las compilaciones «AnyCPU» del .NET son erráticas, presentan un comportamiento diferente y en general terminan haciendo explotar la máquina virtual .NET en cuanto les das algo de caña.


Una de las alternativas es, ya lo habrán adivinado, las QT de Trolltech (qué curioso que una empresa se llame El troll tecnológico, por algo será). Esta biblioteca tiene, desde mi punto de vista, dos contrapartidas: es muy cara (unos 3.000 euros/año) y, al igual que el C++ para .NET, no es un C++ puro. En este caso es necesario un metacompilador que nos generará las clases a partir de lo realizado con los generadores visuales. Desde mi punto de vista adolece del mismo problema que el C++/CLI.


Evidentemente, existe la versión Open Source, pero con ella no se pueden realizar proyectos propietarios lo que, sumado al hecho de no ser un C++ puro, me retrae a probarla.


La otra que nos queda es, ya lo habrán adivinado, wxWidgets, antes wxWindows, nombre que Microsoft consiguó cambiar a golpe de talonario. Esta biblioteca no integra RTTI ni excepciones, cosa que no creo que tenga mucha importancia. Con ella se han construido muchos programas de éxito y, por tener, hasta tiene un IDE creado con ella misma. Este IDE se llama Code::Blocks, y tiene una pinta inmejorable, sobre todo las nighty builds. Pero como la mayoría de las cosas Open Source, está a medio hacer y tiene más problemas que cosas funcionan o, más bien, todo funciona pero no lo hace del todo bien.


Por ello, tras descartarlo, me decidí a integrar wzWidgets en el Visual Studio 2005. Integrar es un decir, porque lo único que he hecho es hacer que el VS2005 compile y trabaje con dicha biblioteca. Existen soluciones de integración comerciales, pero no creo que sean muy necesarias, ya que el IntelliSense con código nativo funciona correctamente en el VS2005 y el archivo de ayuda se puede abrir a mano.


PRIMER PASO: Obtener y compilar wxWidgets
El primer paso consiste en acercarnos a http://www.wxwidgets.org y bajarnos el código fuente de la biblioteca. Necesitaremos la versión completa o la MSW. Debemos descomprimirla sobre una ruta sin espacios en blanco. Una vez hecho esto, nos vamos a la carpeta



<ruta_wx>build/msw


mediante la consola de compilación que el Visual Studio nos proporciona (Ya saben: Inicio -> Visual Studio 2005 -> Tools -> Command Prompt).


Ahora debemos detenernos a considerar una serie de cosas. Existe una solución para generar la biblioteca completa, tan completa, que genera todas las combinaciones posibles, llenándonos el disco duro con casi doce gigas de código, por lo que no la vamos a utilizar. Lo normal es tener dos o cuatro versiones como mucho, y en casos extraordinarios hasta ocho. Podemos elegir entre:



  • Unicode y no Unicode

  • Estática y DLL

  • Modular y Monolítica

  • Debug y Release

El autor se ha construido la versión Unicode Estática Monolítica, tanto en Debug como Release. Para ello ha utilizado el fichero makefile.vc situado en la carpeta citada.


Para construir la versión de depuración ejecutamos:



nmake -f makefile.vc UNICODE=1 BUILD=debug MONOLITHIC=1


y para la Release:



nmake -f makefile.vc UNICODE=1 BUILD=release MONOLITHIC=1


Si queremos trabajar con DLLs (cosa que sólo recomiendo si se van a distribuir muchas aplicaciones en un mismo equipo), tan sólo debemos añadir «SHARED=1» a las dos líneas anteriores. Otro tipo de build que podemos hacer es enlazar con las bibliotecas de tiempo de ejecución estáticas del Visual C++, con lo que evitaremos el hecho de tener que distribuir las DLL del Visual Studio 2005. En ese caso debemos añadir «RUNTIME_LIBS=static» a los comandos anteriores. Quizás sea esta la mejor opción de todas, para evitar el error que suele aparecer casi siempre en el que la aplicación, en lugar de decirnos que le falta tal o cual DLL, nos dice que la reinstalemos. Si quitamos «MONOLITHIC=1», en lugar de generar una DLL o una LIB de proporciones considerables, se crearán varias, cada una con un subconjunto de toda la biblioteca.


En la carpeta «<ruta_wx>libvc_lib» podemos encontrar las bibliotecas compiladas y listas para integrar. Si queremos comprobar si todo ha ido bien, podríamos ir a la carpeta «samplesminimal» y ejecutar el mismo comando que para construir las bibliotecas. Deberíamos obtener un ejecutable que nos lanzará una aplicación Windows básica.


SEGUNDO PASO: Integrar en Visual Studio
Ahora viene la que quizás sea la parte más liosa de todas, y la que los tutoriales que he encontrado por ahí no terminan de explicar todo lo bien que sería necesario. Debemos abrir nuestra copia de Visual Studio, nos sirve incluso la Express con el Platform Builder instalado y las rutas correctamente establecidas para crear ejecutables nativos, cosa que sabremos que está bien si hemos podido compilar la biblioteca.


Creamos un proyecto vació, al que le añadiremos, copiándolo, el código que hay en el fichero «<ruta_wx>samplesminimalminimal.cpp» o asociando dicho archivo. Si intenamos compilar ahora veremos que el compilador no encuentra las rutas.


Nos vamos a las propiedades del proyecto, en la configuración Debug, y en la pestaña «C/C++ -> General», «Additiona Include directories», añadimos en primer lugar la ruta



<ruta_wx>libvc_libmswud


¿Por qué esta ruta, y por qué en primer lugar? Pues para que el compilador encuentre el archivo «wxsetup.h», que le indicará qué compilar y cómo. Dicho archivo depende de cada configuración, y por eso se sitúa en la carpeta de las bibliotecas. Si nos damos cuenta en el nombre de la ruta, «mswud», veremos que se trata de la versión Windows («msw»: MicroSoft Windows), Unicode («u») y Debug («d»).


La otra ruta a añadir es la típica de inclusión, «<ruta_wx>include».


Si hemos creado la biblioteca con la versión estática de las bibliotecas del Visual Studio (opción «RUNTIME_LIBS=static»), debemos ir a «C/C++ -> Code Generation» y cambiar el valor de «Runtime Library» de «Multi-threaded Debug DLL (/MDd)» a «Multi-threaded Debug (/MTd)», ya que si no lo hacemos obtendremos errores de símbolos duplicados durante el enlazado.


Ahora, en la pestaña «Linker -> General», en la opción «Additional Library Directories» tenemos que especificar la ruta a las nuevas bibliotecas,



<ruta_wxlibvc_lib»


Ya por último, en la pestaña «Linker -> Input» tenemos que añadir los ficheros de la biblioteca. Aquí es donde más errores cometen todos los tutoriales, pues no indican claramente qué ficheros incluir. Si hemos construido la versión monolítica, debemos añadir




  • wxexpatd.lib


  • wxjpegd.lib


  • wxmsw28ud.lib


  • wxpngd.lib


  • wxregexud.lib


  • wxtiffd.lib


  • wxzlibd.lib


  • comctl32.lib


  • rpcrt4.lib


  • winmm.lib


  • wsock32.lib

y si es la versión partida, debemos sustituir wxmsw28ud.lib por todas sus partes equivalentes. Observamos que los cuatro últimos ficheros no pertenecen a wxWdigets, si no al propio Visual Studio. Si no los incluímos, obtendremos errores por falta de símbolos.


Y ya está, ya podemos compilar nuestro proyecto.


También debemos repetir todos estos pasos para la compilación Release, pero cambiando la ruta de inclusión «<ruta_wx>libvc_libmswud» por «<ruta_wx>libvc_libmswu», y los nombres de los ficheros .lib por aquellos cuyo nombre no finalice en d.


TERCER PASO: Cabeceras Precompiladas
Las cabeceras precompiladas son el quebradero de cabeza de muchos, ya que el tema no está muy bien tratado por Microsoft. Otros compiladores que las soportan son más claros en detallar los pasos y el uso de las mismas. En nuestro caso, dado que no se trata de los ficheros de inclusión estándar de Microsoft, la gente de wxWidgets han solucionado el tema a su manera.


El uso de las cabeceras precompiladas requiere de dos pasos. El primero consiste en crearlas, el segundo en usarlas, ya que parece ser que los compiladores de Microsoft son incapaces de generarlas si no están y usarlas una vez generadas.


Lo primero nos vamos a las propiedades del proyecto y en la pestaña «C/C++ -> Precompiled Headers», en «Create/Use Precompiled Header» cambiamos la opción a «Use…», y en «Create/USE PCH Through File», tecleamos «wx/wxprec.h». Y por útlimo en «Precompiled Header File», colocamos el fichero



<ruta_wx>libvc_libwxprecd.pch.


Cerramos la ventan de las propiedades y construimos una vez nuestro proyecto, que nos creará el fichero «wxprecd.pch» en la carpeta de las bibliotecas de wxWidgets. De este modo ya no será necesario volver a generarlas si no cambiamos nada de la biblioteca. También podemos dejarlas en el directorio por defecto, pero en este caso tendremos que generarlas en cada proyecto.


Para usarlas, volvemos a las opciones del proyecto y cambiamos «Create…» por «Use…» en «Create/Use Precompiled Header». Para la pestaña de Release también repetiremos los pasos, pero en este caso el nombre será



<ruta_wx>libvc_libwxprecd.pch


o el que nos pone el entorno por defecto.


Para utilizarlas, en nuestros programas debemos colocar



#include «wx/wxprec.h»


en todos los ficheros de código fuente.

Workaround a "arreglo unas, rompo otras", o cómo evitar que el background de una ficha se vaya de viaje con el SP1

Las prisas y el mucho trabajo me han impedido publicar una solución alternativa al bug comentado en este post, y también tiene que ver el estress para determinar si iba a finalizar a tiempo el proyecto que llevo entre manos, recogido de un gabinete de ingeniería con pocas luces y menos ganas de trabajar. En fin, que visto el final a lo que estoy haciendo (he estado unas 12-14 horas/días, sábados y domingos incluídos -y sí, estos días de fiesta también) y no sólo voy a llegar a tiempo, sino con una semana de anticipo, es ahora cuestión de tomarnos las cosas con más calma.


Lo curioso del fallo no es el problema en sí, sino cómo se presenta. Tenemos una ficha que se ve bien, y tras unos segundos, parpadea y el fondo se corrompe. ¿Quizás cuando entra el recolector de basura? ¿La segunda pasada para re-optimizar el código? La cuestión es que ocurre algo dentro de la máquina virtual .NET que no me gusta un pelo, pero parece ser que ese es el único efecto secundario. Veremos cuando mi aplicación lleve unos meses en la calle.


Bueno, la solución es sencilla y directa: coge un componente PictureBox, sueltalo sobre la ficha y pon su Dock a Fill. A la hora de trabajar con el pintado de la ficha, en lugar de operar sobre ella, debemos hacerlo sobre el componente PictureBox. Menos mal que dicho componente trae casi los mismos eventos que la propia ficha y no presenta el bug descrito.


 Y a ver si nos relajamos un poco (lo digo pro mi mismo).


Saludos a todos y espero que estéis pasando unas felices fiestas.