Conversión ANSI a Unicode y viceversa

Seguro que no es la primera vez que te pasa que te dejan una serie de rutinas (generalmente en formato DLL) que reciben cadenas en formato ANSI y tu aplicación está escrita en Unicode, y cuando preguntas si tienen versión Unicode te responden que no y que no la van a tener… Y como no quiero perder mi empleo, voy a parar aquí.

Pues bien, existe una forma muy sencilla de pasar cadenas entre ambos formatos sin tener que liarnos con las funciones de conversión de Win32, que no son precisamente fáciles de manejar. Y como esto va a modo de receta, no entro en más detalles.

Lo primero es incluir atlconv.h en aquella unidad de compilación en donde vayamos a hacer conversiones.

Lo segundo es colocar la macro USES_CONVERSION; dentro del método en el que vamos a realizar las conversiones.

Lo tercero es declarar un puntero ANSI o Unicode dependiendo de si queremos pasar de un lado a otro.

Y finalmente usar la macro adecuada. Veámoslo con un ejemplo:

#include <atlconv.h>

Void FuncionQueNecesitaPasarDeANSIaUnicode(void)
{
    USES_CONVERSION; //ANSI 2 UNICODE macros

    char szCad[MAX_PATH];
    LPWSTR szCadUnicode;
    FuncionQueNecesitaANSIporqueNoTieneUNICODE(szCad);
    szCadUnicode=A2W((LPCSTR)szCad);

    CString printCad;
    printCad.Format(_T("ATPC: %s"),szCadUnicode);
    AddLogString(printCad);
}

Hay una espuerta de macros con el formato

[C]<origen>2[C]<destino>[EX]();

Origen y destino pueden ser A para ANSI, W para Unicode, T para cuando estamos generando código agnóstico en cuanto al formato de cadena, y OLE para cadenas OLE. C se usa para cuando es una cadena constante.

De este modo, A2W convertirá de ANSI a Unicode, y W2A al revés. Aquí la referencia completa.

Publicado por Rafael Ontivero | 3 comment(s)
Archivado en: ,,,

Todo lo que quisiste saber sobre las DLL y no te atreviste a preguntar (II)

Como podéis ver no se me ha olvidado el tema, simplemente es que he estado demasiado gandulete como para escribir sobre esto, pero una pregunta en el foro web de C++ me ha hecho que me ponga a ello. En teoría esta debería ser la tercera parte en lugar de la segunda, aunque la verdad no es que realmente tenga mucha importancia. Por lo tanto, esta entrada que estás leyendo sigue a esta otra.

Aquí voy a contar los tres modos que hay de usar una DLL desde C y C++, aunque solo voy a explicar dos de ellos porque el tercero digamos que viene de las primeras versiones de Windows y ni siquiera sé si los compiladores modernos la soportan.

Pero antes, una introducción.

¿Qué necesito para usar una DLL de terceros?

Cuando vamos a usar una DLL, ya sea nuestra o de terceros, se nos deben suministrar al menos tres archivos. El primero, que no nos haría falta para el segundo método, es un fichero cabecera conteniendo los prototipos de las exportaciones de la DLL, es decir, las firmas de las funciones y la declaración de los tipos contenidos.

En general, el formato de cada prototipo debe seguir de cerca el siguiente formato:

__declspec(dllexport) __stdcall int HazAlgo(int algo);

O

extern “C” __declspec(dllexport) int HazAlgo(int algo);

La primera firma declara una función con formato de llamada estándar (el __stdcall) que pertenece a una DLL y que resulta exportado, es decir, que podemos usar. La segunda declara lo mismo pero con el protocolo de llamada de C, que también podríamos haber puesto como __cdecl. Del protocolo de llamada ya hablé en la entrada anterior, y en general todos los compiladores de C y C++ deberían entender ambas declaraciones, y si no lo hacen seguro que tienen expresiones similares.

El segundo fichero es lo que se conoce como biblioteca de importación, que es un archivo con la extensión .lib, con la diferencia de que, en este caso, en lugar de contener las propias funciones como en una biblioteca normal, lo que contiene son las firmas de las funciones exportadas y cómo están relacionadas con la propia DLL.

En el mundo Windows existen dos formatos de ficheros .lib: el COFF, que es el que usa Microsoft y compiladores compatibles y el OMF, que es el que usan otros compiladores como el C++Builder. Generalmente todos los compiladores suelen venir con una herramienta de conversión de formato.

Y finalmente necesitamos la DLL y todas sus dependencias si las hubiera.

En general muchas veces no es necesario recompilar el código de nuestra aplicación cada vez que se modifique la DLL si no hemos cambiado nada de la parte pública. Creo que incluso si se añaden funciones a la DLL tampoco es necesario recompilar siempre y cuando no se cambie ninguna declaración de las existentes y enlazadas.

Primer método: el fácil

El método más sencillo para usar una DLL en nuestro código en C o C++ es añadir la biblioteca de importación (el fichero .lib) a las demás bibliotecas en nuestro proyecto, lo que se suele hacer en las opciones del enlazador.

Luego incluimos el fichero cabecera en donde nos haga falta y ya está todo hecho: tan sólo debemos llamar a las funciones de la DLL como si fueran funciones normales y corrientes.

Debemos tener en cuenta que si la DLL contiene clases o tipos no estándar, quizás el enlazador sea incapaz de encontrar los nombres porque cada compilador de C++ ofusca el nombre de las funciones de una manera diferente, y en ese caso sólo nos queda la opción de o bien pedir una DLL compatible con nuestro compilador o bien hacer un strip del fichero LIB, ver cómo se llaman en realidad las funciones, y hacernos nosotros mismos el fichero cabecera. No os lo recomiendo.

El último paso es dejar la DLL en una ruta que nuestro programa sea capaz de encontrar, como el directorio donde se está ejecutando o en algún camino disponible en el PATH.

Segundo método: carga dinámica

No lo he dicho antes, pero cuando se usa el primer método, las DLL se cargan de forma automática a la vez que nuestra aplicación, y siguen en memoria hasta que salimos.

Existe una forma de carga manual un poco más laboriosa, pero que nos permite cosas como la carga dinámica de temas, de extensiones, de plugins o de diferentes versiones de una misma DLL.

Supongamos que tenemos una DLL con las cadenas para cada idioma. Cuando arranquemos nuestra aplicación miramos qué idioma está en la configuración y cargamos manualmente la DLL adecuada. Es lo que hace automáticamente el sistema MUI de Windows y la internacionalización en .NET y otros lenguajes. Pues de igual forma que se hace automáticamente, se puede hacer manualmente.

Para este método sólo necesitamos la DLL en cuestión y la lista de prototipos de funciones. Lo primero es hacernos un puntero a función de cada una de las funciones de la DLL:

int (__stdcall *pHazAlgo)(int);

Luego llamamos a la función de Win32 LoadLibrary() pasándole la cadena en donde está la DLL que queramos cargar. La función nos devolverá un HINSTANCE, que si todo ha ido bien será diferente de NULL. Entonces, para cada una de las funciones de la DLL, la cargamos sobre nuestro puntero mediante GetProcAddress() pasándole el HINSTANCE y la cadena con el nombre de la función en la DLL:

pHazAlgo=GetProcAddress(hLib,”HazAlgo”);

Ahora tendremos un puntero a función (que realmente es un doble puntero, porque ese puntero apunta a una tabla dentro de la DLL que a su vez apunta a la función real). Con ese puntero podemos llamar a la función sin problemas:

int respuesta=pHazAlgo(3);

Quizás tengas que hacer algún tipo de moldeo en la llamada a GetProcAddress().

Cuando queramos descargar de memoria esta DLL, llamamos a FreeLibrary() y será descargada. Nuestros punteros seguirán asignados, pero apuntarán a ningún sitio, por lo que si volvemos a necesitar la DLL (u otra versión de la misma) deberemos repetir el proceso.

Tercer método: ordinales y ficheros DEF

Este es el que no voy a explicar porque es muy antiguo y ni siquiera sé si sigue siendo válido. Antiguamente las funciones dentro de las DLL se guardaban numeradas. Es decir, en lugar de guardar una cadena con el nombre y un puntero a la relación relativa dentro del fichero en los metadatos de la DLL, se guardaba un array de punteros que apuntaba a cada una de las funciones.

Luego se tenía un fichero DEF que ponía el nombre de la función y el ordinal que tenía dentro de la DLL (la posición dentro de ese array), de forma que cuando se compilaba un programa para una DLL de este tipo (usando el fichero DEF en lugar del LIB), se dejaba un array de punteros a funciones que el cargador pareaba con el de la DLL…

Ya os podéis imaginar la que se podía armar si por cualquier motivo ese orden era cambiado por el compilador. El que faltara un ordinal simplemente lanzaba un error, pero el que se cambiara uno por otro podía terminar tumbando no solo nuestra aplicación, sino Windows al completo, ya que en aquella época las DLL sólo se cargaban una vez y eran usadas y compartidas por todas las aplicaciones que la requerían, sistema operativo incluido.

Esto se hacía así porque con la velocidad de los procesadores de aquellos años, el tiempo para buscar la cadena con la función en los metadatos y parearla con el puntero correspondiente consumía una gran cantidad de tiempo, sobre todo si había muchas funciones dentro de una DLL, y en la forma de ordinales tan sólo había que copiar punteros en bucle.

Obtener el propio número de versión desde MFC y Win32 (y II)

Hace poco expliqué cómo obtener el número de versión del propio ejecutable desde C++ Builder utilizando la VCL (su biblioteca de clases) y dejé como pendiente el obtenerla desde Win32 o MFC. Como lo he necesitado, he modificado el código y lo presento aquí:

TCHAR appName[MAX_PATH];
GetModuleFileName(AfxGetApp()->m_hInstance,appName,MAX_PATH-1);
DWORD handle;
DWORD size = GetFileVersionInfoSize(appName, &handle);
if(size!=0)
{
    TCHAR *buffer = new TCHAR[size + 1];
    GetFileVersionInfo(appName, 0, size, buffer);
    VS_FIXEDFILEINFO *pFixedInfo;
    UINT uVersionLen;
    if (VerQueryValue(buffer, _T("\\"), (void**) & pFixedInfo, (UINT*) & uVersionLen) != 0)
    {
        CString version;
        version.Format(_T("%d.%d.%d.%d"),HIWORD(pFixedInfo->dwFileVersionMS),LOWORD(pFixedInfo->dwFileVersionMS),HIWORD(pFixedInfo->dwFileVersionLS),LOWORD(pFixedInfo->dwFileVersionLS));
        CString title;
        GetWindowText(title);
        CString newTitle;
        newTitle.Format(_T("%s - V%s"),title,version);
        SetWindowText(newTitle);
    }
    delete[] buffer;
}

Si os fijáis, el código es muy similar al del otro post, tan sólo cambia la forma de usar las cadenas, en una mezcla de MFC y Win32.

Para obtener la ruta del propio ejecutable, tenemos que hacer una llamada a la función de Win32 GetModuleName() que, a través del HINSTANCE de la aplicación, rellenará una cadena con la ruta, devolviendo el tamaño ocupado. No deben preocuparnos los desbordamientos de buffer si pasamos el tamaño correcto de la cadena: la función truncará hasta el máximo.

Los pasos siguientes son iguales, y lo que cambia, de nuevo, es el manejo de las cadenas y cómo las añadimos al título de la ventana.

Para que este código funcione en Win32 puro, tan sólo hay que usar las cadenas de C (y sprintf() y accesorias) o bien usar las cadenas de la biblioteca de C++, que es lo que recomendaría.

Instalar el SDK del iRex DR1000S en Linux (Ubuntu 9.10 Karmic Koala)

Ya sabéis qué poco me gusta Linux, pero a veces tiene su interés, o más bien me da por tomármelo sin más. Añadamos que Ubuntu no es que sea de muy de mi agrado y tendremos la perrería más absoluta en investigar sobre todo eso.

Pero resulta que sí tengo otra pasión, y es la de los aparatos equipados con pantallas de tinta-e, y la mayoría de ellos –por no decir todos- llevan en sus tripas Linux, y si algún fabricante saca algún SDK este seguro que va a ejecutarse bajo dicho sistema operativo.

Los productos de la empresa iRex tendrán muchas pegas y deficiencias en cuanto al software, pero lo que sí es cierto es que se toman muy en serio el Open Source. Cuando abandonaron el desarrollo del iLiad, publicaron todo el código fuente completo, y el DR1000S tiene su SDK e incluso su emulador. Esperemos que el recién salido DR800S también cuente con él.

Bueno, pues el otro día me dio por intentar, por enésima vez, instalarme el citado SDK dentro de una Ubuntu. En su momento, con la versión adecuada y la documentación oficial, no conseguí hacerlo funcionar, así que menos con una versión más moderna. Pero me picó el gusano y me puse a ello. Y lo conseguí tras muchos sudores y esfuerzos.

Pero primero lo que ya existe. La descarga oficial del código fuente y de los SDK está aquí: http://developer.irexnet.com/pub/iOn/. Tan sólo tienes que buscar la versión adecuada. En el momento en el que escribo esto, es la versión 1.7 para el SDK y 1.7.1 para el código fuente. Las instrucciones para instalarlo están aquí.

