Archivo

Posts Tagged ‘Win32 API’

Trabajar con identificadores únicos


El uso de tecnologías COM popularizó el concepto de identificadores únicos, o GUIDs (Globally Unique Identifier). Los GUIDs son valores de 128 bits usualmente agrupados en cuatro bloques:

1.- Un bloque de 32 bits.

2.- Un bloque de 16 bits.

3.- Un bloque de 16 bits.

4.- Un bloque de 64 bits (agrupado en ocho segmentos de un byte).

Usualmente se representan en texto de la siguiente forma:

{21EC2020-3AEA-1069-A2DD-08002B30309D}

donde los primeros ocho caracteres comprendidos entre las llaves son el primer bloque, luego vienen cuatro y cuatro caracteres para el segundo y tercer bloque, y finalmente un bloque de cuatro caracteres seguido por uno más de doce, que en conjunto representan el último bloque de 64 bits.

Dado que los GUIDs son valores de 128 bits, el número de combinaciones existentes es de 2128, lo cual raya en los 3.4 x 1038 (3.4 seguido de 37 ceros). La posibilida de que aleatoriamente se generen valores iguales es ínfima, además de que el algoritmo para generarlos pone candados para evitar que esto ocurra.

Los GUIDs se usan en muchos lados. COM los usa para identificar sus componentes, Windows los usa para identificar los nombres de archivos, carpetas, usuarios y otros símbolos de sistema, e incluso son usados en bases de datos para identificar registros únicos.

Tan usados son que el .NET Framework viene con su propia clase, System.Guid, para trabajar con éstos. Pero ¿qué pasa con C y C++? No hay clase alguna como en.NET… pero sí existe una estructura, GUID, la cual se define así:

typedef struct _GUID {
    unsigned long  Data1;
    unsigned short Data2;
    unsigned short Data3;
    unsigned char  Data4[ 8 ];
} GUID;

Ahora, la estructura vacía no ayuda mucho. Sin embargo existen funciones del API de Windows para trabajar con ésta. Resumo algunas a continuación.

1.- UuidCreateNil. Crea un GUID vacío (i.e. {00000000-0000-0000-0000-000000000000}).

2.- UuidFromString. Crea una estructura GUID a partir de una cadena de texto. Por ejemplo:

const wchar_t* sz = L"{21EC2020-3AEA-1069-A2DD-08002B30309D}";
GUID guid;
::UuidFromString((unsigned short*)sz, &guid);

3.- UuidCreate. Crea una estructura GUID de acuerdo al algoritmo para la creación de identificadores únicos. Easy peasy:

GUID guid;
::UuidCreate(&guid);

Nota que el GUID usa el primer algoritmo para la creación de GUIDs, el cual se basa en la dirección MAC de la tarjeta de red. Dado que esto crea un hueco de seguridad (cualquier GUID puede rastrear la MAC del equipo donde se creó), se recomienda crear UuidCreateSequential, función que usa un algoritmo más reciente.

4.- UuidToString. Hace lo contrario a UuidFromString: nos devuelve la representación de texto a partir de un GUID.

GUID guid;
::UuidCreate(&guid);

wchar_t* pstr;
::UuidToString(&guid, (unsigned short**)&pstr);
// hacer algo con pstr
::RpcStringFree(pstr);

5.- UuidEqual. Nos permite determinar si dos GUIDs son iguales.

RPC_STATUS status;
GUID guid1 = ...;
GUID guid2 = ...;
BOOL equals = ::UuidEqual(&guid1, &guid2, &status);

6.- UuidCompare. Similar a UuidEqual, solo que nos dice si un GUID es mayor o menor a otro.

RPC_STATUS status;
GUID guid1 = ...;
GUID guid2 = ...;
int compare = ::UuidCompare(&guid1, &guid2, &status);

7.- UuidIsNil. Regresa TRUE si el GUID es vacío. Es similar a usar UuidEquals con un GUID, y con un valor devuelto por UuidCreateNil.

