El caso del fondo de escritorio inmutable

Con este artículo inicio una temática que apenas había tratado hasta ahora en el blog y es la de programación para Windows y los fallos más comunes e interesantes que he ido viendo en Internet. Hoy vamos a ver un error no poco frecuente entre la gente que empieza a adentrarse en la programación para Windows.

El caso de hoy es el de un usuario que quería cambiar el fondo de escritorio de su máquina de manera inmediata mediante un programa que hiciera uso de la API de programación de Windows. La función que nos proporciona la API para cambiar el fondo de escritorio es SystemParametersInfo, con el primer parámetro establecido a la constante SPI_SETDESKWALLPAPER. El programa que había escrito el usuario era, de manera simplificada, el siguiente:

int _tmain(int argc, _TCHAR* argv[])
{
    char wallString[200];

    strcpy(wallString, "C:\\Users\\Public\\Pictures\\Sample Pictures\\Desert.jpg");

    SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, (void*)wallString, SPIF_SENDWININICHANGE);  

    return 0;
}

Si examinamos la documentación de la función SystemParametersInfo, el programa tiene buen aspecto: El primer parámetro, SPI_SETDESKWALLPAPER, le indica al sistema que lo que se va a cambiar es el fondo de escritorio; el segundo parámetro está establecido en 0, ya que no es aplicable cuando el primer parámetro está establecido en SPI_SETDESKWALLPAPER; el tercer parámetro es la cadena correspondiente al fondo de escritorio; y el último parámetro le indica al Windows que debe notificar del cambio de fondo a todas las ventanas de primer nivel del sistema, de forma que el cambio se vea inmediatamente.

Sorprendentemente para el usuario, el código no funciona, aunque en otros sistemas antiguos lo había hecho perfectamente. ¿Cuál sería el problema?

Vamos a fijarnos bien en la variable local “wallString”. Está declarada como un vector de tipo “char”. En C/C++, el tipo de datos “char” representa por defecto a un entero con signo entre –128 y 127 y, por lo tanto, ocupa un byte. La documentación de la función SystemParametersInfo dice que el tercer parámetro debe recibir una variable de tipo PVOID, que es un “alias” de un puntero a una dirección de memoria que contenga cualquier tipo de datos, es decir, lo que en C++ se expresa como void*.

Aunque SystemParametersInfo venga documentada como función en MSDN, en realidad es una macro de C que “delega” el trabajo a la verdadera función, dependiendo de si el programa está compilado como Unicode (SystemParametersInfoW) o no (SystemParametersInfoA). Así que hoy en día es más que probable que internamente el programa esté llamando a SystemParametersInfoW. La clave está en la interpretación de la cadena de caracteres que se pasa como tercer parámetro. El usuario estaba pasando una cadena ANSI (1 byte por caracter), mientras que el sistema la estaba interpretando como Unicode (2 bytes por caracter). Este es un esquema gráfico de lo que ocurre por lo bajo:

Unicode

Los números hexadecimales representan cada carácter (un dígito hexadecimal es medio byte). Al interpretar la cadena con caracteres anchos (de 2 bytes) como unidad básica, se traduce en una ristra de caracteres “extraños” y por consiguiente el fondo de escritorio no se cambia correctamente.

Lo ideal hoy en día es usar Unicode en todo lugar. Si se usa el formato de carácter de Windows TCHAR, éste se convertirá en un carácter normal (char) en sistemas antiguos que no soportan Unicode y en un carácter ancho (wchar) en sistemas Unicode. La “T” que precede a “CHAR en “TCHAR” indica que se trata de un tipo de datos general que se convertirá apropiadamente en la versión ANSI o Unicode dependiendo de los parámetros de compilación. Si necesitamos un puntero en lugar de un único carácter, podemos usar el tipo LPTSTR (Long Pointer to String). Este sería el programa resultante:

int _tmain(int argc, _TCHAR* argv[])
{
    LPTSTR lpString = TEXT("C:\\Users\\Public\\Pictures\\Sample Pictures\\Desert.jpg");

    SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, lpString, SPIF_SENDWININICHANGE);  

    return 0;
}

La moraleja del artículo es que siempre hay que tener en cuenta el tipo de cadena que empleemos. La API de Windows ofrece muchos nuevos tipos de datos de caracteres que conviene interiorizar para evitar este tipo de fallos y ser más productivos mientras se aprende programación usando la API de Windows. Otro punto destacado de la programación con cadenas es la posibilidad de incluir vulnerabilidades de seguridad con cierta facilidad. Trataremos este punto en un próximo artículo.

En el futuro iré tratando otros problemas más complejos que pueden surgir al programar usando la API de Windows, especialmente casos en los que la documentación oficial no ofrece información, o bien la que ofrece es ambigua, o bien inexacta.

Leave a Reply

Your email address will not be published. Required fields are marked *