En Mobileread hay una imagen con Ubuntu lista para utilizar, en este hilo. No sé qué versión de SDK traerá, seguro que una antigua, pero simplemente hay que quitar el viejo y poner el nuevo. Cosas que no me molan de esa imagen es que la versión de Anjuta es muy vieja, así como el propio Ubuntu, pero para quien sólo quiera echarle un vistazo rápido le será suficiente.

Si quieres seguir esta entrada con detalle, ahora es el momento de echarle un vistazo a los primeros puntos del documento de la instalación. Yo cogí mi vmWare e instalé la última versión de Ubuntu, y aunque el manual diga que el SDK no funciona dentro de un sistema operativo virtualizado, no es así, no al menos en mi Windows 7 x64 con 8 GB de RAM. Y para que la VM no ande escasa, le he asignado 2GB de RAM.

Una vez instalada la distro, no añadas el repositorio de la sección 2.2, en lugar de ello haremos algunas cosas a mano. Si intentas seguir el documento te vas a encontrar con un Anjuta y un Poki que no van a funcionar.

Instalemos Anjuta y Anjuta-dev desde los repositorios normales. Una vez hecho eso instalaremos, a mano, los plugins de Anjuta Extras, que tendrás que bajar en código fuente de aquí: http://projects.gnome.org/anjuta/downloads.shtml.

Yo me he creado una carpeta dependiente de mi home con el nombre de “dr1000s”, y ahí dentro he puesto otra nueva llamada “dependencies”, que es donde voy a colocar el código fuente dependiente para que me funcione todo bien.

Descomprime e instala los Extras (ya sabes: ./configure, make, sudo make install), y si no sabes de qué estoy hablando mejor te coges la imagen ya preparada.

***

Ahora viene el tinglado gordo. En la página de arriba hay una serie de plugins, tenemos que instalar, a mano, el Poky SDK Plugin, que va por la versión 0.5 y no es compatible con las versiones modernas de Anjuta, que es el problema que tendríamos si siguiéramos las instrucciones de la documentación original.

Tenemos que bajarnos el código, descomprimirlo e intentar compilarlo. Igual que a mi, os va a dar docenas de errores, que hay que ir corrigiendo a mano, ya que el API de desarrollo de Anjuta ha variado y hay funciones que cambian de parámetros y otras que no existen, cosa que mola mucho y parece que nadie aprende a evitar. Si tienes un API y necesitas crear otro nuevo, no llames a las funciones iguales, o bien mantienes el viejo y creas nuevas funciones o bien creas las nuevas y abandonas las viejas.

También hay otro problema con el paquete gnome-vfs, que parece que ha cambiado de lugar o que el configurador del Poky SDK es incapaz de encontrar, el tema está en que hay que modificar una buena cantidad de código. Trasteé tanto que al final ya no recuerdo qué cambié.

Una cosa que tienes que hacer es copiar el contenido de /usr/include/gnome-vfs-2.0/gnomevfs en /usr/include/gnomevfs en tu distro. Ya sé que es una chapuza, que lo que hay que hacer es añadir las rutas en la configuración del proyecto, pero yo al menos no sé dónde se hace eso.

Bueno, en este hilo de Mobileread tienes el código fuente que modifiqué, listo para un sudo make install… Y lo pongo así porque hay que modificar un makefile generado por el configure, pero como de nuevo no sé dónde hay que añadir la línea, lo hice en el makefile final.

El tema está jodido, porque aparte de tener que añadir inclusiones nuevas, he tenido que poner una buena espuerta de NULL en las funciones que reciben nuevos parámetros, y comentar otras llamadas a funciones que ya no existen en Anjuta, de modo que lo mismo he hecho una bomba de relojería… Si quieres ver qué he cambiado, puedes hacer una comparación de los ficheros originales del Poky SDK con los que yo he modificado.

En fin, el tema está en que siguiendo estas instrucciones y las del propio documento, tendrás instalado tu SDK y tu emulador para desarrollar código para el DR1000 desde el IDE de Anjuta, con integración de todo y en un Ubuntu bastante más moderno y funcional que la imagen original.

Lo que no he conseguido ha sido lanzar el depurador desde el IDE, cosa que queda pendiente para otro momento. Quizás cuando lo consiga publique la imagen en algún lado.

¿Se fragmenta el registro de Windows?

La respuesta corta es que sí. La larga es que no mucho. Veamos.

Acabo de terminar de leer la parte del libro de Windows Internals que se refiere al registro de Windows, y básicamente me ha quedado un mal sabor de boca, cosa que a veces suele pasar con este tipo de libros políticamente aprobados, y es que no deja nada claro si el registro se fragmenta o no.

En disco, el registro no es más que una serie de ficheros repartidos a lo largo de varios lugares, ficheros que se corresponden con las diferentes ramas del mismo. La mayor ventaja de Vista y siguientes frente a XP es que ahora existe un juego de llamadas a función que realizarán operaciones transaccionales sobre el mismo. Es decir, igual que en las bases de datos –y el registro no deja de ser una-, iniciamos una transacción, realizamos una serie de acciones y si las completamos bien, cerramos dicha transacción, y si no, la abortamos. Para los programas instaladores esta característica es cojonuda, y a veces también para los programas de usuario. Con sólo imaginarnos dejar el registro a medio actualizar a causa de un corte de corriente…

Lo que sí queda claro es que en memoria la fragmentación es mínima. Es decir, casi da igual el grado de fragmentación que haya en los archivos (los hive, como los llaman), cuando estos son cargados a memoria mediante virtualización de la misma, la posible fragmentación queda reducida al mínimo. Además, los valores más comúnmente accedidos quedan cacheados. También se mantienen listas y cachés de escritura, para minimizar la sobrecarga en el sistema, lo que a veces, ante un corte imprevisto de corriente, hace que no se graben dichas modificaciones, aunque Windows garantiza la no corrupción de los hives mediante un sistema de mapeados y de escrituras en ficheros dobles y triples (más o menos lo mismo que hago yo con los firmwares cuando los datos no deben corromperse ante un corte de luz –en mi caso se trata de memorias flash y eeproms, pero la situación es la misma). La descarga a disco se hace cada cinco segundos, aunque podemos forzar nosotros una mediante la llamada a la función RegFlushKey() que, pese a lo que diga la documentación, hace un volcado de todo lo pendiente, no solo de la Key abierta.

Pero en disco la cosa no está tan clara, y los autores tampoco lo dicen expresamente, pero parece ser que sí, que el registro se puede fragmentar.

El registro se guarda en disco en bloques de 4KB. Es decir, aunque necesitemos 1 byte, el hive crecerá en 4KB. Eso no quiere decir que hayamos desperdiciado el resto, que no va a ocurrir, ya que luego dicho bloque se irá rellenando conforme se vayan añadiendo claves.

Ese es el primer nivel de indirección. Encima existe un nuevo nivel lógico, en el que el registro se distribuye en forma de listas sobre dichos bloques, y que realmente conforman la estructura que nosotros vemos. Digamos que este nivel consiste en una especie de mezcla de árbol y lista enlazada que se va distribuyendo a lo largo de los bloques en disco.

La combinación de ambas estructuras permite la fragmentación de forma muy fácil: tan solo hay que ir creando árboles y luego eliminando nodos y árboles anteriores. De hecho, en poco tiempo podríamos tener un buen nivel de fragmentación, sobre todo cuando hemos instalado y desinstalado un montón de programas.

No obstante, Windows lucha contra esta fragmentación mediante la compactación de los espacios libres. Es decir, cuando en un bloque físico hay huecos, estos serán compactados para dejar el mayor espacio libre contiguo, para –creo entender- volver a ser reutilizado.

No obstante, el bloque no será liberado de disco hasta que no esté completamente vacío, y sólo cuando esté al final del fichero. Es aquí donde puede venir la fragmentación, ya que podríamos tener muchos bloques vacíos por en medio y una jodida clave al final que nos impidiera la limpieza de un buen espacio en disco, ya que el algoritmo de desfragmentación no coloca los huecos al final, sino que simplemente los maximiza.

Y aquí es donde entra la estadística. Estadísticamente hablando, siempre habrá algo de fragmentación porque habrá huecos intermedios, que serán rellenados conforme se vayan necesitando, y en el peor de los casos el registro no decrecerá en tamaño, pero tampoco crecerá hasta que todos los huecos estén rellenos.

Lo que más me jode de todo esto es que en el libro no está muy claramente contado, sino que el tema de la fragmentación se recorre de puntillas, como si fuera un pecado. Tan sólo se comentan, de pasada, dos funciones que podrían permitirnos compactarlo y quizás decrecerlo: RegSaveKey() y RegReplaceKey(), que dice son usadas por Windows Backup.

Podríamos intentar jugar con dichas funciones para ver si conseguimos compactar nuestro registro pero, siéndoos sincero, no tengo ganas de trastear con algo que podría dejar mi ordenador completamente inservible sólo por ganar unos cuantos megas de disco…

Acronis ya no es lo que era

Vamos a empezar el año dando caña de la buena. Acronis siempre ha sido un buen referente en cuanto a copias de seguridad. Recomendado por muchos expertos, yo, sin serlo, nunca había experimentado problema alguno con el programa. Ni cargaba el sistema, ni producía efectos laterales indeseados y ni siquiera se notaba cuando se estaba realizando la copia de seguridad.

Pero la versión 2010 es otra cosa. Aunque tiene cosas buenas, como que ahora no tarda un cuarto de hora a arrancar analizando las particiones y los discos (y tengo seis). Otra ventaja es que ahora las cosas están mucho más fáciles de encontrar. Se nota que han reorganizado la ergonomía del programa pensando en el usuario novel y con prisas. Antes encontrar las tareas programadas podía ser una odisea de movimientos por el programa.

No obstante esas mejoras, hay otros aspectos del programa que ciertamente lo hacen, a mi modo de ver, completamente inusable. Y el problema no está en los fallos, que los tiene, el problema está en la confianza que te inspira el programa. Hablamos de copias de seguridad, de lo que quizás salve a tu empresa del desastre total. Y si un programa de este tipo no te inspira confianza, lo mejor es que lo quites pese a haberlo pagado a tocateja. Podemos aplicar lo mismo al caso del usuario no profesional. Imagina que pierdes todas las fotos y vídeos hechos a tu hijo…

El programa usa el 100% de la CPU cuando está lanzado. En un Dual Core será el 50%, y el 25% en un Quad. Aunque no esté haciendo nada. Acronis dispone de un servicio que es el que realmente hace el trabajo duro. El programa que lanzamos no es más que un interfaz hacia dicho servicio, y es ese programa el que consume toda la CPU.

Sigue en memoria una vez cerrado y ya no podemos volverlo a lanzar. Es decir, cuando lo cierras, deja de usar el 100% de la CPU pero se queda en memoria, menos mal que sin usar tiempo de proceso. Pero a partir de ese momento ya no podremos volver a lanzarlo. Irán creándose procesos con cada lanzamiento pero no aparecerá nada en pantalla hasta que no los hayamos matado todos (o reiniciado).

Las copias shadow interfieren con la shell. Acronis 2010 tiene un sistema de copias shadow sobre otra unidad, lo que significa que realiza la misma funcionalidad que el “restaurar versiones previas” del propio Windows, pero la copia está en otra unidad de disco, y así matamos dos pájaros de un tiro: copia de seguridad y versionado de archivos. El problema está en que si tenemos eso activado a veces resulta por completo imposible mover o borrar archivos de la unidad protegida. El explorador se queda indefinidamente esperando algo.

Las copias shadow se desactivan solas. Sí, cuando menos te lo esperas, miras el icono de la barra de tareas y resulta que el servicio se ha detenido, sin aviso alguno, y si intentas iniciarlo suele negarse a hacerlo si no abres el Acronis completo. Y entonces a veces empieza desde cero, borrando lo que ya tenía y rehaciendo la imagen. Para confiar en él.

Problemas varios con el secure zone. Cuando activas esta característica (que es una partición especial no visible por el sistema en donde se guardarán y gestionarán de forma automática las copias de seguridad), aparece una nueva unidad en el Explorador. Si intentamos abrirla nos salta el UAC, y una vez aceptado, podemos ver todas las copias de seguridad que tengamos ahí, acceder a ellas y recuperar ficheros. Lo que parece sencillo no lo es. En primer lugar es lento de cojones. En segundo no te muestra todas las copias (en concreto las incrementales). Y en tercero muchas veces aparecen como vacías.

Montar unidades. Acronis permite montar ficheros de copia de seguridad como unidades de disco. Cuando funciona, claro, porque la mayoría de veces te dice que no encuentra una unidad de disco libre (yo tengo 19), y cuando lo monta tarda como cuarto de hora mientras el ordenador se queda como autista, y a veces tampoco lo consigue.

Ralentiza la carga de Windows entre 20 y 60 segundos según cómo lo pilles. Mi Windows 7 tarda sobre 40 segundos a arrancar desde que se quita la pantalla del POST de la BIOS hasta que veo el escritorio. Teniendo el Acronis ese tiempo pasa de 40 a 60 u 80, y a veces más.

