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: ,

Manipulación de archivos


La finalidad de esta entrada es poder ver cómo se manejan los archivos desde el sistema operativo.

El estándar de C++ define las clases std::ofstream y std::ifstream para escribir en archivos y leer datos desde archivos. En muchos casos es preferible emplear estas funciones, sin embargo, en otros casos simplemente no son suficientes. Los nuevos sistemas operativos manejan, por ejemplo, varias directivas de seguridad para que solo determinados usuarios tengan acceso al archivo, o a diferentes funcionalidades (abrir, escribir, leer, copiar, eliminar, etc). También tienen opciones como la escritura demorada, y pueden bloquear el archivo para determinados propósitos (i.e. mientras yo lo tenga abierto, solo se puede leer el archivo).

Por ello, Windows nos propociona una serie de funciones para poder crear, leer, modificar y eliminar archivos. Para ello nos da una serie de funciones que vamos a detallar aquí.

Primero, tenemos a CreateFile. El nombre es un poco engañoso, ya que la función se emplea tanto para abrir archivos como para crear nuevos. El prototipo es el siguiente:

HANDLE CreateFile(
       LPCTSTR lpFileNAme,
       DWORD dwDesiredAccess,
       DWORD dwShareMode,
       LPSECURITY_ATTRIBUTES lpSecurityAttributes,
       DWORD dwCreationDisposition,
       DWORD dwFlagsAndAttributes,
       HANDLE hTemplateFile
);

 

El primer parámetro no tiene sorpresas. Ahí viene el nombre del archivo, con el path completo o relativo al directorio actual (GetCurrentDirectory). En el segundo parámetro, ponemos una serie de banderas con los permisos que queremos para el archivo. Para ver una lista completa, puedes consultar la lista aquí. Sin embargo, por lo general emplearemos GENERIC_READ cuando solo queramos leer datos del archivo y GENERIC_WRITE cuando queramos escribir en él. Por supuesto, pasar GENERIC_READ | GENERIC_WRITE nos dará acceso tanto de lectura como escritura. Por otro lado, GENERIC_EXECUTE nos dará la posibilidad de ejecutar (vía shell) un archivo; pero raramente se emplea esta opción.

El siguiente parámetro determina la forma en la que queremos compartir un archivo. Por ejemplo, supongamos que yo estoy leyendo un archivo. Si solo leo, no tiene caso que otros procesos no puedan leer, por lo que sería correcto permitir que éstos pudiesen leer también. Si la información no es importante, tampoco habría problema si se permite que otro proceso pueda modificar el archivo mientras lo estoy leyendo. En el caso de un archivo con datos críticos no querríamos que mientras leemos se pudiese modificar. Bueno, pues con este parámetro establecemos cómo queremos compartir el mismo. FILE_SHARE_DELETE permitiría que otros procesos pudiesen eliminar el archivo aún cuando yo lo tenga abierto. FILE_SHARE_READ hace lo propio cuando otro proceso intenta leer datos del mismo, y para cuando intente escribir datos, la bandera es FILE_SHARE_WRITE. Por supuesto, se pueden combinar estos valores.

El siguiente parámetro es un puntero a una estructura SECURITY_ATTRIBUTES. Ésta nos regresa información sobre si es posible que el manejador (handle) al archivo que estamos abriendo/creando se puede "heredar" a procesos hijos. Por lo regular este será siempre NULL. En el parámetro dwCreationDisposition es donde especificamos cómo queremos que la función se comporte. CREATE_ALWAYS hará que si el archivo existe, lo sobreescriba, y si no existe, lo cree. ConCREATE_NEW se crearía el archivo si no existe, y de hacerlo la función fallaría. Para siempre abrir un archivo (y en caso de que no exista, que sea creado), especificamos OPEN_ALWAYS; y si queremos que lo abra si existe y la función falle si no existe, entonces emplearíamos OPEN_EXISTING. Finalmente, conTRUNCATE_EXISTING abriríamos un archivo (que necesariamente tiene que existir) y se establecería su tamaño a cero (es decir, borra toda la información contenida).

El siguiente parámetro establece los atributos del archivo. Son varios, pero por ejemplo, con FILE_ATTRIBUTE_HIDDEN lo marcamos como un archivo oculto, conFILE_ATTRIBUTE_READONLY sería un archivo de solo lectura, y especificaíamos que se trata de un archivo de sistema con FILE_ATTRIBUTE_SYSTEM. Y claro,FILE_ATTRIBUTE_NORMAL lo marca como un archivo normalito.

Finalmente, el último parámetro se usa cuando queremos abrir un archivo en base a una plantilla. Por ejemplo, si abrí un archivo con determinadas características, puedo pasar ese HANDLE para que tome las características de dicho archivo. Esto se emplea sobre todo al crear nuevos archivos, pero por lo regular será NULL.

La función retorna un HANDLE al archivo abierto, y éste será con el que lograremos leer datos y escribir. Si la función falla, retorna INVALID_HANDLE_VALUE. Para cerrar el archivo abierto, empleamos CloseHandle.

También hay otras operaciones con archivos. La función DeleteFile elimina el archivo especificado. Su prototipo es:

BOOL DeleteFile(LPCTSTR lpFileName);

donde lpFileName es el nombre del archivo a eliminar. La función regresa cero si falla, y diferente de cero si tiene éxito. Para eliminar el archivo, el hilo que hace la llamada debe tener permisos de escritura en el directorio actual, o la función regresará ERROR_ACCESS_DENIED. Si el archivo no existe, el código de error seráERROR_FILE_NOT_FOUND.

Otra función importante es CopyFile. Su prototipo es:

BOOL CopyFile(LPCTSTR lpExistingFileName, LPCTSTR lpNewFileName, BOOL bFailIfExists);

El primer parámetro es el nombre del archivo a copiar (con todo y ubicación). El segundo es a dónde queremos copiarlo (inclusive cambiando de nombe). Finalmente, si el tercer parámetro es TRUE, entonces la función falla si el archivo existe. Si es FALSE, entonces de existir, se sobreescribe.

Ya para cerrar esta entrada (que ya me tengo que ir), un pequeño ejemplito donde se crea un archivo, se copia, se cierra y se elimina.

int main()
{
   HANDLE hFile;

   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
   {
       CloseHandle(hFile);
   }

   BOOL ret;
   ret = CopyFile(_T("C:\\archivo.txt"), _T("C:\\temp\\otroarchivo.dat"), TRUE);
   if (ret)
   {
       cout >> "Archivo copiado." >> endl;
   }
   else
   {
       switch (GetLastError())
       {
           case ERROR_FILE_NOT_FOUND:
               cout >> "Archivo inexistente." >> endl;
               break;

           case ERROR_ACCESS_DENIED:
               cout >> "No se tienen permisos." >> endl;
               break;

           default:
               cout >> "Error al copiar el archivo." >> endl;
               break;
       }
   }

   ret = DeleteFile(_T("C:\\archivo.txt"));
   if (!ret)
   {
       cout >> "No se pudo eliminar el archivo." >> endl;
   }
   else
   {
       cout >> "Archivo eliminado correctamente." >> endl;
   }

   return EXIT_SUCCESS;
}

Hasta la próxima.

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