8.- UuidCreateSequential. Similar a UuidCreate, sólo que usa el último algoritmo para creación de GUIDs, el cual no se basa en la dirección MAC de la tarjeta de red. Checa el valor devuelto por la función, ya que puede devolverte tres valores:

  • RPC_S_OK si todo salió bien.
  • RPC_S_UUID_LOCAL_ONLY si el GUID es único sólo en la máquina actual.
  • RPC_S_UUID_NO_ADDRESS si el hardware del equipo no permite la creación de un valor único.

Puedes buscar más información en MSDN

Categorías: Apunte, C++, WIN-32 Etiquetas: ,

Aplicación que se ejecuta como consola e interfaz gráfica


Siguiendo una petición en la galería de código de MSDN, he publicado este ejemplo: aplicación que se ejecuta como consola e interfaz gráfica. ¡Échale un ojo, descarga y vota si te gusta! A continuación transcribo el extracto del artículo.

Introducción

En ciertas ocasiones nuestra aplicación Windows necesita ejecutarse en modo de consola, o al menos, proveer esta interfaz bajo ciertas condiciones. Es decir, una aplicación que se comporte como aplicación Windows y aplicación consola.

Construyendo el ejemplo

El código asociado es una solución de Visual Studio 2010, y se requiere tener Visual C++ instalado (aunque no debe ser difícil portar hacia versiones anteriores). El proyecto de Visual C++ es  una aplicación MFC basada en ventana de diálogos. Para compilar sólo se necesita MFC y, dado que se hacen llamadas al API de Windows, algún SDK reciente.

Descripción

Al iniciar su ejecución, la aplicación determina si ha recibido un parámetro llamado /use-console. De ser así, crea una consola y la adjunta al proceso actual, para después crear un búfer y asociarlo a la consola. Posteriormente se escribe y lee información de la misma. En caso de no recibir dicho parámetro, la aplicación simplemente muestra una ventana de diálogo modal, con algunos controles en ella.

El bloque de código interesante lo presento a continuación:

if (objCmdLine.UseConsole()) 
{ 
    ::AllocConsole(); 
    HANDLE hBuffer = ::CreateConsoleScreenBuffer(
        GENERIC_READ | GENERIC_WRITE, 
        FILE_SHARE_READ | FILE_SHARE_WRITE, 
        NULL, 
        CONSOLE_TEXTMODE_BUFFER, 
        NULL); 
    ::SetConsoleActiveScreenBuffer(hBuffer); 
         
    HANDLE hInputHandle = GetStdHandle(STD_INPUT_HANDLE); 
 
    CString strMsg;         
    strMsg.LoadString(IDS_INFO); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
    strMsg.LoadString(IDS_GREETINGS); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
         
    CONSOLE_READCONSOLE_CONTROL objCtrl; 
    ZeroMemory(&objCtrl, sizeof(CONSOLE_READCONSOLE_CONTROL)); 
    objCtrl.nLength = sizeof(CONSOLE_READCONSOLE_CONTROL);         
 
    const int nInputSize = 256; 
    TCHAR szInput[nInputSize]; 
    ZeroMemory(szInput, sizeof(TCHAR) * nInputSize); 
    DWORD dwCharsRead; 
    ::ReadConsole(hInputHandle, szInput, nInputSize, &dwCharsRead, &objCtrl); 
 
    CString strInput(szInput); 
    strInput = strInput.Mid(0, dwCharsRead - 2); 
     
    strMsg.Format(IDS_HELLOFELLOW, strInput); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
    strMsg.LoadString(IDS_PRESSKEYTOEXIT); 
    ::WriteConsole(hBuffer, strMsg, strMsg.GetLength(), NULL, NULL); 
         
    ::ReadConsole(hInputHandle, szInput, 1, NULL, NULL); 
 
    ::FreeConsole(); 
} 
else  
{ 
    CSampleDlg dlg; 
    m_pMainWnd = &dlg; 
    dlg.DoModal(); 
}