Si os dais cuenta, prácticamente todas las características del programa tienen alguna pega, incluso las que funcionaban bien en versiones anteriores. Entiendo que son cosas muy complicadas, sobre todo las novedades, pero es que llevan como diez revisiones de la versión 2010 y no dan pie con bola. Se escudan en los cambios que Microsoft ha hecho al núcleo y a los servicios de Windows, pero a mi modo de ver eso no es más que una excusa para no solucionar los problemas, ya sea porque no quieran o porque no puedan.

Son todas las cosas que me están ocurriendo a mi en mi Windows 7 x64 con 8GB de RAM en un Q4 con 6 discos duros. Si os vais a los foros de Acronis en la web del fabricante veréis cómo son miles las personas que están teniendo problemas similares.

Esto me lleva a pensar en una idea aprovechando las características de Windows Vista y 7: utilizar las copias shadow para hacer una copia. El algoritmo sería el siguiente:

  • Crear una copia shadow.
  • Montarla en una carpeta o unidad.
  • Copiar dicha unidad en otro disco, quizás zipeándola o de cualquier otra forma.
  • Desmontar la copia.
  • Opcionalmente borrarla.

Tan solo tendríamos que programar una tarea con un script de comandos con dichas instrucciones y listo, se podría copiar periódicamente. Incluso podríamos tener una especie de shadow en destino, haciendo una copia en una carpeta diferente cada día de la semana.

Yo tengo algo parecido ahora mismo, pero simplemente se trata de un xcopy sobre las carpetas normales, con lo que los ficheros abiertos no se copian. Y son los abiertos los que interesa copiar, ya que son los que han variado.

Siendo un poco cucos y usando el atributo de archivo (que se pone cada vez que un fichero ha sido modificado) podríamos tener un histórico semanal o quincenal o mensual.

Imaginemos que tenemos la unidad Z:, que es donde están nuestros datos. Hacemos una copia shadow y la montamos en, por ejemplo Z:\Shadow. Hacemos la copia de dicha carpeta sobre nuestra unidad de copia de seguridad, digamos M:\Backups\<dia_de_la_semana>. Y copiamos sólo los archivos con el atributo de archivo activado, con lo que en M:\Backups\<dia_de_la_semana> tendremos los cambios de ese día. Si generamos un registro de los ficheros copiados, y queremos recuperar la última versión de uno de ellos, abrimos dicho log y buscamos la última ocurrencia del mismo. Si hemos decidido tener un histórico semanal, cada domingo hacemos una copia completa. O incluso podemos tener una cola de copias de seguridad. Si estamos a jueves, el miércoles será la copia anterior, y así hacia atrás hasta el viernes. Evidentemente podemos elegir la periodicidad que queramos siempre que tengamos sitio en la unidad.

El problema es que no sé cómo hacerlo. He estado mirando varios comandos pero no he conseguido sacarle punta, y si Windows lo hace automáticamente, también podrá hacerse a mano. Es cuestión de investigar más a fondo, y yo no tengo tiempo. Lo dejo abierto para quien quiera hacerlo, y si me lo envía se lo publicaré aquí.

BingOS o cómo no reventar de impaciencia

Ya es oficial. Ya no tengo que morderme los nudillos, ni ponerme esparadrapos en la boca, ni pisarme a mi mismo para evitar soltarlo: Microsoft ha lanzado un nuevo sistema operativo especialmente diseñado para los ultraligeros y toda su familia anexa, por lo que tampoco descarto ver lectores de libros-e con esta tecnología en sus tripas.

Como está basado en Windows CE, el núcleo será igual de estable, y al ser una versión “destripada”, todavía más. Por ejemplo, yo una vez conseguí una imagen de CE de 4MB conteniendo la GUI completa (también IE) e ignorando muchas cosas accesorias.

La ventaja evidente es que muchos de los programas existentes para CE (léase Windows Mobile) funcionarán sin modificar…

Pero la cosa no termina ahí, sino que, como nos tiene habituados, MS sacará varias versiones, incluso una “Starter” que sólo podrá navegar por determinados sitios, así como otra con sólo la posibilidad de navegación.

Parece ser que la idea es darle una buena coz a Google, y aunque MS sea un dinosaurio lento y torpón, si se decide a poner en marcha la máquina, saca ventaja a quien sea. El anuncio más o menos oficial, aquí: http://www.ethek.com/bingos-el-nuevo-sistema-operativo-de-microsoft/.

Concurso “NET Minds”

A veces a uno lo sorprenden (y cuando digo sorprenden es sorprenden). Y encima gratamente. Primero la imagen que os llevará a la web:

image

Se trata de un juego en el que los tres primeros en terminarlo tendrán un Visual Studio 2010 con suscripción a la MSDN Premium, todo ello valorado en muuuuuuuuuuuuuuuucha pasta gansa.

Como podréis comprender, yo no voy a participar porque si no sería el seguro ganador de no una, sino de las tres suscripciones.[tachado]

No se trata de un concurso de sapiencia sobre desarrollo (y si lo fuera volvería a ganar[tachado]), sino un concurso de inteligencia del estilo de

En el armario de un jefe de proyecto nos encontramos que todos los polos son blancos menos dos, todos son azules menos dos y todos son rosas menos dos.

¿Cuántos polos hay de cada color?

Por eso cualquiera puede participar, no tenéis que ser ni genios de la programación (como yo[tachado]) sino genios a pelo (también como yo[tachado]).

PS: Sí, ya sé, se me olvidó tomar las pastillas de humildad y modestia, y pero es que no tengo abuela y estoy de coña. No vaya nadie a mosqueárseme por un quítame allá esas pajas (mentales, digo, no de las otras).

NOTA: Las partes entre paréntesis en las que pongo mis tonterías, y el párrafo que empieza por "Como podéis comprender" los he escrito tachados, pero en la visualización no aparecen como tal. Cosas de los BUGS.

Salió QT 4.6

Pues eso, que ha salido hace nada la versión 4.6 de QT, con importantes añadidos como el soporte para dispositivos táctiles multitouch, grandes aceleraciones en el renderizado gráfico y soporte para Symbian, o eso es lo que dice, entre otras cosas, la noticia de la salida del mismo. Podemos obtenerlo desde aquí: http://qt.nokia.com/.

Si no queremos soporte para Visual C++, con instalar el paquete está todo listo para funcionar. No obstante, dado que el compilador de Visual C++ es significativamente mejor que el de GNU, es recomendable instalarse el AddOn para Visual Sutdio y compilarse una versión para él.

Compilar QT para Visual C++
Ya lo expliqué aquí, y parece ser que esta versión tiene el mismo bug a la hora de compilarnos la versión para Visual C++, por lo que hay que seguir los pasos descritos allí. De todos modos, voy a ampliar algo la información.

QT se instala por defecto en C:\QT\<versión>, y yo voy a asumir que lo has hecho así. En mi caso tengo un fichero BAT con el siguiente contenido:

cd %1\qt
configure -debug-and-release -shared -no-qt3support –opensource
nmake
del src\3rdparty\webkit\WebCore\tmp\moc\release_shared\mocinclude.tmp
del src\3rdparty\webkit\WebCore\tmp\moc\debug_shared\mocinclude.tmp
copy src\3rdparty\webkit\WebCore\tmp\moc\release_shared\moc_QnetworkReplyHandler.cpp src\3rdparty\webkit\WebCore\tmp\moc\debug_shared\
nmake
cd ..
cd ..

Tan sólo hay que posicionarse en una consola de comandos en la carpeta de C:\QT y ejecutar el comando configure <ruta>, picar “y” cuando se nos pregunte y esperar las varias horas que tarda a compilarse el tema.

La consola
Tenemos que abrir una consola de compilación, que trae listo un entorno de compilación para Visual C++. Si tenemos instalado el SDK, la consola está en su carpeta en el Menú Inicio, y si no usaremos la de Visual C++.

Una vez abierta tenemos que elegir el entorno deseado con el comando “setenv”, que reflejamos en la captura:

image

Debemos elegir entre “/Debug” o “/Release”, entre “/x86”, “/x64” o “/ia64”. También entre “/xp”, “/vista”, “/2003”, “/2008” o “/win7” como sistema operativo base. Hasta donde yo sé, esta última opción no afecta para nada al resultado de la compilación de QT, por lo que con poner “/xp” nos basta. Las otras sí que afectan, construyéndonos QT alrededor de un runtime de depuración o final, o un QT de 32 ó 64 bits. La opción “/ia64” no sé si generará algo útil o no.

Publicado por Rafael Ontivero | 4 comment(s)

Obtener tu propia versión

Muchos habréis observado que en las propiedades de un ejecutable o una DLL suelen aparecer, en la pestaña Detalles de las propiedades, una serie de nombres y de datos sobre la versión de dicho fichero.

Eso se consigue embebiendo en el ejecutable un recurso de versión, que en Win32 se llama RCVERSION y que consta de una serie de cadenas y valores predefinidos que podemos rellenar a nuestro gusto.

En las imágenes de abajo podemos ver un ejemplo añadido a un ejecutable, tanto desde C++ Builder como desde Visual C++.

image

image 

Si os fijáis, ambas tienen más o menos los mismos elementos, pero en el caso del C++Builder existe la opción de incrementar el número de Build automáticamente. Hasta donde yo sé, eso no lo permite Visual C++ (quizás se haga en conjunción con un CVS o cuando haya uno compatible), y básicamente consiste en incrementar automáticamente el último numerito cada vez que compilemos algo. A lo largo de diferentes versiones del producto, este incremento se producía cuando se hacía un Build, un Make o incluso nunca (debido a un bug). En la versión 2010 se hace cada vez que compilemos algo.

Bueno, una vez que hemos especificado nuestro recurso de versión, es tiempo de que podamos leerlo desde el propio ejecutable para poderlo poner, por ejemplo, en la pantalla de “Acerca”, en la de “Splash” o donde queramos. Si os dais cuenta, esos números suelen aparecer en muchas aplicaciones.

El código es muy sencillo:

DWORD handle;
DWORD size = GetFileVersionInfoSize(Application->ExeName.w_str(), &handle);
wchar_t *buffer = new wchar_t[size + 1];
GetFileVersionInfo(Application->ExeName.w_str(), 0, size, buffer);
VS_FIXEDFILEINFO *pFixedInfo;
UINT uVersionLen;
if (VerQueryValue(buffer, L"\\", (void**) & pFixedInfo, (UINT*) & uVersionLen) != 0)
{
    String version = L" " + String(HIWORD(pFixedInfo->dwFileVersionMS)) + L"." + String(LOWORD(pFixedInfo->dwFileVersionMS)) + L"." + String
    (HIWORD(pFixedInfo->dwFileVersionLS)) + L"." + String(LOWORD(pFixedInfo->dwFileVersionLS));
    m_caption = Caption + version + L" - ";
    Caption = m_caption + m_document->FileName;
}
delete[] buffer;

El ejemplo que he puesto es para C++ Builder Unicode, pero resulta trivial convertirlo a C ó C++ estándar. Tan sólo hay que cambiar el tipo de las cadenas por las adecuadas.

Primero se hace una llamada a GetFileVersionInfoSize() pasando la ruta de nuestro ejecutable y un DWORD que de nada sirve. Con el tamaño del recurso, llamamos a GetFileVersionInfo() volviendo a pasar la ruta del ejecutable y un buffer con el tamaño adecuado.

Después creamos un puntero a una estructura del tipo VS_FIXEDFILEINFO y la pasamos a la función VerQueryValue(), que también recibe el buffer original, qué parte del recurso queremos sacar (“\\”, es decir, la versión raíz –los recursos de versión se pueden embeber unos dentro de otros) y algún que otro valor más.

Si esta función encuentra lo que le decimos, entonces podemos usar el puntero de la estructura y formatear una cadena con los cuatro números de versión.

Lo que hagamos con esa cadena es cosa nuestra. En este caso la añadimos al título de la ventana actual y lo guardamos en una variable miembro que usaremos cada vez que cambiemos de documento.

Y no nos olvidemos de liberar la cadena asignada.

Publicado por Rafael Ontivero | 4 comment(s)
Archivado en: ,,

Manifiesto en defensa de los derechos fundamentales en internet

Origen: http://fernand0.blogalia.com/historias/65244

