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.

Pintar texto de abajo a arriba, un posible bug y un workaround

Pues eso.


Para pintar texto en vertical, es necesario pasar al método DrawString un último parámetro, una instancia de StringFormat con lo siguiente:



m_stringFormat=gcnew StringFormat();
m_stringFormat->FormatFlags=StringFormatFlags::DirectionVertical;



Pero esto nos dibuja el texto de arriba hacia abajo y mirando hacia la izquierda. Si nosotros queremos hacerlo justo al revés, de abajo hacia arriba, y mirando hacia la derecha, hasta donde he podido llegar, no se puede hacer directamente.

La solución obvia consiste en cambiar las coordenadas del dispositivo, rotándolas 180 grados, y pintar. En mi caso, sobre la propia ficha. Pero no he conseguido que funcione, si toco las rotaciones o las traslaciones, pese a lo que dice la documentación y el Petzold, no se pinta nada (y no porque me salga de la superficie, no).


Así que debemos optar por la solución más compleja, que consiste en pintar sobre un bitmap oculto y luego copiarlo a la superficie de la ficha:



Bitmap ^bmp=gcnew Bitmap(m_editNumberRect[m_idxCurrentEditing].Width,m_editNumberRect[m_idxCurrentEditing].Height,e->Graphics);
Graphics ^g=Graphics::FromImage(bmp);
g->TranslateTransform((
float)(m_editNumberRect[m_idxCurrentEditing].Width+15),(float)m_editNumberRect[m_idxCurrentEditing].Height);
g
->ScaleTransform(-1,-1);
g->DrawString(m_editNumber[m_idxCurrentEditing],m_textFont,Brushes::Black,0,0,m_stringFormat);
e->Graphics->DrawImage(bmp,m_editNumberRect[m_idxCurrentEditing]);


Creamos un bitmap de las dimensiones deseadas compatible con la pantalla actual. Luego obtenemos la superficie para pintar sobre el propio bitmap. La tercera línea (TranslateTransform) desplaza el origen de coordenadas para que al hacer la inversión mediante ScaleTransform pintemos el texto mediante DrawString en el mismo sitio que antes de rotar. De este modo, hemos pintado el texto rotado 180 grados.



Hacerlo de la forma fácil, es decir:


g->TranslateTransform(…);
e->Graphics->ScaleTransform(-1,-1);
e->Graphics->DrawString(m_editNumber[m_idxCurrentEditing],m_textFont,Brushes::Black,m_editNumberRect[m_idxCurrentEditing],m_stringFormat);


simplemente no funciona, quizás porque se me escape algo.


En el código de ejemplo, el que el ancho esté desplazado 15 píxeles no es otra cosa que el desplazamiento causado por la altura de la letra; como la aplicación va a ir en un ordenador replicado, no es necesario liarse con las métricas de las fuentes.


Y eso es todo, que no siempre es cuestión de despotricar. 😛

Arreglo unas, rompo otras

De veras que lo siento, de veras. 🙁


Me molesta mucho ser tan mosca cojonera, tan impertinente y tan combativo, pero es que se lo merecen.


¿Recuerdan que han publicado el SP1 del Visual Studio 2005? ¿Sí?


Hagamos un expermiento en C++/CLI.


Creemos un proyecto vacío Windows Forms.


A la ficha principal, en las opciones de propiedades, asignamos FormBoderStyle a None.


Cambiamos el valor Size a 800;600.


En el constructor, debajo de InitializeComponent(), añadimos una línea como la siguiente:



BackgroundImage=Bitmap::FromFile(«<ruta>\debug\img\MENU_PRINCIPAL.bmp»);


En donde el fichero MENU_PRINCIPAL.bmp es una imagen bmp normal del mimso tamaño que la ficha, en nuestro caso, 600×800


Ejecutemos el ejemplo.


Vemos que el fondo de la ficha muestra nuestra imagen. Esperemos unos segundos sin ocultarla.


