Capítulo 35 Cadenas de caracteres

Windows trata las cadenas de caracteres de un modo algo distinto a como lo hacen las funciones estándar C. Esto se debe a que Windows maneja varios conjuntos de caracteres: ANSI, que son los que ya conocemos, como caracteres de ocho bits y Unicode, que son de dos bytes.

También puede manejar, diferentes formas de comparar y ordenar cadenas, diferentes configuraciones de idioma, que afectan a la forma de representar mayúsculas y minúsculas, o de comparar caracteres, etc. Por ejemplo, en español, la letra 'ñ' es mayor que la 'n' y menor que la 'o'. En inglés ni siquiera existe esa letra. Otro ejemplo, si intentamos obtener la mayúscula de la letra 'ñ' usando funciones estándar, el resultado no será la 'Ñ'.

Recursos de cadenas

Al igual que podemos crear recursos para mapas de bits, menús, iconos, etc, también podemos crearlos para almacenar cadenas y leerlas desde la aplicación. Esto tiene varias ventajas y aplicaciones.

Los recursos de una aplicación pueden ser modificados por un editor adecuado sin modificar la parte ejecutable de una aplicación. Esto permite traducir una aplicación a distintos idiomas sin tener que compilar la aplicación ni tener que compartir el código fuente.

Es más, podemos crear nuestras aplicaciones para que sean multilenguaje, de modo que usen las cadenas adecuadas según la configuración de la aplicación.

Fichero de recursos

Lo primero que debemos crear es una tabla de cadenas (stringtable) dentro del fichero de recursos, esto se hace mediante la sentencia STRINGTABLE:

STRINGTABLE
BEGIN
    ID_TITULO,    "Título de la aplicación"
    ID_SALUDO,    "Hola, estoy preparado para empezar."
    ID_DESPEDIDA, "Gracias por usar esta aplicación."
END

Como se ve, una tabla de cadenas empieza con la sentencia STRINGTABLE y a continuación, entre un bloque BEGIN-END una lista de identificadores y cadenas entre comillas, separadas con una coma. En este caso, como suele ser nuestra costumbre, los identificadores son etiquetas definidas en nuestro fichero de cabecera de identificadores, aunque podría tratarse de números enteros de 16 bits.

El objetivo es hacer nuestra aplicación tan independiente del idioma como sea posible. Esto significa que en la aplicación no deberían aparecer cadenas literales, sino que deben usar cadenas de recurso. De este modo, sólo con traducir las cadenas del fichero de recursos, todos los literales usados en la aplicación cambiarán de idioma. Esto evita tener que repasar todos los ficheros fuente buscando literales para sustituirlos, y tener que compilar la aplicación de nuevo.

Cargar cadenas desde recursos

Para obtener una cadena desde un recurso se usa la función LoadString. Esta función necesita cuatro parámetros. El primero es un manipulador de instancia, generalmente a la misma instancia de nuestra aplicación, aunque como veremos en capítulos más avanzados, también podemos obtener cadenas desde otros módulos, o desde DLLs. El segundo parámetro es el identificador de la cadena, el tercero el búfer donde se recibe la cadena leída, y el cuarto el tamaño de dicho búfer.

Por ejemplo:

    static HINSTANCE hInstance;
    char mensaje[64];
    char titulo[64];
...
        case WM_CREATE:
           hInstance = ((LPCREATESTRUCT)lParam)->hInstance;
           LoadString(hInstance, ID_TITULO, titulo, 64);
           LoadString(hInstance, ID_SALUDO, mensaje, 64);
           MessageBox(hwnd, mensaje, titulo, MB_OK); 
           break;

Funciones para cadenas

Algunas funciones estándar C tienen una versión repetida en el API de Windows. En el caso de las siguientes funciones, están mejoradas para manipular cadenas Unicode:

Windows C estándar
lstrcat strcat
lstrcmp strcmp
lstrcmpi strcmpi
lstrcpy strcpy
lstrlen strlen