Ante la inclusión en el Anteproyecto de Ley de Economía sostenible de modificaciones legislativas que afectan al libre ejercicio de las libertades de expresión, información y el derecho de acceso a la cultura a través de Internet, los periodistas, bloggers, usuarios, profesionales y creadores de internet manifestamos nuestra firme oposición al proyecto, y declaramos que...
1.- Los derechos de autor no pueden situarse por encima de los derechos fundamentales de los ciudadanos, como el derecho a la privacidad, a la seguridad, a la presunción de inocencia, a la tutela judicial efectiva y a la libertad de expresión.
2.- La suspensión de derechos fundamentales es y debe seguir siendo competencia exclusiva del poder judicial. Ni un cierre sin sentencia. Este anteproyecto, en contra de lo establecido en el artículo 20.5 de la Constitución, pone en manos de un órgano no judicial -un organismo dependiente del ministerio de Cultura-, la potestad de impedir a los ciudadanos españoles el acceso a cualquier página web.
3.- La nueva legislación creará inseguridad jurídica en todo el sector tecnológico español, perjudicando uno de los pocos campos de desarrollo y futuro de nuestra economía, entorpeciendo la creación de empresas, introduciendo trabas a la libre competencia y ralentizando su proyección internacional.
4.- La nueva legislación propuesta amenaza a los nuevos creadores y entorpece la creación cultural. Con Internet y los sucesivos avances tecnológicos se ha democratizado extraordinariamente la creación y emisión de contenidos de todo tipo, que ya no provienen prevalentemente de las industrias culturales tradicionales, sino de multitud de fuentes diferentes.
5.- Los autores, como todos los trabajadores, tienen derecho a vivir de su trabajo con nuevas ideas creativas, modelos de negocio y actividades asociadas a sus creaciones. Intentar sostener con cambios legislativos a una industria obsoleta que no sabe adaptarse a este nuevo entorno no es ni justo ni realista. Si su modelo de negocio se basaba en el control de las copias de las obras y en Internet no es posible sin vulnerar derechos fundamentales, deberían buscar otro modelo.
6.- Consideramos que las industrias culturales necesitan para sobrevivir alternativas modernas, eficaces, creíbles y asequibles y que se adecuen a los nuevos usos sociales, en lugar de limitaciones tan desproporcionadas como ineficaces para el fin que dicen perseguir.
7.- Internet debe funcionar de forma libre y sin interferencias políticas auspiciadas por sectores que pretenden perpetuar obsoletos modelos de negocio e imposibilitar que el saber humano siga siendo libre.
8.- Exigimos que el Gobierno garantice por ley la neutralidad de la Red en España, ante cualquier presión que pueda producirse, como marco para el desarrollo de una economía sostenible y realista de cara al futuro.
9.- Proponemos una verdadera reforma del derecho de propiedad intelectual orientada a su fin: devolver a la sociedad el conocimiento, promover el dominio público y limitar los abusos de las entidades gestoras.
10.- En democracia las leyes y sus modificaciones deben aprobarse tras el oportuno debate público y habiendo consultado previamente a todas las partes implicadas. No es de recibo que se realicen cambios legislativos que afectan a derechos fundamentales en una ley no orgánica y que versa sobre otra materia.

Publicado por Rafael Ontivero | 2 comment(s)

Cómo se accede al hardware

Dado el éxito que están teniendo estas entradas, voy a dar otra vuelta de tuerca al tema y voy a explicar cómo un programador puede trastear con el hardware, cosa que al final, todos los micros terminan haciendo. No voy a hablar de Windows, ni de ningún otro sistema operativo, sino que voy a intentar explicar cómo, a través del código, se accede al hardware, periféricos y demás. Esta entrada es completamente teórica y no me voy a centrar en ningún micro ni hardware en concreto, ya que no vale la pena dada la amplia variedad disponible y que, además, esto no se puede hacer desde Windows (ni desde cualquier otro PC si no arrancamos con algo como MS-DOS y a veces ni siquiera así).

Todos sabéis (o deberíais) que un microprocesador consta de varios subconjuntos lógicos: la ALU, que hace cálculos aritméticos, los registros de propósito general, que son unas celdillas de memoria interna al micro para usos varios, la unidad de decodificación y ejecución, que se encarga de interpretar la instrucción cargada, y en micros más modernos podemos encontrarnos con módulos convertidores analógico-digitales, unidades de coma flotante, unidades SIMD, etc.

La arquitectura de los microcontroladores es algo diferente a la de los microprocesadores, muchas veces sólo en apariencia, aunque en general programar un microcontrolador es más fácil porque normalmente sólo hay que ir activando bloques y funcionan sin más. Es decir, si queremos un timer, o un AD, o una UART (puerto serie, para entendernos), simplemente se activan y configuran con las instrucciones adecuadas y listo. En un microprocesador suele ser más complicado, ya que hay de por medio otros temas, como prioridades entre los dispositivos y demás.

Por otro lado, cuando en informática se habla de unos y ceros, se está haciendo una importante abstracción lógica. En general, a nivel de señales eléctricas, lo que se entiende por un uno podría ser que una pata del micro esté a 5V, pero no siempre es así, porque si es una arquitectura basada en lógica negativa, un uno será cuando dicha pata esté a 0V y un cero cuando valga 5V. Es decir, dependiendo de la lógica, un uno será una cosa u otra. De hecho, conforme vamos avanzando en velocidades, un uno ni siquiera es un nivel, sino un flanco. En otras palabras, cuando nosotros leemos una pata de un micro a 1 lo que hemos leído es el paso de la misma de 0 a n voltios, o al revés. De hecho, nunca debemos asumir que, si leemos algo y está a 1, la pata equivalente vaya a estarlo también. Dependerá de muchas cosas y siempre tendremos que morir en la documentación técnica del micro, de la placa o de quien quiera que haya hecho nuestro hardware.

Y con esto hemos dado un paso más: podemos leer el nivel (o mejor dicho, el estado) de una pata. Es decir, dentro de nuestro micro hay algo que cuando nosotros ejecutamos una instrucción en ensamblador, nos devuelve un 0 o un 1 dependiendo de lo que haya ocurrido en una de sus patas. En general, y dependiendo del tipo de micro, las patas vienen agrupadas en bloques de 8 o 16, que es lo que se conoce como un puerto, y una lectura sobre él nos devolverá un byte o un word con el equivalente lógico.

En los microcontroladores es habitual que el espacio de direcciones se encuentre separado por tipos. Tendremos un espacio de direcciones para el código, otro para la RAM (ya sea interna o externa), otro para los puertos que acabamos de ver, para EEPROM interna si tenemos, etc. O visto de otra forma: no nos vale con decir que queremos leer de la dirección de memoria 0x12345678, sino que tenemos que indicar también de qué tipo es. En general, esto se consigue con el tipo de instrucción ensamblador, que es diferente para cada uno.

No obstante, a veces los espacios están mezclados, o solo tenemos uno, o dependiendo del rango de dirección estaremos actuando sobre la RAM, la ROM o lo que sea. Todo ello depende del micro y cómo se configure, y os puedo asegurar que cada fabricante lo hace de una forma, no sólo para los suyos, sino incluso para cada familia o grupo.

Si esos elementos están dentro del micro, lo tenemos fácil, ya que con muy pocas instrucciones (y a veces ninguna), éste está listo para funcionar. Hay micros que tienen RAM, EEPROM, ROM, IO, todo ello dentro de sí mismo, pero hay otros en los que algunos de estos elementos están (o pueden estar) fuera, en circuitos independientes.

Es entonces cuando entran en juego el chip select, que son unas patas extra que sirven para alertar al dispositivo adecuado. Supongamos por un momento que nuestra arquitectura tiene todos los elementos externos y que vamos a configurarlos de la siguiente manera: de la dirección de memoria 0 a la 0x7fff, está la ROM en el chip select 0. De la 0x8000 a la 0x8fff tenemos la EEPROM, y de la 0x9000 a la 0xafff la RAM. Lo primero es conectar esos rangos del bus de direcciones con cada chip equivalente. Por ejemplo, para la ROM podemos dejarnos sin conectar la pata 15 del bus de direcciones porque no va a ser usada nunca, ahorrando complejidad en las pistas.

Luego tenemos que cablear los chip select, que en nuestro caso son dos patas extra del micro. Cuando en ellas haya un 00, estaremos accediendo a la ROM, cuando haya un 01 a la RAM, y cuando haya un 10 a la EEPROM. Si nos damos cuenta nos queda la combinación 11, que podríamos asignar a periféricos o dejar sin usar. Tenemos que poner combinaciones de puertas lógicas de tal forma que cada una de esas combinaciones activen el o los chips correspondientes.

¿Cómo? Pues con lógica combinatoria, puertas AND, NAND, NOR, XNOR, etc. No hay una única solución, y cuando las placas son algo complejas se suele usar una FPGA o similar, que debe ser programada para esto y para más cosas. Recordemos que quizás un chip esté activo cuando su pata CS esté a cero, y si se tratara de los chips donde está la ROM, tendríamos que primero negar los dos CS que salen del micro y luego poner una NAND entre ellos (y da la casualidad que hay un chip con esa configuración exacta).

Ahora que ya tenemos el hardware, nos hace falta el software. Lo primero es decirle al micro qué CS se corresponde a qué área de memoria, de forma que cuando escribamos dentro de un rango, se active automáticamente la combinación de CS elegida.

No lo he dicho, pero un micro tiene un bus de direcciones y un bus de datos. En el bus de direcciones se pone la dirección a acceder, y en el de datos o bien obtenemos el dato que haya en esa dirección o bien lo ponemos nosotros para que sea leído por el elemento externo. En el caso que estamos viendo, se trata de un micro de 8 bits, que tendrá un bus de 16 bits (generalmente 16 patillas dedicadas a ello, aunque a veces no son tantas y entonces hay que hacer “trucos” que no voy a explicar aquí) y otro de 8 para los datos. Volviendo a los párrafos anteriores, tanto el bus de datos como el de direcciones está conectado a todos los chips externos que los necesiten (ya hemos visto que, por ejemplo, el bit de mayor peso del de direcciones no es necesario para la ROM y por tanto no es necesario cablearlo). Con el CS correspondiente, tan sólo se activará el chip correspondiente, que atenderá la petición.

Ahora necesitamos una pata extra, la WR, para indicar al periférico si queremos leer o escribir. Esto también lo controla el micro automáticamente, es decir, si ejecutamos una instrucción de ensamblador que es una lectura, cambiará la pata de forma adecuada y automáticamente.

Pero todavía no hemos terminado. Nos falta el reloj, ya que sin él todo lo que hemos explicado estaría muerto. El reloj no es más que un generador de onda cuadrada (más o menos, he visto relojes con el osciloscopio que me dan serias dudas de que la placa funcione) al que se conectan absolutamente todos los periféricos y el micro, y aquí sí, aquí lo válido son los flancos. A cada flanco, se produce un paso. Es lo que se llama ciclo de reloj. En algunos micros y hardware son válidos tanto los flancos de subida como los de bajada, en otros sólo los de subida o los de bajada.

Ante cada ciclo, se produce un evento en el sistema. Imaginemos que el micro ya sabe qué instrucción va a ejecutar. Pongamos por caso que se trate de una lectura de una dirección de RAM. Pasa un ciclo y el micro mueve las patas de los CS y WR, alertando al chip de la RAM. En el caso que nos ocupa, los CS valdrían 01, lo que activaría la entrada CS de la RAM (y sólo de la RAM). Al ciclo siguiente el micro pone en el bus de direcciones la dirección a leer. Dado el CS adecuado, sólo la RAM se pondrá a la escucha, o validará lo que quiera que venga después. En el siguiente ciclo la RAM leerá del bus de direcciones, y necesitará un determinado número de ciclos más para hacer su trabajo, al cabo de los cuales pondrá en el bus de datos el valor solicitado, y en el siguiente, el micro lo tomará. Esto es sólo un posible ejemplo de lo que ocurre. Algunos elementos necesitan un solo ciclo. Por ejemplo, el micro podría poner la dirección, los CS y el WR en un solo ciclo, y la RAM ser tan rápida que al siguiente ya tenga la solución en el bus de datos. En general, cuando un elemento complejo necesita un solo ciclo, es que dicho elemento es capaz de subdividir cada ciclo en varios más de forma interna.

Otra de las cosas que debemos decirle al micro antes de empezar es cuántos ciclos tiene que esperar a que el dato pedido esté disponible o sea grabado para cada CS o rango de direcciones. De hecho, grabar todos estos valores (y más) es lo que se conoce como startup del micro. Si os fijáis, el CS para la ROM es 00, lo que viene a decir que cuando el micro arranque empezará a leer a partir de una dirección predefinida (a veces dependiendo de cómo se encuentren las patas de arranque, por llamarlas de alguna manera). Es en esa dirección, y siguientes, en las que se encuentran las instrucciones para ir activando y configurando todos estos elementos.

De hecho, cada micro, cada familia de micros, cada fabricante, lo hace a su manera, indica unas direcciones y unas formas concretas, no sólo de cómo arranca un micro, sino también del tipo de lógica, periféricos permisibles, velocidades de reloj, retardos máximos y mínimos, y un larguísimo etcétera. Por ejemplo, los micros ARM tienen un encendido más que curioso. Necesitan de un chip que inyecte, a través de un canal serie, el código de arranque. Es decir, al encendido el micro está muerto, y es un chip externo el que, a través de una de las patas del micro, inyecte el código necesario para que éste sea capaz de arrancar. Se trata de un sistema de seguridad bastante potente, porque en ese código podría estar el algoritmo para decodificar una ROM encriptada.

***

Bueno, ya tenemos nuestro micro encendido y funcionando. ¿Qué? Ah, se me olvidaba, que todavía no sabemos cómo decirle todo lo que hemos visto. Aquí la cosa resulta algo más sencilla, ya que sólo hay unas pocas aproximaciones posibles.