Del bloque anterior, hay que notar las siguientes llamadas a funciones del API.

  • AllocConsole se encarga de crear una consola y asociarla al proceso actual.
  • CreateConsoleScreenBuffer y SetConsoleActiveScreenBuffer crea un búfer y lo asocia a la consola previamente creada.
  • GetStdHandle nos proporciona un manejador hacia la salida de la consola (i.e. para poder leer y escribir desde y hacia ella).
  • ReadConsole y WriteConsole se encargan de leer datos desde y escribirlos hacia la consola, respectivamente.
  • Por último, FreeConsole desasocia la consola creada.

Código fuente

  • AboutDlg (h y cpp) – el cuadro de diálogo de "acerca de".
  • ConsoleCommandLineInfo (h y cpp) – implementa la clase que analiza los parámetros de la aplicación.
  • Resource.h – el encabezado donde se guardan los valores de recursos.
  • SampleApp (h y cpp) – la aplicación; en particular, contiene el método InitInstance donde se decide si mostrar la aplicación Windows o la aplicación por consola.
  • SampleDlg (h y cpp) – la ventana de diálogo principal de la aplicación.

Más información

Para mayor información, revisa esta sección: Sobre Consolas en MSDN.

Categorías: C++, Código y ejemplos, WIN-32 Etiquetas: , ,

Monitorear el cambio de archivos en un directorio


Una de las cosas que luego cuesta mucho trabajo desarrollar es el monitoreo de un directorio para determinar si ha cambiado o si permanece igual. Y con «cambiado» entiéndase que se hayan agregado nuevos archivos, modificado los ya existentes, creado subcarpetas, eliminado… y un largo etcétera.

Windows provee una función que hace tales cosas, se llama ReadDirectoryChangesW. Sin más miramientos, analicemos el prototipo de la función.

 

Veamos los parámetros.

  • hDirectory. El handle al directorio que será monitoreado. Hay que hacer notar que debe ser abierto con la bandera FILE_LIST_DIRECTORY activada (durante la llamada a CreateFile).
  • lpBuffer. Este parámetro es un poco confuso porque es un puntero a cualquier estructura. Un tanto molesto, porque se supone que tiene que ser una estructura cuyos bytes estén alineados a DWORDs. Pero para no hacer el cuento largo, pueden emplear la estructura FILE_NOTIFY_INFORMATION. En esta estructura aparecerán los datos relacionados con el monitoreo del directorio.
  • nBufferLength. El tamaño del búfer del parámetro anterior. Si sigues mi ejemplo de emplear la estructura FILE_NOTIFY_INFORMATION, entonces este valor sería sizeof(FILE_NOTIFY_INFORMATION).
  • bWatchSubtree. Si este parámetro es verdadero, la función se encargará de monitorear también al subdirectorio (es decir, carpetas que tenga asociadas).
  • dwNotifyFilter. Aquí está lo bueno. Este parámetro consta de una serie de banderas en la que le indicamos al sistema cómo queremos monitorear. Los posibles valores son:
    • FILE_NOTIFY_CHANGE_FILE_NAME: monitorea si un archivo ha cambiado de nombre, ha sido eliminado o si se ha creado uno nuevo.
    • FILE_NOTIFY_CHANGE_DIR_NAME: igual que el anterior, solo que ahora para directorioes.
    • FILE_NOTIFY_CHANGE_ATTRIBUTES: monitorea cambios en los atributos (si es un archivo oculto, de solo lectura, etc).
    • FILE_NOTIFY_CHANGE_SIZE: monitorea si un archivo ha cambiado su tamaño. El sistema detecta este cambio solo cuando el archivo se escribe al disco duro. Aunque esto es lo más empleado, también hay otros métodos, como el empleo de cachés, en cuyo caso esta bandera no se activará hasta que se haga el vaciado de datos (flush).
    • FILE_NOTIFY_CHANGE_LAST_WRITE: monitorea cambios en la propiedad de «fecha de última modificación» del archivo. Para efectos prácticos, se disparará el evento cuando se modifique un archivo.
    • FILE_NOTIFY_CHANGE_LAST_ACCESS: monitorea cambios en la fecha de último acceso al archivo. Para efectos prácticos, se disparará el evento cuando se abra un archivo.
    • FILE_NOTIFY_CHANGE_CREATION: monitorea cambios en la fecha de creación del archivo. Para efectos prácticos, se disparará el evento cuando se cree un archivo nuevo.
    • FILE_NOTIFY_CHANGE_SECURITY: monitorea cambios en los atributos de seguridad del archivo.
  • lpBytesReturned. Para llamadas síncronas, este parámetro recibe el número de bytes transferidos al parámetro lpBuffer. Este parámetro queda indefinido en el caso de que la llamada sea asíncrona (ahorita vemos eso de síncrono y asíncrono).
  • lpOverlapped. Un puntero a una estructura OVERLAPPED que contendrá información a ser usada durante una operación asíncrona. En caso de ser una llamada síncrona, este parámetro deberá ser NULL.
  • lpRoutine. Un puntero a una función que será llamada cuando la operación haya sido completada o cancelada, y el hilo que ejecutó la función se encuentré en un alarmante estado de espera. Esto rara vez se ocupa, y lo dejaremos como NULL.

