Ya hace unos cuantos meses que estoy escribiendo sobre WinInet, y la verdad es que he aprendido bastante desde aquel primer artículo. Una de las cosas que no expliqué en el artículo introductorio era cómo obtener el mensaje de error a partir de su código. Es decir: siempre hemos dicho la mayoría de las funciones retornan FALSE cuando ocurre un error, y para obtener el código de error hay que llamar a GetLastError. Cada código de error tiene un mensaje descriptivo asociado, aunque hasta ahora no sabía cómo hacer para obtenerlo. No se puede saber todo desde el principio ¿no? Mis averiguaciones se habían atascado en el punto en que sabía que los mensajes estaban almacenados como recursos de cadena dentro del archivo "wininet.dll". Lo que no sabía era cómo localizar el recurso adecuado a partir de un código de error concreto. Después de pasar muchas horas consultando el MSDN, pude encontrar la clave del asunto. La cuestión está en una función que no pertenece al API WinInet, sino al API genérico de Win32: FormatMessage. Esta función trabaja de distintas formas, obteniendo el mensaje (teniendo en cuenta el idioma instalado) de un error genérico de Win32, formateando una cadena con máscaras al estilo "printf", u obteniendo un mensaje a partir de una librería independiente. Ahí está el asunto. Podemos obtener los mensajes que están almacenados como recursos dentro de la librería "wininet.dll", localizándolos a partir del código de error.
Lo primero que necesitamos es un descriptor de la librería, bien abriéndola con LoadLibrary, o intentando obtener un descriptor ya creado por el proceso, a través de la función GetModuleHandle. También podemos hacer una solución mixta, intentando obtener un descriptor ya creado, y si no existe, crearlo nosotros.
El siguiente paso es llamar a la función FormatMessage. La sintaxis es la siguiente, aunque sólo voy a explicarla superficialmente porque esta función tiene muchas formas de trabajar:
DWORD FormatMessage(
DWORD dwOpciones,
LPCVOID lpOrigen,
DWORD dwCódigoError,
DWORD dwIdioma,
LPTSTR lpBuffer,
DWORD dwLongitudBuffer,
va_list *argumentos
);
- dwOpciones: es una máscara de bits que indican cómo debe comportarse la función. Se puede utilizar una combinación de los siguientes valores:
- FORMAT_MESSAGE_ALLOCATE_BUFFER: la función reservará memoria en el montón por defecto del proceso (¿alguien se acuerda del artículo sobre “Los montones∞”?) para almacenar el mensaje. El que llame a FormatMessage con este parámetro es el responsable de liberar el buffer resultante con la función HeapFree(GetProcessHeap(), buffer)
- FORMAT_MESSAGE_FROM_STRING: hace que la función actúe como un "fprintf", es decir, formateando una cadena que contiene máscaras.
- FORMAT_MESSAGE_FROM_HMODULE: el mensaje de obtiene de una librería independiente, buscando en los recursos de cadena. Este es el valor que nos interesa para buscar los mensajes en "wininet.dll"
- FORMAT_MESSAGE_FROM_SYSTEM: se busca en los mensajes de sistema el código indicado. Este parámetro nos sirve para conseguir la mayoría de los mensajes de funciones básicas de Win32, como CreateFile, CloseHandle, etc.
- lpOrigen: se trata de un puntero genérico en el que podemos pasar distintos valores. El tipo de dato que debemos pasar depende de las constantes que hayamos pasado en el parámetro dwOpciones, pudiendo ser:
- FORMAT_MESSAGE_FROM_STRING: un puntero a una cadena que será formateada.
- FORMAT_MESSAGE_FROM_HMODULE: un descriptor de librería, obtenido con LoadLibrary o GetModuleHandle.
- cualquier otro: este parámetro debe ser NULL.
- dwCódigoError: el código numérico del error. Normalmente lo obtenemos con GetLastError.
- dwIdioma: Se trata de un valor numérico que identifica el idioma en que queremos obtener el mensaje. Este valor lo podemos obtener con las funciones GetSystemDefaultLangID ó GetUserDefaultLangID, las constantes LANG_SYSTEM_DEFAULT o LANG_USER_DEFAULT. Podemos pasar el valor 0 para obtener el mensaje en el idioma por defecto del usuario o sistema.
- lpBuffer: en este parámetro se pasa un puntero a una cadena de caracteres en la que se copiará el mensaje obtenido. Si hemos pasado el valor FORMAT_MESSAGE_ALLOCATE_BUFFER en el parámetro dwOpciones, en realidad lo que se debe pasar es la dirección de un puntero a cadena. En esa dirección se copiará a su vez la posición de memoria donde se ha creado el nuevo buffer.
- dwLongitudBuffer: este parámetro puede contener el tamaño del buffer pasado en "lpBuffer", o el tamaño mínimo a reservar por la función, esto último sólo si hemos pasado FORMAT_MESSAGE_ALLOCATE_BUFFER en el parámetro dwOpciones.
- argumentos: se trata de una lista de argumentos variables que se utilizarán par sustituir las máscaras de la cadena, si hemos utilizado la opción FORMAT_MESSAGE_FROM_STRING. En caso de no necesitar este parámetro, podemos pasar el valor NULL.
La función retorna el número de caracteres que se ha copiado en el buffer de salida, o 0 en caso de error. Se puede llamar a GetLastError() para averiguar la causa del error.
De todas formas, en el mundo de la programación, un ejemplo vale más que mil palabras, así que aquí tenéis un algoritmo típico para obtener el mensaje de un código de error que retorne GetLastError:
{
LPTSTR buff;
HMODULE hLib;
DWORD err = GetLastError();
BOOL liberar = FALSE;
se obtiene el descriptor de la librería
hLib = GetModuleHandle("wininet.dll");
if (!hLib)
{
hLib = LoadLibrary("wininet.dll");
liberar = TRUE;
if (!hLib)
return;
}
FormatMessage(
FORMAT_MESSAGE_FROM_HMODULE | FORMAT_MESSAGE_ALLOCATE_BUFFER, opciones
hLib,
librería
err, código de error
LANG_SYSTEM_DEFAULT,
idioma
buff, , buffer y longitud
NULL
sin parámetros
);
MessageBox(GetActiveWindow(), buff, "Error", MB_ICONERROR);
se libera el buffer que ha reservado FormatMessage
HeapFree(GetProcessHeap(), , buff);
se libera la librería
if (liberar)
FreeLibrary(hLib);
return;
}