En general, cada micro tiene un conjunto de registros especiales, que es donde nosotros escribiremos y leeremos la configuración. A veces el registro viene implícito en la instrucción, es decir, existe una única instrucción para modificar dicho elemento. Si tenemos un AX, BX, DX, que vienen codificados en la instrucción, también podemos tener un MBAR…

Cuando uno ejecuta una instrucción en ensamblador, por ejemplo “MOV AX,1234”, su código de operación (lo que realmente está leyendo el micro), es 0x87, por lo que, en la ROM, nos encontraríamos con los valores hexadecimales 0x87, 0x34, 0x12. Con esto no quiero decir que para un Intel, el código de operación de esa instrucción sea 0x87, es solo un ejemplo inventado.

En binario, ese código de operación es 10 000 111. Fijaros que lo he dividido en tres grupos. Lo que hace el micro, cuando está ejecutando esa instrucción, es leer los dos primeros bits, 10. Esos dos primeros bits le dicen a la unidad de decodificación que es una instrucción MOV. Podríamos tener cuatro combinaciones, 00, 01, 10, y 11. La primera podría ser para JMP, la segunda para CMP, la tercera para MOV y la cuarta para una instrucción multibyte o extendida. Es sólo un ejemplo.

Ya sabe que es un MOV, y también sabe que los tres bits siguientes son el registro de destino, y los tres últimos el de origen, así que sigue decodificando (quizás en un nuevo ciclo de reloj). 000 se corresponde al registro AX, y 111 a código inmediato, es decir, el registro de origen no es un registro, sino los dos siguientes bytes dentro de la ROM, por lo que inicia la secuencia descrita arriba para obtenerlos, y una vez que lo tiene todo, termina ejecutando el tema y poniendo 0x1234 en el acumulador. Y aquí es donde entran los ciclos máquina que cada instrucción necesita, que suelen ser diferentes incluso para cada modo de una misma. Por ejemplo, MOV AX,BX quizás requiera sólo dos ciclos, decodificar y ejecutar, pero MOV AX,0x1234 requerirá 2 ciclos más 4 para obtener el primer byte inmediato más 4 para el segundo, y eso sin contar los de espera hasta que los datos han salido de la ROM.

Si os fijáis, el orden de los bytes inmediatos en el ROM está invertido. Es una de esas cosas que son porque son, sin más justificación que el fabricante así lo ha decidido. Aunque no es realmente cierto, quizás al colocarlos así ahorra en puertas lógicas en la unidad de decodificación, pero es otra cosa más a tener en cuenta, y es el orden de los bytes, Little Endian o Big Endian.

Ahora que ya sabemos cómo decodifica una instrucción un micro, quizás el código de operación 0xc5 más los bytes 0x00 0x1f sean la instrucción para decirle al micro que el MBAR está en 0x1f00. Por tanto, nuestras tres primeras instrucciones han sido ejecutadas, y le hemos dicho al micro que, a partir de ahora, los registros especiales están en 0x1f00.

Esto nos lleva a otra forma de configurarlo. Hay micros en los que los registros especiales son simples direcciones de memoria normales y corrientes pero que, en lugar de acceder a memoria normal (sea del tipo que sea), simplemente cambian la configuración interna del mismo. Quizás escribir 0x01 en la dirección 0xffff sea decirle al micro que está usando memoria externa, y que escribir 0x0000 en las direcciones 0xfffe y 0xfffd respectivamente sean decirle que el chip select 0 empieza en 0x0000. Y a su vez al escribir 0x7ffff en las 0xfffc y 0xfffb le estemos diciendo que la dirección final del CS 0 sea esa misma.

Y lo mismo con los demás CS y sus correspondientes registros mapeados en memoria. Esta es la forma más común, pero así estamos usando direcciones que no podremos utilizar en nuestros programas, por lo que algunos micros utilizan instrucciones especiales. Por ejemplo, si hacemos MOV 0xffff,0x01, estaremos escribiendo en RAM, pero si hacemos IMOV 0xffff,0x01 lo haremos en la configuración del micro.

Pero no os asustéis, en general todo esto ya viene predefinido con los ensambladores y los compiladores mediante definiciones y macros, e incluso a veces los fabricantes implementan construcciones y extensiones a C o a C++ (y a ensamblador) para lidiar con todo esto. Por ejemplo, si en un compilador de C de Imagecraft para Atmel colocamos

__flash int cero=0;

en lugar de

int cero=0;

ese entero irá en ROM en lugar de RAM. Sin embargo, la línea con la extensión, en uno de IAR para el mismo micro, nos colocará cero en la flash interna en lugar de la ROM. Y no quiero entrar en esto, en cómo cada fabricante pone las extensiones que les sale de los cojones y como les salen de los cojones, eso daría para otro post con más mala leche que este.

Volviendo al tema que nos ocupa, si ahora queremos leer el estado de bytes de un puerto, una simple instrucción en C no los soluciona:

unsigned char puerto=PINC;

El compilador hará lo que tenga que hacer para que en puerto esté la representación binaria de bits de lo que quiera que haya en dicho puerto. La macro PINC tiene su intríngulis, ya que podría ser cualquier cosa, desde una llamada a una función intrínseca al compilador hasta una simple lectura de una dirección física o una instrucción en ensamblador, quizás RMOV 0xff33,puerto.

Pongamos por caso que hayamos conectado al bit0 del puerto C (que se corresponderá con una pata del micro), un sensor de una puerta, que pondrá dicho bit a 1 (lógico) cuando la puerta se abra. Por tanto, el código:

void main(void)
{
    inicializa_hardware();
    for(;;)
    {
        if((PINC&0x01)!=0)
        {
            suena_sirena();
        }
    }
}

Nos servirá para construir una alarma. Quizás la función suena_sirena() escriba un 1 en otro bit de otro puerto, cuya correspondiente pata del micro vaya conectada a un transistor que a su vez activará un relé que pondrá en marcha una sirena…

Dentro de inicializa_hardware() habremos puesto sentencias como:

PORTC=0xff; //Encender todos los pull-up internos del Puerto C
DDRC=0x00; //Poner el Puerto C como entradas
PORTD=0x00; //Quitar los pull-ups del Puerto D
DDRD=0x01; //Poner el Pin0 del Puerto D como salida
PIND=0x00; //Apagar la sirena.

Los comentarios son auto descriptivos, y nos enseñan cómo poner un puerto como elemento de entrada (para leer datos de él) o de salida (para escribir), si queremos tener pull-ups (unas resistencias especiales para añadir carga), etc. Luego esto será traducido a código ensamblador del micro correspondiente, y de nuevo la parte izquierda del igual son, en este caso, macros.

De todos modos la cosa no es tan sencilla. En este caso da igual que escribamos en un puerto de entrada, el micro lo ignorará, pero en otros microprocesadores no se puede hacer a riesgo de romper algo, por lo que operaciones mixtas requieren técnicas de doble buffer, como mantener un juego del estado de entradas o salidas anteriores (lo que de paso nos daría para mantener flancos lógicos, que nada tienen que ver con los físicos de la placa).

Por si no ha quedado claro, hacer algo como PINC|=0x01; significa leer el estado del puerto C y, sin importarnos cuál es el valor del bit0, ponerlo a uno (ya que una o lógica con uno de los dos bits a 1 siempre resultará en 1) sin modificar los demás y volver a escribir el resultado en el puerto. Como antes hemos leído los valores, sólo estamos cambiando el bit0. Pero ocurre que en algunos micros las salidas no se pueden leer (no es que no se puedan, sino que no leen nada coherente), por lo que tenemos que mantener variables de estado (de nuevo el doble buffer).

Esta operación de escritura en ese puerto, quizás sea compilada como una escritura en una dirección de memoria, o la ejecución de una instrucción especial de ensamblador, pero el resultado final será que la pata del microprocesador que se corresponda con ese bit pasará a estar a un uno lógico, que realmente podría ser ponerse a 5V, o a cero, o cambiar su estado, o ejecutar un flanco de subida, o de bajada, o uno de subida y luego otro de bajada, o justo al revés. Todo dependerá de qué micro y cómo esté configurado.

Cómo salta Windows entre anillos (Modo Kernel y modo Usuario)

Esta semana estamos de fiesta, y es que una de mis pasiones en el desarrollo siempre ha sido verle las tripas a Windows, y a ello vamos. Antes de nada, lo que voy a contar aquí es un resumen del punto System Service Dispatching del capítulo 3 de Windows Internals 5ª edición, pero es un resumen un poco especial porque voy a añadir cosas de mi cosecha, como siempre hago, ya que no es cosa de ir parafraseando lo que voy aprendiendo/recordando.

En entradas anteriores, y algunas veces en los foros, he comentado sobre saltos entre anillo 0 y anillo 3, y aquí voy a explicar de pasada lo que significa. Un microprocesador moderno (y no tan moderno), cuenta con un juego de instrucciones en cierta medida especiales, instrucciones que son necesarias para configurar el hardware y el propio funcionamiento del software. Hablamos aquí de las interrupciones y toda su parafernalia asociada, del control de la memoria virtual, del acceso directo a hardware (es decir, escribir o leer en una dirección de memoria o en un puerto en el que no hay tal, sino alguna electrónica que debe reaccionar en base a dicha lectura o escritura), etc.

Esas instrucciones son peligrosas de usar, y en general no deben estar disponibles para los programas normales, ya que entonces un error podría llevar a la caída total del sistema o incluso a la rotura de algún elemento electrónico.

Es decir, que podríamos dividir el juego de instrucciones de un microprocesador en dos grandes grupos: aquellas que no son necesarias para que un programa funcione normalmente y las que, si se usan de forma incorrecta, pueden terminar armando un buen pirifostio.

Pues básicamente eso son los anillos 0 y 3. En el 0 están las instrucciones peligrosas, en el 3 las que cualquier programa puede usar, y si un programa normal utiliza una del cero, generará una excepción (¿De qué me sonará esto?) que será capturada por el sistema operativo y manejada a su antojo. Aunque realmente no es del todo así, para nuestro propósito es suficiente.

(Si os dais cuenta, he hablado del 0 y del 3, pero también hay un 1 y un 2, que Windows no utiliza. Digamos que la separación de las instrucciones en estos distintos niveles permiten una serie de facilidades a la hora de construir software de sistemas, ya sean sistemas operativos o aplicaciones que vayan a correr en un microprocesador con estas características, lo que ocurre es que Windows aprovecha estos niveles únicamente de una sola forma, que llama modo usuario y modo kernel).

Pues bien, el kelmer, uis, perdón, el kernel, la mayoría de los drivers, y el motor gráfico (que fue ampliamente criticado en su momento pero que permitió que NT 4.0 pudiera ser usado en ordenadores no tan potentes) se ejecutan en el anillo 0, y los demás subsistemas (incluido Win32) en el 3. De esta forma un programador chapucero (o un compilador) sólo podrá tumbar su programa y nada más. Recordemos los días de Windows 3.x y DOS en los cuales tumbar por completo el sistema era tan fácil como ejecutar dos instrucciones de ensamblador seguidas: cli y luego sti, o al revés, ya no me acuerdo.

Una aplicación llama a una función, que debe terminar siendo ejecutada dentro del kernel (por ejemplo, abrir y leer de un fichero, cosa que necesita que los drivers de disco hagan trabajo de bajo nivel sobre la controladora y sobre el propio disco). Pongamos que nuestro programa llama a WriteFile(), y nos da igual que sea una aplicación .NET llamando a sus clases, ya que terminará en esa función. Esa función, que está en Kernel32.dll y pertenece al subsistema Win32, llamará a NtWriteFile(), que está en Ntdll.dll y no pertenece a ningún subsistema, sino que está disponible para cualquiera de ellos. Esta función llamará entonces a KiSystemService(), que está en Ntoskrnl.exe y se ejecuta en el anillo 0 en lugar del 3. Finalmente, esta última función hará lo que tenga que hacer llamando a otras funciones del kernel y de los drivers hasta que al final devolverá el resultado hacia atrás hasta nuestra aplicación.

Fácil, ¿no? Pues no, porque si nuestra aplicación hubiera llamado a WriteFile() con algún parámetro mal que fuera crítico (y se me ocurre pasar un valor aleatorio diferente de cero al handle del fichero sobre el que queremos escribir), lo más seguro es que termináramos en una pantalla azul (como más o menos ocurría en Windows 3.x y a veces en Windows 9x). Además, es muy posible que durante el procesamiento de la función, se salte varias veces entre el modo kernel y el user por necesidades del guión.

Pero todavía hay más, ya que no hemos tenido en cuenta que el controlador de tiempos y de procesos puede cortar en cualquier momento esa llamada para atender otros procesos o incluso hilos de nuestra aplicación. Vamos, que no es fácil ni de lejos.