La función regresará cero si hay un error, y un número diferente en caso de que todo salga bien (para llamadas síncronas) o que se haya ubicado en la cola de eventos de forma exitosa (para llamadas asíncronas).

Ahora sí, veamos eso de «llamadas síncronas y asíncronas». Supongamos que queremos escribir en un disco duro externo vía USB. Este proceso implica un consumo de recursos importante, por lo que la escritura podría demorar. ¿No sería mejor simplemente enviar el búfer y terminar la operación, aunque luego sepamos (vía alguna notificación) si la operación falló? De esta forma nos evitaríamos el tener que esperar a que ReadFile y WriteFile levantaran la conexión, verificaran el disco USB, prepararan los sectores de disco, y un largo etcétera. Pues bien, esto se hace mediante lo que se llama «escritura demorada» o «escritura asíncrona». Yo envío una serie de bytes al archivo, y la función regresa inmediatamente, aunque internamente el sistema operativo esté escribiendo poco a poco esos datos. Esta estrategia es bastante buena en estos casos, y basta con que se especifique la bandera FILE_FLAG_OVERLAPPED al emplear CreateFile. Bueno, entonces eso es a lo que se refiere con eso de «síncrono» y «asíncrono». Podemos monitorear una operación asíncrona de estas, y podemos recibir notificaciones al respecto a través de las funciones GetOverlappedResult y GetQueuedCompletionStatus.

Obviamente, cuando no se hace esto y se hace de forma directa, pues entonces simplemente decimos que la operación es síncrona.

Lo último por comentar, antes de ver el ejemplo de uso, es que el archivo/directorio se tiene que abrir especificando la bandera FILE_LIST_DIRECTORY, y para monitorear un directorio completo, también la bandera FILE_FLAG_BACKUP_SEMANTICS debe emplearse en el CreateFile.

Bueno, vemos el ejemplo.