¿Qué ocurre?


Pues el fondo se desplaza solo, como si nos hubiéramos equivocado al pintar.


Cuando ocultamos y volvemos a mostrar la ficha, la imagen aparece de forma correcta, hasta que al poco se vuelve a desplazar.


Con otras fichas hijas, esto no ocurre, solo con la padre y, por supuesto, con el Visual Studio 2005 sin SP1, tampoco.


¿Tengo o no razón para quejarme amargamente, en despotricar contra el hatajo de chapuzas, mierdaprogramadores, tontoslaba de la gente de Microsoft?


Pues eso.

Deploy con Visual Studio 2005 SP1

Por todos lados están anunciando que ha salido el Service Pack 1 del Visual Studio. Casi todos los post de Geeks tienen su entrada correspondiente, y yo no voy a ser menos. 🙂


Pero en lugar de anunciar que ha salido, voy a comentar algo sobre las mejoras observadas y sobre otros temas. Como estoy hasta las cejas de faena -en este momento estoy programando, aunque ya lo dejo hasta el lunes (bueno, hasta el miércoles, mañana salgo de viaje a Barcelona)- voy a ser bastante sincrético.


Creo que en mi otro blog ya he comentado algo cuando el SP estaba en fase beta, y más o menos la situación es la misma. De momento me voy a referir al C++/CLI, puesto que de momento es lo que estoy utilizando -y hasta mediados de enero, eso y el C++ tradicional, cosas de faenas con urgencia-.


En general ha mejorado algo el rendimiento, ahora la transición ficha/código es algo -sólo algo- más rápida. Tampoco estoy con un proyecto con fichas complejas, más que nada porque la superficie la relleno en tiempo de ejecución, por lo que no he podido verificar si se ha solucionado el bug del doble clic. En el editor visual, a veces, si hacías clic demasiado rápido sobre un componente, en lugar de seleccionarlo, el entorno entendía que habías hecho doble clic y te lanzaba el evento por defecto. Creo que está solucionado, porque ahora cuesta más seleccionar el componente, como si hubieran puesto un retardo entre que pones el ratón encima y te acepta el clic (que personalmente considero es una chapuza, pero bueno). Ya comentaré mas sobre eso, seguro que la semana que viene.


Otra pifia bien gorda que seguro han solucionado, es el tema de la compilación de los CPP. Unas veces raramente, otras casi siempre, al IDE se le olvidaba que habías modificado el cpp y no lo compilaba, creando verdaderos maremagnums de errores que se solucionaban con un Build All. A veces hasta se caía el IDE entero.


Lo que no han solucionado es el IntelliSense en C++/CLI. Sigue fallando más que una escopeta de feria, esperemos que el problema con los proyectos grandes sí que lo hayan solventado.


Y ahora viene lo interesante. Esta mañana, tras instalar el SP1, como siempre, he lanzado la aplicación en la máquina remota para descubrir que no se ejecutaba. Me daba un error algo así como que tenía que instalarla, igual que cuando ejecutas un EXE generado en Debug y no encuentra las DLLs (uis, perdón, ensamblado) de depuración. Lo mismo pero con la Release.


Efectivamente, han cambiado el runtime del C++, por lo que la solución pasa por realizar un proyecto de instalación con tu aplicación y marcar en propiedades que es necesaria la dependencia del Runtime del Visual Studio. En la carpeta del instalador, una vez construido el proyecto, residirá el citado instalador, que debemos trasladar al otro PC e instalar.


Lo que me llama mucho la atención es la necesidad que tiene un aplication .NET pura del runtime de C++. ¿No es el .NET independiente de la plataforma? En fin.


Si os pasa, avisados estais.

Windows CE (II). Diferencias con un PC