¿Cómo se produce el salto entre el anillo 3 y el 0, que hemos descrito más arriba? Pues depende del procesador que estemos usando. En la época de Windows 9x (y de la parte en modo protegido de Windows 3.x cuando la había), y para micros antiguos, NtWriteFile(), después de verificar que los parámetros recibidos son correctos, coloca en varios registros del procesador lo necesario, y ejecuta la instrucción int 0x2e, que genera una excepción si se hace desde el anillo 3. Esta excepción es recogida por el Kernel, que mediante los parámetros que hay en los registros determina si es una petición válida (y si no lo es protestará) y la procesará mediante la ejecución de la función correspondiente situada en un array de punteros a función que se llama System Service Dispatch Table, de las que creo que hay varias. Básicamente, lo que está haciendo es recibir un índice que se corresponde a una posición dentro del array de punteros, y como nos equivoquemos con esto la hemos cagado a base de bien, y es por eso por lo que antes del salto entre anillos se verifican los parámetros. El retorno al anillo 3 se produce con la ejecución de la instrucción iretd.

En micros Intel más modernos, el salto se hace mediante la instrucción sysenter y la vuelta mediante sysexit (o iret si estamos en modo depuración). En un AMD las instrucciones son syscall y sysret.

Y finalmente, en micros de 64 bits con la arquitectura x64 las instrucciones son las mismas que en el AMD (por que fueron los primeros en implementarla) y en la I64, epc.

Si os dais cuenta, dependiendo del microprocesador se hace de una u otra forma, lo que puede ser un serio quebradero de cabeza. Lo que hace Windows al arrancar un sistema de 32 bits es instalar la instrucción correcta en una posición de memoria y juego ejecutar sobre ella, con lo que a lo anterior añadimos un nuevo nivel de indirección.

La secuencia completa de paso del anillo 3 al 0 consiste, más o menos, en lo siguiente: primero se verifican los parámetros recibidos de la forma más completa posible, luego se ejecuta la instrucción que hay en cierta posición de memoria, que a su vez generará una excepción, que será controlada por el sistema operativo, que mirará si los parámetros son correctos, y si lo son volverá a saltar sobre un puntero a puntero a función, y finalmente lo que haya allí hará el trabajo.

Eso para un solo salto. Imaginaros cuántos son necesarios para que nuestro programa, y el sistema operativo funcione. Y no hemos hablado de que en cada una de esos pasos es necesario que la pila se guarde o se recupere, y que los datos deben ser copiados desde la pila de modo user a la de modo kernel para evitar que la aplicación pueda modificarlos en medio de una operación interna…

En un sistema x64 la operación es algo más sencilla porque la instrucción de salto es siempre la misma y no es necesaria la indirección extra sobre la instrucción ya que da igual que el microprocesador sea un Intel o un AMD.

Esto quizás te presente una duda en el caso de equipos de 32 bits: ¿por qué no está cableada dicha instrucción y simplemente a la hora de instalar Windows o de arrancarlo, el cargador pone en memoria el juego de DLLs correcto? Pues realmente no tengo ni idea, lo que sí tengo claro es que en ese caso Windows debería contar con al menos 4 juegos completos de DLLs y o bien elegir el necesario durante la instalación o casi cuadriplicar el espacio ocupado en disco. Lo que sí es cierto es que si se hubiera hecho así, el Windows de 32 bits funcionaría sensiblemente más rápido… o no.

Sólo nos queda ver, por mor de completitud, cómo funciona internamente el kernel sobre sí mismo. Supongamos por un momento que el kernel necesita acceder a la funcionalidad que hay en WriteFile(), pero en lugar de llamar a esta función directamente, lo que hace es llamar directamente a la rutina a la que se está apuntando en la tabla SSDT y santas pascuas, ya que en principio no se necesita verificar los parámetros que se suponen son correctos, y aparte de eso ya estamos en el anillo 0. No obstante, un driver no puede hacerlo así porque Microsoft no se fía de ellos, y es por ello que existen unas funciones que tiene como prefijo Zw y que son las que llamarán a su homónimo apuntado por la SSDT después de realizar las verificaciones –menos que si se llamara desde el anillo 3- oportunas.

Eligiendo qué hacer cuando pete nuestra aplicación

En la entrada anterior expliqué qué ocurre cuando una aplicación peta y se genera una excepción no controlada (o controlada pero relanzada), y en esta voy a explicar cómo podemos, desde nuestro propio programa, configurar el modo en que eso ocurre. Pero antes una introducción.

En algunos lados he dicho que las excepciones son caras. Con eso no me refiero a que cuesten dinero, sino a que es un tipo de característica que resulta muy complicada de procesar y que a veces requiere de bastante tiempo de proceso. Cuando se dispara una interrupción en una aplicación, sobre todo si ocurre en el lado del kernel, éste tiene que entrar y salir varias veces entre su modo y el de usuario, operaciones que no son baratas (de nuevo no en dinero, aunque lo mismo en su servidor en producción sí) ya que significan saltar entre los anillos 0 y 3 del procesador. Aparte de eso, se debe recorrer la pila (a ambos lados de los anillos) para ir buscando el controlador adecuado, ejecutarlo y en la mayoría de casos continuar con la búsqueda.

Cuando ponemos un bloque try/catch, o __try/__except ó __try/__finally, el compilador deja “marcas” en la pila, “marcas” que serán leídas por el controlador de excepciones. Dependiendo de cómo lo hayamos hecho, y el número de bloques y de controladores, el sistema tendrá que salvar y restaurar el estado de la pila y de la aplicación para ir ejecutando los diferentes bloques de captura, y a veces incluso se necesita de cierta “inteligencia” para que la ejecución sea la correcta. El tema queda bastante claro una vez que se han leído los capítulos de los dos libros que mencioné, con una excepción: que esos recorridos requieren tiempo, sobre todo si hay saltos entre anillo 0 y 3, por lo que en general el uso de excepciones está para eso: para ser usadas cuando realmente se produzca una, y no alegremente como una mera característica de nuestro lenguaje.

Dicho esto, considero que el ejemplo que Jeffrey Ritcher pone al final del capítulo 25 no es muy correcto, no porque esté mal, sino porque realizar gestión de memoria mediante excepciones me parece que es matar moscas a cañonazos… aparte del desperdicio de la misma. Ritcher crea una hoja de cálculo de un tamaño predefinido pero sin asignar ningún tipo de memoria. Luego, ante cada requerimiento de que una celda sea llenada con algo, intenta escribir en la posición. Si ya hay memoria asignada todo irá bien, pero si no, se disparará una excepción que asignará un bloque a dicha celda. La barbaridad está en que cada asignación, aunque sea de un solo byte, asignará 4KB (que es el tamaño de página de RAM), y en que el gasto de la excepción y su control es enormemente más caro que simplemente comprobar si la memoria está asignada o no. A no ser que se me escape algo, como ejemplo sobre el funcionamiento de las excepciones, vale, pero como ejemplo de algoritmo genérico, no.

De todos modos, el sistema de excepciones es my potente y versátil, y con la combinación del registro, podemos configurar a base de bien el comportamiento de las no controladas. Windows Internals lista 25 claves para cambiar el comportamiento, alguna de ellas tan radicales como hacer que ni siquiera se ejecute WerFault.exe y nuestra aplicación desaparezca de forma silenciosa.

Pero como diría el personaje de los dibujos animados, no se vayan todavía, que hay más. Aparte de poder instalar y controlar ciertas funciones de control de excepciones, podemos configurar el modo con que nuestra aplicación se comunica con el gestor de las mismas. Es decir, mediante ciertas llamadas a funciones de Win32, podemos decirle al sistema WER cómo debe comportarse, qué ficheros adjuntar, etc.

La mayoría de las funciones que voy a citar aquí de pasada, se encuentran dentro de kernel32.dll y para usarlas debemos incluir werapi.h.

  • WerSetFlags()/WerGetFlags(). Cambiamos o leemos si queremos que se incluya el heap (montículo) en el informe, si se suspenderán todos los hilos o sólo el que falló, si se añadirá el informe a la lista de errores, y si se enviará o no a Microsoft.
  • WerAddExcludedApplication()/WerRemoveExcludedApplication(). Añadir o eliminar una aplicación a la lista de aplicaciones excluidas de la generación de informes.
  • WerRegisterFile()/WerUnregisterFile(). Añadir o quitar el fichero indicado al informe.
  • WerRegisterMemoryBlock()/WerUnregisterMemoryBlock(). Añadir/Qutar un volcado de un área de la memoria de nuestro programa.

También podemos crear un informe desde nuestro programa cuando queramos. Los pasos a seguir son:

  1. Llamar a WerReportCreate(), que inicia el informe.
  2. Llamar tantas veces a WerReportSetParameter() como parámetros queramos cambiar.
  3. Llamar tantas veces a WerReportAddDump() como bloques de memoria queramos incluir.
  4. Llamar tantas veces a WerReportAddFile() como ficheros del tipo que sean queramos incluir.
  5. Llamar a WerReportSetUIString() para añadir opciones a la pantalla de WerFault.exe.
  6. Llamar a WerReportSubmit(), que dependiendo de la configuración para el programa y/o genéricas que se tengan, enviará o no, preguntará o no, etc. qué hacer con el informe.
  7. Llamar a WerReportCloseHandle() para terminar con el tema.

Como podéis ver, la cosa es muy potente, y puede ser enormemente útil en determinadas situaciones. Imaginemos que nuestra aplicación esté petando en casa del cliente y no haya modo de ver qué puñetas pasa. La generación de informes a demanda podría ser la solución. Lo que no he visto es cómo recibir e interpretar esos informes.

También podemos usar estas funciones cuando sea nuestra aplicación la que esté controlando la excepción, pero debemos ser cuidadosos ya que ésta podría estar tan estropeada que el uso de todo esto volviera a genera nuevas excepciones que terminaran en el mismo código, que a su vez generara otras que…

Se supone que Windows es lo suficientemente inteligente como para detener la cadena, pero como yo ya he visto más de una vez petar de forma continua a una aplicación en Windows Vista hasta que la he matado desde el administrador de tareas, debemos ser cuidadosos con su uso.

De excepciones, del depurador, de Windows y de Visual Studio

Esto leyendo la quinta edición de Windows Internals, que cubre Windows Vista y Windows Server 2008 y es la última versión disponible del libro. Allá por los años del Windows 95 y del NT 4.0, un poco antes, un poco después, estaba yo muy interesado en las tripillas de los sistemas operativos, y aparte de empacharme con algún que otro mamotreto genérico sobre el tema, le di caña a los equivalentes, como Windows 95, al descubierto, o el Windows NT de Helen Custer, así como otros de similar temática. Vamos, que prácticamente me leí todo lo que pude conseguir sobre las interioridades de esos sistemas operativos.

Pero desde hace un tiempo me veo en la tesitura de que, conversando aquí y allí, me he dado cuenta de que me he quedado bastante obsoleto en el tema, con cosas que no sólo han sido mejoradas, sino que ahora funcionan de forma completamente diferente, así que me he puesto las pilas y le estoy dando un buen repaso al libro…

Bueno, pues como iba diciendo, el capítulo 3 del citado no es que sea nada del otro mundo. Bueno, sí, lo cierto es que como no sepas de programación de hardware y hayas hecho algo, te vas a quedar poco menos que a dos velas con muchas de las cosas que aparecen, ya que tienen que ver con la gestión de interrupciones, tanto de software como de hardware, así como las excepciones y demás intríngulis que todo sistema operativo ha de cumplir.

Personalmente yo he hecho un par de proyectos basados en un microprocesador, aunque generalmente, cuando lo hago, suelo trastear con microcontroladores más o menos potentes, y la verdad es que muchas de las cosas que aparecen ahí explicadas, con la evidente diferencia entre un sistema operativo como es Windows y lo que un mindundi como yo haya podido hacer, me las he encontrado en mis desarrollos. Tener en cuenta las prioridades y los niveles de las interrupciones, programarlos y cambiarlos en tiempo real, instalar y quitar vectores de interrupción, etc.

Por lo menos eso es de lo que trata la parte del capítulo 3 que he leído, que ciertamente me parece demasiado detallista en cosas que, la verdad, a no ser que vayas a meterte de lleno en la creación de drivers primarios para Windows, de poco te sirve. Y si sabes de sistemas operativos, lo único llamativo es que te enteras de que Windows no es mucho más diferente que otros sistemas.

Volviendo al tema (lo que me mola irme por las ramas), sobre la página ciento y pico (je je, me quedan todavía más de mil cien por leer, ¡qué emoción!) se pone a hablar del control de las excepciones del sistema operativo, y cómo son controladas en conjunción con el software de aplicación (ese que tu y yo hacemos) y el propio sistema. Y entonces te dice que si no tienes claro cómo funciona a nivel de programador, que mires los capítulos finales de Windows via C/C++. Y como resulta que tengo ese libro en la lista de pendientes (hace unos años leí y estudié la misma versión pero mucho más antigua, y encima en castellano), pues dejé uno y me puse con el otro, máxime recordando que fueron precisamente esos capítulos los que no leí en la edición anterior.

El sistema de excepciones de Windows es muy potente y versátil, y todos deberíamos saber lo básico sobre él. Pero no, no voy a entrar en detalles sobre el mismo, simplemente os diré que funciona muy parecido a las excepciones de un lenguaje de programación, con la salvedad de que es el sistema operativo el que las lanza y las gestiona si no lo has hecho tu.