Por ejemplo, la versión Windows de strlen, lstrlen siempre calcula la longitud de una cadena en caracteres, independientemente de si los caracteres son de uno o dos bytes.

           // Comparar cadenas:
           if(lstrcmp("Niño", "Ñape") < 0)
              lstrcpy(mensaje, "Niño es menor que Ñape");
           else
              lstrcpy(mensaje, "Niño es mayor que Ñape");
           TextOut(hdc, 10, 280, mensaje, strlen(mensaje));
           if(lstrcmp("Ola", "Ñape") < 0)
              lstrcpy(mensaje, "Ola es menor que Ñape");
           else
              lstrcpy(mensaje, "Ola es mayor que Ñape");
           TextOut(hdc, 10, 300, mensaje, strlen(mensaje));

La salida de este fragmento de programa es la que cabría esperar:

 Niño es menor que Ñape
 Ola es mayor que Ñape

Es decir, la función considera que la letra 'Ñ' está entre la 'N' y la 'O', como realmente ocurre en español. Siempre y cuando nuestro Windows esté instalado en español, o el usuario haya seleccionado ese idioma, claro.

Otros casos donde es necesario crear nuevas funciones es cuando necesitamos operar con caracteres dentro de cadenas. Como en Windows los caracteres pueden ser de uno o dos bytes, moverse a lo largo de una cadena puede no ser siempre tan directo como usando cadenas C estándar. Disponemos de dos funciones para movernos dentro de cadenas: CharNext para avanzar al siguiente carácter de una cadena, y CharPrev para retroceder al anterior.

Algo parecido pasa con la conversión de mayúsculas a minúsculas, y viceversa. En este caso disponemos de cuatro funciones:

Función Descripción
CharLower Convertir un carácter o una cadena a minúsculas.
CharLowerBuff Convertir un carácter o una cadena a minúsculas.
CharUpper Convertir un carácter o una cadena a mayúsculas.
CharUpperBuff Convertir un carácter o una cadena a mayúsculas.
    static char alfabeto[] = "abcdefghijklmnñopqrstuvwxyz áéíóúëü ç";
...
           // Mayúsculas y minúsculas
           CharLower(alfabeto);
           TextOut(hdc, 10, 220, alfabeto, strlen(alfabeto));
           CharUpper(alfabeto);
           TextOut(hdc, 10, 240, alfabeto, strlen(alfabeto));

También en este caso el resultado es el esperado, las letras se convierten a mayúscula y minúscula correctamente, aunque se trate de caracteres con acentos, diéresis o tildes:

 abcdefghijklmnñopqrstuvwxyz áéíóúëü ç
 ABCDEFGHIJKLMNÑOPQRSTUVWXYZ ÁÉÍÓÚËÜ Ç

Otro grupo de funciones estándar que se ven afectadas por el modo de trabajar en Windows son las del grupo de "ctype". En este caso tenemos otras cuatro funciones:

Función Descripción
IsCharAlpha Verificar si un carácter es alfabético.
IsCharAlphaNumeric Verificar si un carácter es alfanumérico.
IsCharLower Verifica si un carácter está en minúscula.
IsCharUpper Verifica si un carácter está en mayúscula.

Por último, tenemos un par de funciones que sustituyen a las funciones estándar sprintf y vsprintf. Se trata de wsprintf y wvsprintf.

           for(i = 0; i < 10; i++) {
              wsprintf(mensaje, "Cadena formateada %d: valor %c",
                 i+1, alfabeto[i]);
              TextOut(hdc, 10, i*20, mensaje, strlen(mensaje));
           }

En Windows debemos usar las variantes del API, ya que están preparadas para trabajar con cadenas y caracteres Unicode, algo que las funciones estándar no pueden hacer.

Hay que tener en cuenta que estas funciones no aceptan las mismas cadenas de formato que sprintf o vsprintf, por ejemplo, no sirven para valores en punto flotante o punteros. Sin embargo, tienen más opciones para cadenas Unicode/ANSI.

Ejemplo 40


  Nombre Fichero Fecha Tamaño Contador Descarga
D Ejemplo 40 win040.zip 2004-07-16 3318 bytes 269