Protección de Procesos
Quizá a simple vista pueda parecer un paso hacia atrás, a los tiempos del Windows 3.1, pero en Windows CE el sistema de protección entre tareas es muy débil, de modo que una aplicación puede, si no desestabilizar el sistema, sí detenerlo por completo.

No se trata de un fallo, sino de una limitación de los microprocesadores que se utilizan en entornos embebidos, de modo que, efectivamente, un proceso inestable puede llegar a hacer necesario un reinicio del dispositivo.

Pero debemos tener en cuenta el hecho de que el destino de estos aparatos no es el mismo que el de un PC, sino que el número de aplicaciones que van a ejecutar es conocido y está determinado en tiempo de desarrollo.

Habrá dispositivos que ejecuten como máquinas cerradas, por ejemplo un sistema de parking con la típica pantallita táctil; en este tipo de desarrollos dentro del dispositivo se va a ejecutar el sistema operativo y la aplicación, de forma que el comportamiento de ésta va a estar perfectamente prefijado desde un principio, por lo que en teoría cualquier problema, y sobre todo cualquier problema que bloqueen el equipo, va a estar solventado en origen.

Otros elementos serán más versátiles, como los puntos de información, o las máquinas de venta de productos variados, o incluso un videojuego; aquí el software entraña mayor complicación, por lo que se requieren más validaciones, pero al final se trata de lo mismo: un sistema completamente cerrado, por lo que el tema de protección de procesos pierde interés frente a una mayor seguridad del hardware o simplemente al coste del dispositivo…

Tamaño de la pantalla

Esta diferencia sí que puede ser rompedora a más no poder. En un dispositivo embebido no podemos asumir ni siquiera un rango de tamaños de pantalla… Podemos tener aparatos sin pantalla de ningún tipo -un router, una impresora-, otros con un simple display alfanumérico, y entre ellos desde uno de una línea de caracteres fijos hasta los de varias, o gráficos, o mixtos, o de 7 segmentos…

También podríamos disponer de una pequeña pantalla tipo PDA más o menos estándar, o una que no lo sea, de, por ejemplo, 1000×10 píxeles (un rótulo), o incluso varias pantallas, unas con chip 3D, otras con framebuffer, o con acelerador 2D… Incluso dispositivos a los que no se les haya incluído el soporte de vídeo dentro del CE pero que tengan pantalla con una biblioteca propia…

Por eso no se puede asumir nada sobre la salida de gráficos… Cada dispositivo tendrá sus características, y un aplicativo realizado para un modelo podría no servir para otro idéntico pero con diferente soporte de vídeo…

Periféricos extra, exóticos… o ninguno
Otra diferencia son los periféricos. Aquí tampoco podemos asumir nada. Habrá aparatos que lleven disco duro, o lectores de CD, otros quizás un sistema de lectura en Compact Flash, o Secure Digital, o simplemente una ROM en chip sin más.

Los habrá con USB, o con cinco o diez puertos serie, o SPI, con sonido o sin él, con una impresora típica o algo más extraño, como una térmica o incluso un elemento impresor de seguridad.

Podrían tener dispositivos para pagar dinero, o aceptarlo…

Incluso podrían tener una batería de relés o comutadores para comandar cualquier cosa…

Velocidad y rendimiento
Tampoco podemos asumir nada respecto al posible rendimiento. Habrá aplicaciones que apenas requieran recursos, y las habrá que necesiten uno o varios procesadores SIMD o MIMD asociados…


Por ello no debemos ser conscientes de la optimización y del consumo de memora y recursos. No estamos ante un PC, sino antes algo mucho más pequeño y más limitado, aunque a simple vista, por la forma de desarrollo, parezca que trabajamos con un PC. Y debemos ser enormemente cuidadosos a la hora de hacer nuestros programas, pues aparte de lo habitual, debemos poner especial cuidado en el optimizar el consumo de memoria y el tamaño de nuestro código.