En Windows Vista y siguientes existe una aplicación que se llama WerFault.exe y que seguro que habéis visto en ejecución. De hecho, cada vez que os peta un programa, es esa aplicación la que toma el control y saca la barra de progreso moviéndose y te permite unas u otras acciones una vez que ha hecho sus deberes.

Es decir, cuando una aplicación genera una excepción, o el sistema operativo dispara una causada por la aplicación (por ejemplo, una división entre cero o una escritura fuera de rango), se trastea con la pila de tu programa y se busca un controlador de excepción adecuado. Ojo, no estamos hablando de bloques try/catch, sino de bloques __try/__except… aunque a veces el control termina también en un catch, sobre todo si usamos C++ y hemos habilitado la compatibilidad de control de excepciones del sistema operativo (y esto no viene explicado así en el libro, que asume la compatibildad). En .NET no sé qué ocurrirá, pero no creo que haya mucha diferencia.

Si nuestro programa tiene un manejador para esa excepción, se ejecutará, y dependiendo de lo que devuelva, se reintentará la instrucción (de código máquina, así que ojo) que generó la excepción, se continuará como si nada hubiera pasado (y en este caso podríamos liarla todavía más gorda), o se seguirá buscando otro nuevo manejador. Es decir, independientemente de lo que quiera que hagamos en nuestro controlador, podemos decirle al sistema operativo que reintente, que ignore o que busque otro controlador más.

Una vez que se han recorrido todos los controladores adecuados, además de dos más extra que podemos instalar mediante AddVectoredExceptionHandler() y SetUnhandledExceptionFilter(), y siempre que se haya llegado al final de toda la cadena de excepciones (que es algo más larga y compleja de lo que aquí he expuesto) sin haber sido detenida, Windows lanzará el citado programa, y le pasará un manejador al proceso que está fallando, aparte de detener por completo la ejecución de todos sus hilos (en XP sólo se detenía el que fallaba) mediante unas APIs ALPC no documentadas.

Este programa, en base a algunas claves del registro, hará una cosa u otra. Como ha recibido los manejadores en modo heredado, tendrá control sobre el programa que está fallando. Aparte de añadir el evento correspondiente, podrá enviar el informe a Microsoft, o añadirlo a la lista de pendientes. Incluso existe la posibilidad de que ni siquiera aparezca, haciendo todas sus tareas de forma oculta, o incluso que no haga ninguna. Podemos incluso filtrar el comportamiento según qué aplicación haya fallado.

Después de que ha hecho sus tareas internas, si existe algún depurador registrado y si la configuración (o lo que hayamos elegido) lo permite, abrirá el programa vsjitdebugger.exe que mostrará los depuradores registrados. De nuevo este programa recibe los manejadores de forma heredada, y mientras, nuestra aplicación está completamente muerta, esperando. Una vez que hemos elegido un depurador, éste será ejecutado pasando, otra vez, la información necesaria para que sea capaz de buscar y localizar la aplicación que ha fallado.

Es entonces cuando ésta se abrirá dentro del depurador, y si están disponibles los símbolos y demás, podremos ver qué ha fallado y por qué.

Si os fijáis, el tema no es sencillo ni de lejos. En primer lugar, el programa fallante no puede hacer nada porque podría estar en un estado indeterminado, con la memoria o la pila completamente corruptas, así que es el sistema operativo el que debe tomar el control y lanzar una nueva aplicación que se encargue de todo el paripé, que a su vez lanzará un depurador que no es tal, sino un concentrador de depuradores, que al final sí, lanzará el depurador elegido, que tomará bajo su ala a nuestro programa petado.

¿Os dais cuenta lo complejo que es algo que asumimos de forma inconsciente como trivial? Pues no todo termina ahí, sino que es ahora el depurador el que debe meter las garras dentro de la aplicación defectuosa, insertándose en su espacio de direcciones de alguna manera.

Para finalizar, comentaros que esto no es más que un resumen de los capítulos 23, 24 y 25 de Windows via C/C++, quinta edición.

Quis custodiet ipsos custodes?

No, no me he vuelto loco (en todo caso ya lo estoy), ni me ha dado por aprender latín (ya tuve bastante de eso en el instituto), ni me he puesto a estudiar ética o filosofía… Bueno, algo de ética sí. Para los que no lo sepáis, es una frase que aparece en una sátira de Juvenal, un escritor romano y que, traducido al castellano, significa “¿Quién vigilará a los vigilantes?”.

Todo esto viene a cuento por lo siguiente. Sed pacientes porque creo que lo que voy a contar hoy aquí es, como poco, un bombazo, si no algo para que algún juez actúe de oficio. Pero comencemos por el principio.

Todos conocéis la campaña de Tractis sobre el DNIe. Por dos euros, destinados a los gastos de envío, puedes recibir en casa un lector de tarjetas inteligentes destinado a operar con el DNIe. Ni trampa ni cartón: te suscribes y a los pocos días recibes tu lector, aunque en mi caso los pocos días han sido como dos meses, pero sé de gente que lo ha recibido en un par de semanas.

Además, el cacharrito funciona de cojones, o al menos se instala sin necesidad de drivers extras. Lo enchufas y listo, a funcionar. No he probado sus bondades porque no tengo DNIe, de hecho pensé en conseguirlo sólo para poder jugar con él, pero a vista de lo que me ha pasado, intentaré usarlo (el DNIe) lo menos posible…

Si sigues las instrucciones, el siguiente paso es instalar el certificado digital y el software anexo de la Policía, que se debe bajar de la propia web. Lo bajas, lo instalas y listo, a funcionar.

Vale, ahora cambiemos de registro.

¿Quién de los que lee esto no utiliza Visual Studio en alguno de sus colores? Pues eso, para muchos es la herramienta con la que se gana las habas. Y todos sabemos que el Visual Studio es muy pejigueras con algunas cosas, y que sin comerlo ni beberlo puede empezar a hacerte cosas raras y a funcionar, cuando menos, raro. Echemos al mejunje una sensible caída del rendimiento general de tu ordenador.

Je, je, algunos ya sabéis por dónde voy. Pero dejad que me regodee un poco en la situación. El ordenador va sensiblemente más lento, sufre enganches en los accesos a disco, y encima el Visual Studio te muestra errores como “All pipe instances are busy”, errores que no has visto en tu miserable vida…

Así que uno se coge el Process Explorer, que si no existiera habría que inventarlo, y le da por inspeccionar el Visual Studio. A la primera, directa en los dientes. Os dejo una captura:

image

Fijaros en la línea seleccionada, y en el delta de consumo de ciclos…

¿Qué cojones hace esa DLL ahí insertada?

Y encima consumiendo un 60% del tiempo de proceso de Visual Studio. Luego uno se pone a mirar otros procesos, como el antivirus, el Messenger… y en todos está. Bueno, todos no, sólo algunos. Sólo aquellos interesantes. Fijaros donde pone State: WrLpcReply. Es una función del Kernel encargada de comunicaciones inter-procesos… y si matamos ese proceso dentro de esa DLL no ocurre nada, Visual Studio sigue funcionando igual –de mal-, pero igual…

La verdad es que no tuve ganas de seguir. Desinstalé el certificado, pero como no me fío un pelo, hice una instalación limpia: apagar del botón de atrás de la fuente. Lanzar una instalación formateando discos, apagar a medio instalar. Volver a lanzar otra, dejando los discos sin particiones. Apagar. Y entonces instalar de nuevo. Así evitamos que se nos queden regalitos en el ordenador.

No tengo nada que ocultar, pero tampoco nada que mostrar.

Las preguntas abiertas para que alguien las responda son:

  • ¿Qué necesidad tiene el DNIe de insertarse en el antivirus, el Messenger o el Visual Studio?
  • ¿Es un error de programación y esa DLL debía insertarse en otro lado y los habituales programadores chapuceros han metido la pata?
  • ¿Por qué ha de consumir esa DLL tanto tiempo de proceso? Un 60% más del normal por aplicación interceptada. Inaceptable.
  • ¿Nos quiere espiar el Estado?

Hemos leído: Adam Nathan: Windows Presentatio Foundation Unleashed

image La idea de adelantar casi un año la lectura de este libro respecto a mi plan ha sido la de hacer un programa utilizando WPF, pero al final ha sido peor el remedio que la enfermedad. O dicho de otro modo: pese a ser enormemente potente, pese a soportar temas, pese a contener ideas relativamente nuevas y bien implementadas... no lo voy a hacer con WPF. Simplemente es que no me gusta cómo ha sido expresada dicha potencia. Me han dicho que quizás si leyera el libro equivalente de Petzold lo vería de otro modo, pero de Petzold ya tuve bastante con el de Programación en Windows con C#, que contenía más errores que aciertos.

Sí, ya sé que los libros de Petzold sobre Win32 y C son una referencia de base, de hecho tengo tres ediciones de las cinco que hay, pero por eso mismo: la referencia son estos, no los nuevos. No creo que nadie pueda negarme que el de C# no tenga errores.

Sin embargo, el de Nathan es otra cosa, y pese a tener ya más de tres años, sigue siendo actual y no desmerece para nada. Me dicen que pasa lo mismo con el de Petzold, que quizás compre y lea.

Dividido en cinco partes, ninguna tiene desperdicio. En la primera se nos presenta XAML y lo que significa para WPF, así como los conceptos que hay detrás de todo esto. Lo más destacable es que, a partir de un fichero de marcas como es XAML (que no es más que una variación de XML), el sistema es capaz de construir el tipo de interfaces que es capaz de hacer. Aunque suene un poco como a magia, no lo es, ya que la construcción del fichero y los tags son complejos.

Otras cosas interesantes son las propiedades propagadas, en las que si tu modificas una de ellas, todos los hijos las modificarán de acuerdo al cambio, con lo que variar aspectos de la UI puede llegar a ser trivial en muchos casos. En los eventos ocurre parecido: si uno hace clic en un componente que no tiene dicho evento asignado, éste irá subiendo por el árbol hasta llegar a alguien que lo maneje. Si bien esto último puede sonar espectacular en .NET, en MFC y otros frameworks es el comportamiento habitual.

La segunda parte nos presenta los controles de que disponemos en WPF. Pese a toda su enorme potencia a veces resultan escasos, porque básicamente son una serie de contenedores. A los programadores de GTK o de QT les deben sonar los componentes de layouts y fluentes en el sentido de que reordenan los hijos de forma automática. Aparte de los botones, los cuadros de lista, los combos y los labels, pocos más hay. También nos cuenta cómo juntar todo esto en un programa, y crea una especie de clon de las ventanas de Visual Studio, y digo "especie de" porque realmente el soporte de docking está como ausente, pero aceptaremos pulpo pese a no haber hablado de arrastrar y soltar (que me temo WPF no soporta directamente).

En la tercera nos explica cómo trabajar con recursos, enlace a datos y el soporte para estilos, plantillas (de estilos), pieles (skins) y temas. Aquí me he llevado otro pequeño desengaño, ya que pese a la potencia de todo esto, resulta sensiblemente difícil añadir o cambiar el aspecto de una aplicación. Para hacerlo es necesario al menos saber XAML, y presenta un potencial y enorme agujero de seguridad ya que dicho XAML puede contener código arbitrario...

La penúltima parte cubre la multimedia: cómo hacer gráficos 2D y 3D, animaciones, vídeo y audio. Pese al soporte ampliado, la verdad es que no hay mucha mejora respecto a otras tecnologías.

La quinta y última parte sí que es interesante, por lo menos para mi: Interoperabilidad con Win32, C++/CLI, COM, ActiveX y Windows Forms. De todos modos, nada nuevo bajo el sol, y para ver interoperabilidad entre C++/CLI y WPF y/o Windows Forms de verdad, el libro de Sivakumar. También vemos cómo crear controles personalizados y de usuario.

No os llevéis a engaño por mis comentarios. El libro es muy bueno, lo explica todo perfectamente, pero la tecnología me ha desilusionado un poco. No sé, esperaba más. No más complejidad (que ya tiene bastante), sino más facilidad y menos picar código (aunque sea XAML)... Sí, ya sé que tenemos el Expression Blend, pero creo que aprender a manejar esa bestia parda tampoco es tarea de un rato...

Publicado por Rafael Ontivero | 6 comment(s)
Archivado en: ,

Visual Studio 2010 Beta 2 y C++

Por fin anoche tuve un rato -y ganas- para instalarme la Beta 2 de Visual Studio 2010 en mi ordenador secundario, y por fin podré contar lo que llevo callándome bastante tiempo por el NDA de ser MVP... porque las novedades de Visual C++ 2010 son muchas y potentes, y seguro que a más de uno le voy a dar una alegría si no lo sabe ya por otras fuentes.

Pese a que hay cosas que ya son públicas desde hace mucho tiempo, voy a contarlas todas poco a poco aquí, y lo más seguro es que veáis más entradas mías sobre puntos concretos. No van en ningún orden de importancia, ni siquiera personal (bueno, sí, las que más me molan van al final), y antes voy a contar mi experiencia sobre la instalación.