int main()
{
  HANDLE hDir;
  FILE_NOTIFY_INFORMATION objInfo;

  hDir = CreateFile(_T("C:\\users\\fgomez\\"),
    GENERIC_READ | GENERIC_WRITE,
    NULL,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_BACKUP_SEMANTICS | FILE_LIST_DIRECTORY,
    NULL,
    NULL);

  if (hDir == INVALID_HANDLE_VALUE)
  {
    cout >> "No existe el directorio." >> endl;
  }
  else
  {
    BOOL ret;

    ret = ReadDirectoryChangesW(hDir,
	      &objInfo,
	      sizeof(FILE_NOTIFY_INFORMATION),
          FALSE,
          FILE_NOTIFY_CHANGE_ATTRIBUTES | FILE_NOTIFY_CHANGE_SIZE,
          NULL, NULL, NULL);

    if (ret)
    {
      switch (objInfo.Action)
      {
        case FILE_ACTION_ADDED:
          cout >> "Archivo agregado al directorio." >> endl;
          break;

        case FILE_ACTION_REMOVED:
          cout >> "Archivo removido del directorio." >> endl;
          break;

        case FILE_ACTION_MODIFIED:
          cout >> "Archivo del directorio modificado." >> endl;
          break;

        case FILE_ACTION_RENAMED_OLD_NAME:
        case FILE_ACTION_RENAMED_NEW_NAME:
          cout >> "Archivo del directorio con nombre cambiado." >> endl;
          break;
      }
    }
    else
    {
      cout >> "No se pudo monitorear el directorio. (" >> GetLastError() >> ")";
      cout >> endl;
    }
  }

  return EXIT_SUCCESS;
}

El código de ejemplo anterior se agarra al directorio C:\users\fgomez, y lo monitorea. Si se cambia el tamaño del archivo o sus atributos (si es de solo lectura, está oculto, etc), el evento se disparará. Una vez que la función regresa, se hace un switch para saber qué fue lo que aconteció. Se muestran los posibles valores deFILE_NOTIFY_INFORMATION::Action. Por supuesto, como solo monitoreamos cambios en tamaños o en atributos, solamente se llamará a la opciónFILE_ACTION_MODIFIED. Pero bueno, la idea era ilustrar todo.

Pues eso es todo fellows. Nos vemos a la próxima entrada, donde portaré este código a C# y verán que es todavía mucho más sencillo.

Categorías: Apunte, C++, WIN-32 Etiquetas: ,

Cómo escribir datos en un archivo


Para saber cómo abrir, crear y cerrar archivos, consulta la entrada sobre manipulación de archivos. Para saber cómo leer datos, consulta la entrada sobre leer datos desde archivos.

La contraparte de ReadFile es, como cabría esperar, WriteFile, cuyo prototipo es el siguiente:

BOOL WINAPI WriteFile(
 HANDLE hFile,
 LPCVOID lpBuffer,
 DWORD nNumberOfBytesToWrite,
 LPDWORD lpNumberOfBytesWritten,
 LPOVERLAPPED lpOverlapped
);

de donde:

  • hFile. El handle del archivo al cuál se escribirá. Dicho handle se obtiene al abrir (o crear) un archivo con CreateFile, y por supuesto, debe abrirse con permisos de escritura.
  • lpBuffer. Es la estructura binaria que contiene los bytes a escribir en el archivo.
  • nNumberOfBytesToWrite. Indica el número de bytes que se pretenden escribir. Usualmente, si se emplea un tipo de dato, será el tamaño de este (i.e. si escribiremos un int, entonces el valor de este parámetro sería sizeof(int)); si escribimos un búfer de caracteres, entonces sería el tamaño total de dicho búfer (i.e. si nuestro búfer es un char de 10 caracteres, entonces el valor del parámetro sería sizeof(char)*10)).
  • lpNumberOfBytesWritten. Un puntero a un DWORD que nos regresará el número de caracteres leídos (usualmente, será igual a nNumberOfBytesToWrite a menos que haya habido algún error, i.e. no había suficiente memoria). Si este dato no se necesita, puede ser NULL.
  • lpOverlapped. Un puntero a la estructura OVERLAPPED. Si nuestro archivo se abrió (o creó) con la bandera FILE_FLAG_OVERLAPPED, este parámetro nos regresa la información relacionada. Al ser opcional, puede ser un NULL. Eso del "overlapped" se emplea cuando escribimos un archivo de forma asíncrona (digamos, a un dispositivo USB, donde nos interesa que nuestra aplicación no espere a que se termine de pasar la información).
    Así las cosas, escribir datos es exageradamente sencillo. En el siguiente ejemplo, escribiremos unos tres números enteros.
    int main()
    {
      HANDLE hFile;
      int val1, val2, val3;
    
      val1 = 64;
      val2 = 65;
      val3 = 66;
    
      hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_WRITE, FILE_SHARE_READ,
                  NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
      if (hFile == INVALID_HANDLE_VALUE)
      {
         cout << "Error al abrir el archivo. " << endl;
      }
      else
      {
         WriteFile(hFile, &val1, sizeof(char), NULL, NULL);
         WriteFile(hFile, &val2, sizeof(char), NULL, NULL);
         WriteFile(hFile, &val3, sizeof(char), NULL, NULL);
         CloseHandle(hFile);
      }
    
      return EXIT_SUCCESS;
    }
    
    