Depuración remota
Generalmente, y salvo que estemos trabajando con los emuladores, el proceso de depuración suele diferir algo respecto al tradicional. Quzás la mayor diferencia sea que ahora la máquina sobre la que ejecutamos nuestro programa es diferente a la que compila y contiene el entorno de desarrollo, y generalmente se trata de arquitecturas diferentes. Pero MIcrosoft soluciona todo eso de forma elegante y casi transparente para el programador.


Lo más difícil de todo es que tengamos que preparar nosotros todo el sistema de desarrollo, incluyendo en el Platform Builder los elementos necesarios para generar nuestro sistema operativo. A veces la tarea puede ser bastante dificultosa, sobre todo si vamos a desarrollar con el Visual Studio 2005 y con una plataforma construida con el Platform Builder 5. Pero si utilizamos el sistema tradicional, el asunto es trivial.


Generalmente será el proveedor de la placa de evaluación o el que suministre el kit quien monte y prepare todo eso. A no ser que seas tu el proveedor, y entonces te tengas que currar todo, que es mi caso.


El proceso para lanzar un ejecutable es bastante sencillo. Desde el IDE, previa conexión con la placa («Tools» -> «Connect to Device», aunque esto a veces es automático), tu programa se compila y genera el ejecutable válido para el ordenador remoto. Cuando lances la depuración, el IDE enviará el ejecutable junto a todo lo necesario a la placa destino, y lanzará la aplicación allí.


Cuando pongas un punto de interrupción, o detengas la ejecución, verás el resultado y la parada del programa en la ventana de tu IDE, pero todo lo demás -salida de video, acciones, etcétera-, se ejecutará en el dispositivo. Como es una conexión remota, todo esto es bastante más lento que programando directamente con el PC, aparte de que la aplicación se ejecutará en destino bastante más lenta de lo que lo haría si no estuviera el depurador anexado.


Formas de conectar hay muchas, desde el ActiveSync por canal serie o USB, hasta una conexión Ethernet, que suele ser la más rápida. Depurar a través de un puerto RS-232 puede ser una tarea desesperante, sobre todo si el programa es grande.


Generalmente dispondremos de un canal de comunicación unidireccional desde el dispositivo hacia el Visual Studio, que aparecerá en la ventana de registro correspondiente. Estas llamadas se suelen hacer mediante la macro RETAILMSG, que se encuentra ampliamente documentada.


Pero no estamos limitados al Visual Studio. Podremos ver el sistema de ficheros remoto, así como el registro, o la lista de procesos. Tenemos hasta un medidor de rendimiento. Todo ello se puede ejecutar desde el menú «Microsoft Visual Studio 2005» -> «Microsoft Visual Studio 2005 Remote Tools». La idea es la misma que con el IDE, en tu ordenador verás lo que hay en el remoto, previa conexión con el dispositivo.


Limitación de APIs
Quizás lo que más moleste a un programador hecho y derecho que se acerque al mundo embebido, o que tenga que desarrollar tanto para PC como para éste, es el hecho de la limitación de las APIs.


La de Win32 no está muy capada, y casi se permiten las mismas cosas, lo único que por lo general sólo hay un camino viable, y lo que se ha eliminado es porque no tiene mucho sentido dentro del mundo embebido, pero otras limitaciones pueden llegar a ser algo desesperantes.


Y este es el caso del .NET. Mientras que para el PC cuenta con un richo conjunto de elementos, el Compact .NET se encuentra bastante -por no decir mucho- limitado, por lo que hay que tener un cuidado exquisito a la hora de diseñar nuestra aplicación, porque seguro que muchas cosas con las que contábamos como evidentes, no están disponibles y te toca o hacer interop al mundo nativo o buscarle tres pies al gato o, simplemente, no hacerlas.


Y una cosa que personalmente me repatea es que no haya C++/CLI para dispositivos embebidos.

"Están locos estos romanos", o "Un asunto de licencias" (II)