Instalando. Primero lo instalé en mi máquina secundaria, un AMD X2 con 2GB de RAM y Windows 7 x86 Ultimate en inglés, pero antes tuve que desinstalar la versión anterior, de la que no diré más salvo que pude limpiarla sin problemas.

OJO CON DESINSTALAR LOS NUEVOS RUNTIMES DEL VISUAL C++ 2008 que el producto instala. Estos deben dejarse o correremos el riesgo de que luego nos peten la mitad de los programas que tengamos instalados. Considero que es una mala cosa eso de instalar runtimes no BETA dentro de una instalación BETA (o peor aún ni siquiera ALPHA), pero bueno, el Scatérgoris es de ellos.

Estuve probando varios programas y funcionaban bien, pese a la lentitud del IDE, que a veces se queda como autista o simplemente te ignora, frente a otras en las que la respuesta está casi adelantada al clic, así que me decidí a instalarlo en mi máquina principal, ya sabéis, un Q4 con 8GB de RAM y una espuerta de discos duros.

Si tenéis el SDK de Silverlight instalado, la instalación de la BETA 2 petará casi al final. Aunque no es ningún problema, ya que luego podéis desinstalar el SDK e instalarlo a mano a partir de la carpeta WCU\Silverlight del DVD de instalación de esta misma BETA. Otro efecto adverso no tiene.

MSBUILD. Hasta la versión 2008, el sistema de construcción de proyectos era el llamado VCBUILD, que como su nombre indica es una especie de sistema de makefiles con esteroides. Pero el sistema es bastante obsoleto, y sólo era usado por Microsoft para Visual C++ ya que desde hace tiempo para los demás productos se viene utilizando MSBuild, que viene a ser lo mismo pero más moderno, basado en XML.

A mi personalmente es algo que me da igual porque no lo he usado más que integrado en el IDE y desde el IDE, así que tampoco sé muy bien qué ventajas tiene uno sobre el otro, aunque quizás la mejor de todas es que ahora el sistema de construcción es el mismo para todos los lenguajes.

No obstante, parece ser que las posibilidades de extensibilidad del más moderno son significativamente mejores que las del anterior, y supongo que serán útiles para aquellos que realicen compilaciones de gran tamaño.

Eso quiere decir que es necesario convertir los proyectos "vcproj" a "vcxproj", cosa que en esta BETA 2 se hace sin mayor problema, pero que en versiones anteriores fallaba estrepitósamente.

MFC. Aquí las novedades son relativamente pocas respecto al Feature Pack del VS2008SP1, pero alguna hay, como el restart manager, que es una extensión que, si tu aplicación peta, es capaz de recuperar tus documentos sin pérdida, o al menos eso es lo que proclaman. Parece ser que es la misma técnica y medios que se usan en, por ejemplo, Office, y lo cierto es que alguna que otra vez sí que me ha salvado el culo.

Luego tenemos los Task Dialogs, que son estas ventanas nuevas que vienen con Vista y 7 que permiten comunicación rica con el usuario, como la ventana de copiar archivos y similares.

image

Arriba podéis ver un ejemplo sacado de la documentación. La verdad es que no está nada mal, y se puede construir una ventana así con muy pocas líneas en C++.

SafeInt es una nueva biblioteca que permite manejar enteros con protección de desbordamiento en operaciones aritméticas, cosa que ni la biblioteca de C ni la de C++ tienen. Está basada en plantillas, de modo que al principio su uso puede resultar un tanto confuso. Un ejemplo sacado de la documentación:

SafeInt<uint>((uint)~0) > -1

devolverá cierto, mientras que si se hace con enteros normales, el resultado será falso. Para los legos, lo que estamos haciendo es una forma rápida de poner a unos todos los bits de un entero sin signo. ~0 es cero invertido, es decir, todo unos. Como lo moldeamos a un entero sin signo, eso significa que no importa el tamaño del tipo, estará lleno de unos de cabo a rabo. Ese valor, para un entero sin signo, es tropecientos mil trillones de trillones o más. Si se hace una comparación como

((uint)~0) > -1

el compilador comparará la pauta binaria. En ambos lados de la comparación, la representación binaria es la misma, todo unos, por lo que el compilador devolverá falso ya que para él ambos valores son iguales. Sin embargo, cuando usamos la versión segura, uno se asegura de que, efectivamente, tropecientos mil es mayor que menos uno.

C++ Estándar. También hay novedades en este aspecto, a ver si de una vez el comité se pone de acuerdo, que les cuesta más que a los políticos hablar de urbanismo. Lo que iba a ser el C++09 es muy posible que sea C++10 o superior. En fin.

La palabra reservada auto pierde su significado anterior (¿pone todavía alguien auto en las variables locales?) y pasa a indicar que la variable así cualificada es una cuyo tipo viene definido por el resultado de su valor-r. Para que me entendáis,

auto i=3;

significa que i es un int.

Las funciones lambda son el producto estrella de esta nueva futura revisión. Son un paso más allá de los punteros a función y los functors, ya que son funciones sin nombre que se pueden guardar en una variable... esto... de tipo auto (y esta es la razón que explica el cambio de significado de dicha palabra).

He dicho auto porque es la mejor forma de declarar un lambda y asignarlo a algo, y si no a ver quién es bonico que me dice el tipo de un lambda que toma tres enteros y devuelve un número en coma flotante sin llenar la declaración de paréntesis, asteriscos y demás menesteres.

Hay más novedades, como el operador decltype que devuelve el tipo de una expresión indicada. También están los declaradores de referencias a valores-r, que como su nombre indica declaran una referencia a un valor-r. Con static_assert tenemos la posibilidad de indicar aserciones en tiempo de compilación. Aunque creo que no pertenece al estándar, se crea la palabra reservada nullptr (y __nullptr) para asignar un puntero a nulo sin importar si estamos en C++ o en C++/CLI.

Si os fijáis, casi todas estas características están para dar un soporte eficiente a los lambdas. No voy a explicar aquí nada más de ellos porque pienso hablar largo y tendido sobre los mismo aquí... o en mi futuro libro si saco ganas para terminarlo.

Otras. Hay más novedades, como la eliminación de los caracteres trigráficos por defecto (se pueden activar), o una nueva opción de optimización de perfilado guiado. Para el que no lo sepa, un trigráfico es una secuencia de tres caracteres que se usa para especificar uno cuando el editor no lo tiene. De esta forma era como, hace muchos años, se podían poner ciertos caracteres inexistentes en los teclados, como ??) para indicar la llave de cierre ]. El estándar en ellos creo que especifica 10 diferentes.

Editor gráfico de Ribbons. Como lo lees. Visual C++ 2010 trata las Ribbons como un recurso más y proporciona un editor gráfico muy similar al de los cuadros de diálogo (que por cierto, también traen nuevos componentes largamente deseados), pudiendo escribir eventos y relacionar la Ribbon con partes del código. Ésta se almacena en un fichero XML embebido en el ejecutable, lo que seguro va a dar para algunos trucos que intentaré.

EL BOMBAZO: Para terminar, el bombazo. Cuando me enteré fue un bombazo, y he estado muchos meses comiéndome las uñas para que no se me escapara, y quizás haya que anunciarlo con letras bien gordas y a otro color:

¡¡VUELVE EL CLASS WIZARD!!

Sí señor, el magnífico, el inigualable, el incopiable, el ínclito Class Wizard. Así que ya no tenéis que seguir usando la morralla esa del Visual C++ 6.0 de los años de Maricastaña con un compilador todavía más obsoleto que uno que me sé y me callo. De todos modos, partes del mismo siempre han estado presentes, si bien algo camufladas.

Y para muestra, un botón:

image

Publicado por Rafael Ontivero | 5 comment(s)
Archivado en: ,,

Concurrencia y más concurrencia

Quienes hayan seguido mi podcast sobre Hilos en .NET se habrán dado cuenta de que la concurrencia bien entendida no es trivial ni de lejos, y eso que yo sólo cubrí los aspectos más básicos y por separado, vistos uno a uno. Quien no lo haya hecho y sienta curiosidad por el tema, puede acceder aquí, registrarse, y bajarse el vídeo y la solución de ejemplo (hay que ir hacia abajo hasta encontrar mi presentación).

Herb Sutter es uno de los mayores gurús de C++, de hecho fue, en conjunción con otros de su mismo nivel, el que desarrolló el C++/CLI (como prueba patente de ello, la traducción que Octavio Hernández y yo hicimos de su Rationale).

Pues bien, el amigo Herb mantiene en lo que antes era la revista Doctor Dobb's Developer Journal y que ahora es un website dedicado al desarrollo, una columna llamada Effective Concurrency en la que habla, cómo no, de la concurrencia... desde el punto de vista de C++.

Ya os lo dije, hablar de todo esto y no citar ni C++ ni Win32 es como hablar de lana sin nombrar a las ovejas, así que es inevitable que la mayoría de elementos en relación a este tema tengan un trasfondo relacionado con un sistema operativo concreto y con C ó C++.

No obstante, siempre se pueden hacer extensivas a nuestro lenguaje favorito, en este caso C# y/o .NET, sobre todo cuando el autor expresa sus ideas con C++ pero explicadas al más alto nivel posible, como es el caso que nos ocupa.

El artículo sobre concurrencia de Sutter de este mes está destinado a cómo evitar la misma ocultándola dentro de métodos síncronos. Básicamente nos da ideas, en inglés, eso sí, sobre cómo adaptar código lineal existente a código que aproveche los nuevos equipos multinúcleo:

· Avoid Exposing Concurrency

· Más artículos del autor sobre concurrencia.

Parafraseando a David Salgado, happy reading.

Hemos leído: Vandevoorde & Josuttis: C++ Templates. The complete guide

El título de guía completa hace justo honor a su nombre, ya que no creo que haya vida en relación a las plantillas más allá de este libro, y si la hay no es más que combinación de lo visto aquí (ejem, obviemos a BOOST).

Está dividido en cuatro partes bastante independientes entre sí, y casi dedicadas a diferentes públicos. La lectura resulta a veces un poco engorrosa, con expresiones como La razón de la sinrazón que a mi razón se hace, de tal manera mi razón enflaquece que con razón me quejo… Esto... digo: The name of a template parameter of a template template parameter can be used only in the declaration of other parameters of that template template parameter p.103). Ahí es nada, aunque si uno lo piensa bien, está diciendo que el nombre de los parámetros que sean a su vez parámetros de una plantilla anidada sólo se pueden usar en la declaración de la misma y no fuera de ella. No obstante eso no evita que de repente tengas que ir a cambiarte los fusibles porque se te han fundido, o notes cómo el cerebro te sale derretido por la nariz...

Ciertamente no han sido pocas las veces que puesto la guía de lectura al principio del capítulo y/o sección y me he ido a dar una vuelta o a ver la tele. De todos modos esta complejidad es inherente en un texto de este tipo, obviamente no dedicado a principiantes en el lenguaje C++.

Primera parte. Hace un recorrido completo por las plantillas, y está dedicado al programador que las use ya construidas por terceros (como en la STL) o que vaya a hacerlas pero de forma casual. El contenido es completo, es decir, en esta primera parte ya está el libro entero, aunque con pocos aspectos prácticos y de implementación.

Segunda parte. Vuelve a recorrer la primera parte pero desde el punto de vista de un implementador de bibliotecas, asumiendo el contenido básico y profundizando en los detalles más técnicos y en los tiquismiquis de las plantillas, que al parecer no son pocos. Personalmente considero que esta parte es demasiado técnica y demasiado enfocada al mínimo detalle y de poca utilidad para alguien que simplemente quiera saber más. Esta parte termina con un capítulo dedicado a la futura dirección que podrían tomar las plantillas, aunque parece ser que, más de siete años después de escribir el libro, todavía no hay nada concreto.

Tercera parte. De nuevo esta parte es para el usuario accidental (es un decir), por lo menos la primera mitad de ella, ya que tiene un enfoque eminentemente práctico, mostrándonos para qué pueden servirnos las plantillas mejorando la calidad y eficiencia de nuestro código. A destacar cómo sustituir el polimorfismo dinámico por estático con plantillas así como nuevas versiones del patrón Bridge con un enlace más estático. También clases Trait y Policies (que no sé cómo traducir al castellano, pero que básicamente son clases intermedias que hacen de adaptadores genéricos para evitar la proliferación de código), y en general todo lo relacionado con programación genérica.

Luego se centra en la herencia con plantillas y pasa a temas más esotéricos, como la metaprogramación y el uso de las plantillas para realizar cálculos y optimizaciones en tiempo de compilación, técnica que usan algunas bibliotecas matemáticas para mejorar el rendimiento. Como guinda final, explica un código fuente que genera números primos en base a los errores creados por el compilador al parsear dicho fuente, amén de otros ejemplos como el cálculo de potencias o raíces.

Cuarta parte. Aquí volvemos al rol de creador de bibliotecas, de hecho explica y fundamenta clases plantilla para disponer de una especie de reflexión, punteros automáticos o inteligentes, n-tuplas y finalmente functors (que básicamente son clases que representan punteros a función -algo así a los delegados de C#, aunque más bien sería la base en la que bebieron los citados delegados).

Publicado por Rafael Ontivero | con no comments
Más artículos Página siguiente >