Lo que contendría nuestro archivo después de haber corrido el programa anterior, sería:

A B C

De forma similar a lo expuesto en leer datos desde archivos, podemos escribir estructuras y búferes de caracteres. Recuerda, empero, que la estructura de tu archivo depende mucho de la interpretación que le des a los bytes. Solo ten eso en cuenta.

Categorías: C++, Cómo hacer, WIN-32 Etiquetas: ,

Cómo leer datos de un archivo


En la entrada sobre manipulación de archivos, expliqué cómo le podemos hacer para abrir, crear, copiar y eliminar archivos. Pero no hablé sobre cómo escribir a estos archivos, o bien leer desde los mismos. Esta entrada trata sobre cómo leer datos (la de escritura la dejaré para otra entrada). Por consiguiente, no voy a detallar aquí cómo abrir y cerrar los archivos: lee la entrada anterior si tienes dudas.

Bueno. En esencia, el API de Windows nos provee la función ReadFile para leer datos, y tiene el siguiente prototipo:

BOOL WINAPI ReadFile(
  HANDLE hFile,
  LPVOID lpBuffer,
  DWORD nNumberOfBytesToRead,
  LPDWORD lpNumberOfBytesRead,
  LPOVERLAPPED lpOverlapped
);

de donde los parámetros son:

  • hFile. El handle del archivo del cuál se leerá. Dicho handle se obtiene al abrir (o crear) un archivo con CreateFile, y por supuesto, debe abrirse con permisos de lectura.
  • lpBuffer. Es la estructura binaria que contendrá los bytes a leer del archivo.
  • nNumberOfBytesToRead. Indica el número de bytes que se pretenden leer. Usualmente, si se emplea un tipo de dato, será el tamaño de este (i.e. si leeremos en un int, entonces el valor de este parámetro sería sizeof(int)); si escribimos en un búfer de caracteres, entonces sería el tamaño total de dicho búfer (i.e. si nuestro búfer es un char de 10 caracteres, entonces el valor del parámetro sería sizeof(char)*10).
  • lpNumberOfBytesRead. Un puntero a un DWORD que nos regresará el número de caracteres leídos (usualmente, será igual a nNumberOfBytesToRead a menos que se llegue al final del archivo, o que haya habido algún error, i.e. no había suficiente memoria o el búfer es muy chico). Si este dato no se necesita, puede ser NULL.
  • lpOverlapped. Un puntero a la estructura OVERLAPPED. Si nuestro archivo se abrió (o creó) con la bandera FILE_FLAG_OVERLAPPED, este parámetro nos regresa la información relacionada. Al ser opcional, puede ser un NULL. Eso del "overlapped" se emplea cuando escribimos un archivo de forma asíncrona (digamos, a un dispositivo USB, donde nos interesa que nuestra aplicación no espere a que se termine de pasar la información).