¿Qué oyó mi amigo Saltatrampas en el teléfono? Pues lo siguiente:



  • Windows no se puede reinstalar, debes comprar una licencia nueva.

  • Si una actualización te pide reactivar, tienes que comprar una licencia nueva.

  • Windows no se puede reinstalar, ni siquiera si se ha estropeado a causa de un driver defectuoso o por programas de terceros (incluída la propia Microsoft).

  • No se puede tener instalada en el mismo equipo más de una copia de Windows con la misma licencia, ni siquiera para evitar el punto anterior.

  • No puedes tener replicados en máquinas virtuales diferentes Windows con una misma licencia, ni siquiera si no los tienes cargados simultaneamente, aunque la propia Microsoft te suministra una utilidad para hacerlo.

Tras haber leído varias licencias de Microsoft, no encuentro dónde pone todo eso, aunque se podría interpretar así. Pero sigamos con nuestra discusión.


¿Qué es un sistema operativo? Una definición entendible por todos es: una pieza de software que se situa entre las aplicaciones de usuario y la electrónica del equipo, y que permite y facilita la interactuación de un ser humano con el ordenador en el que se encuentra instalado. Windows es un sistema operativo.


Si yo compro un ordenador con un sistema operativo incluído, ese ordenador está asociado a esa licencia, y lo podré poner y quitar las veces que me de la gana, siempre que lo haga sobre el propio hardware. No lo voy a poner en una tostadora, ni en un equipo de música. Lo voy a poner en el ordenador para el que fue comprado. Si compro una tostadora y le quito la bandeja que recoge las migas de pan para limpiarla, no tengo que ir a la tienda a comprar otra. Lo mismo que con las lejas de un frigo.


Cuando se me estropea la tele la llevo al taller, me la reparan y me la devuelven. Pero no me obligan a pagar de nuevo el impuesto de lujo, ni la licencia del softwre que la hace funcionar.


Si al equipo de música le añado un nuevo módulo, no tengo que volver a pagar el equipo entero. Tampoco tengo que volver a pagar el impuesto de matriculación si le saco el motor al coche, lo reparo y lo vuelvo a poner. Incluso si lo hago yo.


Puedo tener 10 coches sin matricular siempre y cuando no los saque a la calle. Pero los puedo poner en marcha, revisarlos y quizás correrlos en circuitos de pruebas.


Creo que los paralelismos están lo suficientemente claros


Pero prosigamos.


Desde mi punto de vista es un completo absurdo, un absurdo que raya en lo estotérico, más allá de cualquier explicacion lógica. La única explicación es la prepotencia de Microsoft. ¿Pero qué cojones se han creído? Que miren las historia y que le echen un vistazo al caso Bell Telephone y el día en que el ejército tomó todas sus sedes.


Partimos del principio de que Windows es ilegal en España. Porque lleva la licencia dentro y no puedes leer las condiciones de uso hasta que no has abierto el paquete. Y eso está prohibido. Sería igual a que te tuvieras que comer un yogur para ver la fecha de caducidad, porque ésta está en el fondo por el lado de dentro.


El siguiente punto es que el software, si no se está ejecutando, no es software. Por lo que podré tener cuantas copias e instalaciones me de la gana de Windows en el mismo ordenador, mientras que solo tenga una en ejecución. Esto es así por mero sentido común. Aparte de que cuando lees esa licencia ilegal te están diciendo que puedes EJECUTAR UNA copia del software.


Si el reinstalar un Windows ya activado no es legal, ¿por qué Windows Update te deja? ¿Y por qué pasas la validación de software original? ¿Está Microsoft fomentando la piratería de forma encubierta? ¿Espera a que todos tengamos nuestros Windows instalados de forma ilegal para lanzar a sus perros sabuesos?


Personalmente pienso que ni ellos mismo saben lo que se llevan entre manos, pero que se anden con ojo…


Pero sí que tengo clara una cosa:


 Estoy bastante harto de ese aire de prepotencia y de ordeno y mando que rezuma últimamente Microsoft, máxime cuando llevan varios años sacando productos que no son más que betas encubiertas con miles de bugs que nunca se solucionan.


Por lo menos yo rechazo esas condiciones de licencia, y seguiré utilizando mis Windows (legalmente comprados), y los instalaré las veces que me de la gana y tendré las copias que quiera en un mismo ordenador, y asímismo me gustaría realizar un llamamiento público para que todos nos concienciemos del tema y hagamos llegar a Microsoft nuestro rotundo desacuerdo.


Con todo esto lo único que van a conseguir es que mucha gente termine instalando Linux en sus ordenadores, o que simplemente usen copias piratas sin tantas mierdas de activaciones, verificaciones y mariconadas varias.


Dixit.

"Están locos estos romanos", o "Un asunto de licencias" (I)

El otro día, hablando con mi tío Saltatrampas de esto y de aquello, y de lo de más allá, le surgió una duda bastante seria, duda que inmediatamente me contagió. Y como yo no puedo vivir con una espada de Damocles pendiendo sobre mi cabeza, me decidí a investigar sobre el tema.


Dada mi tendencia a divagar y a irme por los cerros de Úbeda, y aparte escribir con cierto afán demagógico y floriturado, no puedo evitar el hecho de envolver todo este asunto en flores odoríferas y fragantes (no diremos si el aroma es agradable o desagradable), aunque intentaré centrarme en los hechos. Vamos allá.


Una mañana te levantas alegre y dicharachero y decides ir a la tienda a comprarte un ordenador nuevo, puesto que el que ahora posees empieza a emitir tufillos de humo negro cada vez que escuchas a la Lola Flores (tampoco vamos a entrar a discutir si el hecho de que tu ordenador humee se debe al hecho de que no le guste reproducir a tan insigne cantante o a que ya tiene sus años y le cuesta eso de mover los bits). En nuestro mundo ideal, el tendero te atiende con una amplia sonrisa, tu eliges tu cacharro, lo pagas, y vuelves a casa con él bajo el brazo. En el mundo real habría ligeras variaciones. Como que te vas a gastar casi el doble de tu presupuesto («total, por 10 euros más tengo esto que es mejor»), o que habrás de esperar casi una semana a llevártelo. Pero bueno, estamos en un mundo ideal.


Ya en tu casa, contento y emocionado, desempaquetas, enciendes y descubres un ya olvidado «C:» con una cosita parpadeante. «¿Ein?», te dices.Joder, claro, se te ha olvidado comprar el sistema operativo. «Estás tonto, macho». Así que vuelves a la tienda.


Y es aquí donde las partículas subatómicas nos juegan una mala pasada y escinden el universo en dos sub-universos, cada uno de ellos con su línea temporal independiente. Como nosotros no somos dual core,pero tenemos la posibilidad de usar tablas, vamos a ver la secuencia en pseudoparalelo.












El de la tienda, que es un jodío de cuidado, te dice que ya no puede meterte una licencia OEM en la factura, que ésta ya está cerrada, por lo que tienes que pasar por caja comprando una Reail, que vale aproximadamente el doble. El tendero, que es una buena persona y entiende el tema («Pensaba que ibas a meter Linix», te dijo), modifica la factura y te vende una licencia del XP OEM, asociada a tu ordenador recién comprado.
Vuelves a tu casa emocionado con tu caja del XP, que sólo contiene un CD y un librito diminuto, aparte de unos papelajos en cartón que dicen «License». Como eres un prisas, despegas la pegatina del número de serie y la adhieres a la caja del PC. Vuelves a tu casa emocionada con tu sobrecito transpartente del XP, que sólo contiene un CD y un librito diminuto, aparte de otro más que dice «License». Como eres un prisas, despegas la pegatina del número de serie y la adhieres a la caja del PC.