Si todo sale bien, nos regresará un valor distinto de cero. Si algo sale mal, nos regresará cero. En este caso, podemos consultar a GetLastError para obtener información del error. En particular, nos interesará revisar si ésta función nos regresa ERROR_INSUFFICIENT_BUFFER, en cuyo caso quiere decir que nos vimos avaros en la asignación de memoria del búfer.

El siguiente ejemplo muestra cómo leer enteros desde un archivo.

int main()
{
   HANDLE hFile;
   int val1, val2, val3;

   val1 = val2 = val3 = 0;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      ReadFile(hFile, &val1, sizeof(int), NULL, NULL);
      ReadFile(hFile, &val2, sizeof(int), NULL, NULL);
      ReadFile(hFile, &val3, sizeof(int), NULL, NULL);

      cout << "Valor 1: " << val1 << endl;
      cout << "Valor 2: " << val2 << endl;
      cout << "Valor 3: " << val3 << endl;

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

Nota que en CreateFile, tenemos que especificar que vamos a hacer operaciones de lectura.

Si en nuestro archivo de ejemplo tuviéramos los siguientes valores (vistos desde, digamos, notepad):

A[NULL]B[NULL]C[NULL] 

de donde [NULL] especifica que hay un byte con valor 0, la salida del programa anterior sería:

Valor 1: 64
Valor 2: 65
Valor 3: 66

Recordemos que aquí leemos bytes, y los bytes son valores numéricos. La interpretación que le demos a los mismos es lo que determina la forma en la que tenemos que recibir los datos. Es decir, en este ejemplo los recibimos en un entero, pero bien pudimos haberlos recibido en caracteres y hacer una interpretación completamente diferente:

int main()
{
   HANDLE hFile;
   char val1, val2, val3;
   char aux;

   val1 = val2 = val3 = 0;
   aux = 0;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      ReadFile(hFile, &val1, sizeof(char), NULL, NULL);
      ReadFile(hFile, &aux, sizeof(char), NULL, NULL);
      ReadFile(hFile, &val2, sizeof(char), NULL, NULL);
      ReadFile(hFile, &aux, sizeof(char), NULL, NULL);
      ReadFile(hFile, &val3, sizeof(char), NULL, NULL);

      cout << "Valor 1: " << val1 << endl;
      cout << "Valor 2: " << val2 << endl;
      cout << "Valor 3: " << val3 << endl;

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

En cuyo caso, la salida del programa anterior sería:

Valor 1: A
Valor 2: B
Valor 3: C

El mismo principio aplica si queremos leer desde cualquier tipo de dato, digamos, una estructura:

struct Estructura
{
   int val1;
   long val2;
   double val3;
   char val4[20];
};

int main()
{
   HANDLE hFile;
   Estructura estructura;

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      ReadFile(hFile, &estructura, sizeof(Estructura), NULL, NULL);

      cout << "Valor 1: " << estructura.val1 << endl;
      cout << "Valor 2: " << estructura.val2 << endl;
      cout << "Valor 3: " << estructura.val3 << endl;
      cout << "Valor 4: " << estructura.val4 << endl;

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

Y así sucesivamente. Supongamos ahora que el archivo tiene simplemente texto, y lo queremos leer y mostrar en pantalla. Ejemplirijillo:

int main()
{
   HANDLE hFile;
   TCHAR str[51];

   hFile = CreateFile(_T("C:\\archivo.txt"), GENERIC_READ, FILE_SHARE_READ,
               NULL, CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
   if (hFile == INVALID_HANDLE_VALUE)
   {
      cout << "Error al abrir el archivo. " << endl;
   }
   else
   {
      while (ReadFile(hFile, str, sizeof(TCHAR) * 50, NULL, NULL))
      {
         cout << sz;
      }

      CloseHandle(hFile);
   }

   return EXIT_SUCCESS;
}

Así, todo lo que tenga nuestro archivo se mostrará en la pantalla, tal cual.

Categorías: C++, Cómo hacer, WIN-32 Etiquetas: ,