Dado que los quarcks no son tontos, deciden volver a juntar las funciones de onda de las dos líneas espaciotemporales, para ahorrar tiempo de proceso y espacio en el continuum. 


Sacas el CD, lo metes en el lector, y reinicias. Mientras, te vas leyendo la licencia. Tu cara va cambiando de una amplia sonrisa a una no tan amplia para terminar bajando la comisura de los labios hasta conseguir un rictus de mala leche.


Bueno, no importa, te dices, todo eso son palabrerías de abogaduchos de mala muerte.


Terminas de instalar todas las cosas de tu ordenador. Te bajas las actualizaciones, metes todos los drivers, en fin, haces todo lo necesario para tener tu ordenador listo para funcionar. Y lo activas.


1.- Al cabo de dos días descubres que no va todo lo fino que debiera, de vez en cuando tienes una pantalla azul… Investigas y llegas a la conclusión de que te has instalado unos drivers del chipset de la placa base defectuosos… ¡Pero si eran los que venían en el CD! Pues nada, te bajas una nueva versión de la Web del fabricante, la instalas y detectas que hay algunos archivos que no se actualizan. La única opción que te queda es reinstalar Windows. Lo haces y ahora sí, ahora todo va como la seda.


2.- Pasan los meses y cambias la tarjeta de video por una más potente y mejor. Y te vuelve a ocurrir lo mismo que la vez anterior, teniendo que volver a instalarlo todo de nuevo.


3.- Te compras una PDA, instalas el ActiveSync y todo va como la seda, pero un día, mientras estás sincronizando se va la luz y ya no puedes volver a conectar, el ActiveSync se ha ido al garete nadie sabe por qué, y las reinstalaciones del programa no consiguen que éste funcione (es un caso real muy común). La solución es volver a reinstalar Windows.


4.- Compras un programa de terceros que te deja el ordenador echo unos zorros (léase Norton, Panda y similares). La única solución viable es la reinstalación.


5.- Como estás harto de que te pasen todas esas cosas, decides tener dos instalaciones del mismo Windows en el mismo ordenador, una en la que sabes que todo funciona correctamente y otra más en la que haces tus pruebas antes de tocar la anterior. Efectivamente, el sistema de Windows Update te deja tener dos copias instaladas de Windows en el mismo ordenador con el mismo número de serie, y el sistema de validación que determina si tu Windows es original funciona perfectamente.


6.- Ahora tus conocimentos de informática son bastante serios, tanto, que decides instalarte el Virtual PC o el Virtual Server, que Microsoft ofrece gratuitamente. Los instalas, y entonces te surge la duda sobre cómo van las licencias dentro de una máquina virtual. Buscas tu papel en el que está la licencia de uso de tu Windows y te lo vuelves a leer. Y descubres algo sobre instalaciones en equipos físicos, derchos de uso y garantías, pero nada sobre máquinas virtuales, equipos corriendo simultáneamente y cosas de esas, así que instalas tu copia de Windows en una máquina virtual, la activas (el sistema te deja), y luego la replicas todas las veces que quieras.


Y entonces llega Windows Vista. ¡Oh, qué bonito! Pero cobran una pasta por una licencia, aparte de que empiezas a leer por ahí cosas extrañas sobre el nuevo formato, que si sólo te van a dejar activar dos veces sobre el mismo equipo, o que si actualizas algo tendrás que volver a pasar por caja con el Sistema Operativo, y cosas así. Personalmente piensas que se trata de FUD (Fear Uncertainty Doubt), así que pasas de todo eso y esperas ardientemente que salga a la venta.


Pero en el ínterin tienes tus dudas, así que coges el teléfono, marcas el número de atención al cliente de Microsoft, y haces tus preguntas pertinentes… lo que te pone los pelos como escarpias. Tu preguntas y ellos responden… Las respuestas las veremos en el siguiente post de este blog.