Sistemas de ficheros en Windows.
Los sistemas de ficheros montados en un ordenador que corra
Windows están organizados en volúmenes.
Para hacer referencia
a un volumen, se usan las letras de unidad. Esta situación
cambia en Windows 2000.
Podemos saber cuántas unidades hay montadas en el sistema con
GetLogicalDrives
o con
GetLogicalDriveStrings
. La primera nos devuelve
la información sobre un mapa de bits donde la unidad A viene
representada por el bit menos significativo. La segunda nos
da los nombres de las unidades en un búfer separados por el
carácter nulo.
Debido a la gran cantidad de llamadas que existen,
os comentaremos solo las dificultades de manejo de cada una,
debiéndoos remitir al
resumen
para encontrar la información completa.
Una vez sabemos que una unidad existe, podemos obtener su
tipo con GetDriveType
. Hay que pasarle la ruta
del directorio raíz como parámetro. Por ejemplo,
GetDriveType("a:\\");
. El hecho de doblar el
carácter \
viene porque estamos programando en
C y el C exige doblarlo para que no actúe
como carácter de
escape en las cadenas. Tened cuidado porque incluso en la
ayuda del compilador ¡a veces se les olvida!
Sabemos ya el tipo de la unidad.
¿Qué más datos podemos obtener? Por ejemplo
los que nos brinda:
BOOL GetVolumeInformation( LPCTSTR lpRootPathName, LPTSTR lpVolumeNameBuffer,
DWORD nVolumeNameSize, LPDWORD lpVolumeSerialNumber,
LPDWORD lpMaximumComponentLength, LPDWORD lpFileSystemFlags,
LPTSTR lpFileSystemNameBuffer, DWORD nFileSystemNameSize);
La mayor parte de parámetros de la función
son parámetros de
retorno, donde la función nos devuelve información
y nosotros
solo tenemos que proporcionarle la variable donde almacenarla.
En el caso de que sean arrays o cadenas de caracteres le pasamos
también la longitud para que la función
no invada zonas de memoria que no le correspondan.
¿Pero qué os vamos a contar a estas alturas?
Esta función, en concreto,
devuelve el nombre del volumen,
su número de serie, el tamaño
máximo que puede tener en caracteres el nombre de un fichero,
información adicional del volumen
y de qué tipo es el sistema
de ficheros: FAT, NTFS, etc. Los valores almacenados en
lpFileSystemFlags
vienen en el
resumen.
Tenemos también una función
para cambiar el nombre de un volumen,
a saber, SetVolumeLabel
y otra que nos dará el espacio
libre que queda en la unidad: GetDiskFreeSpace
. Esta
información nos la dará
en clústeres (sectores lógicos).
Directorios y manejo de ficheros.
En el tratamiento de los directorios, Windows no presenta ninguna
novedad. Disponemos de las funciones de la API:
GetCurrentDirectory
,
SetCurrentDirectory
,
CreateDirectory
y
RemoveDirectory
, de significado evidente. Para borrar
un directorio, tiene que estar vacío.
Lo que es más novedoso es que se disponga de llamadas a la
API para realizar operaciones sobre ficheros.
Fáciles de manejar, tenemos
CopyFile
,
DeleteFile
y
MoveFile
.
Obtener información de un fichero.
De un fichero podemos conocer su tamaño con:
DWORD GetFileSize( HANDLE hFile, LPDWORD lpFileSizeHigh);
La razón de tan extraños parámetros
la acabamos de ver unas líneas más arriba.
También podemos conocer o cambiar alguno de los tiempos
propios del fichero:
BOOL GetFileTime( HANDLE hFile, LPFILETIME lpCreationTime,
LPFILETIME lpLastAccessTime, LPFILETIME lpLastWriteTime);
Estos tiempos son el tiempo en que se creó el fichero, el
tiempo en que por última vez se accedió a
él y el tiempo más tardío
en el que ha sido modificado. Si el sistema de ficheros no
proporciona alguno de estos tiempos (p. ej. FAT),
nos devolverá
cero. Sin embargo, el problema es que la estructura donde nos
da los datos no es "de consumo humano".
Una vez obtenida la información
debemos usar otras funciones para interpretarla. Si queremos
comparar dos de estos tiempos, usaremos CopareFileTime
y si lo que deseamos es que nos lo traduzcan, usaremos:
BOOL FileTimeToSystemTime( CONST FILETIME *lpFileTime, LPSYSTEMTIME lpSystemTime);
La estructura SYSTEMTIME
ya sí tiene interpretación
directa. Para cambiar los tiempos de un fichero (haciendo
trampa) usaremos SetFileTime
. Los más avispados
os habréis dado ya cuenta
de que hace falta la función opuesta de
traducción. Pues sí,
se llama SystemTimeToFileTime
.
Los muy exigentes disponen de la función
que más información da
de un fichero (la "fstat
" de Windows). Su nombre es
GetFileInformationByHandle
y su especificación está
en el resumen.
Listado de directorios/búsqueda de ficheros.
El procedimiento para encontrar un fichero o para hacer un listado
de los ficheros contenidos en un directorio es el mismo. Consta
de tres pasos:
- Apertura de un objeto de búsqueda
y obtención del
correspondiente handle con:
HANDLE FindFirstFile( LPCTSTR lpFileName, LPWIN32_FIND_DATA lpFindFileData);
En lpFileName
irá el nombre del fichero
que se busca (con posibilidad de uso de comodines para
hacer listados).
- Llamar cuantas veces sea necesario hasta acabar
con el directorio a la función
FindNextFile
.
- Destruir el objeto de búsqueda con la función
FindClose
.
Estas funciones, según van encontrando ficheros,
devuelven información
en una estructura del tipo WIN32_FIND_DATA
. Sus campos
tienen interpretación directa,
excepción hecha del último:
typedef struct _WIN32_FIND_DATA
{DWORD dwFileAttributes;
FILETIME ftCreationTime; FILETIME ftLastAccessTime; FILETIME ftLastWriteTime;
DWORD nFileSizeHigh; DWORD nFileSizeLow;
DWORD dwReserved0; DWORD dwReserved1;
TCHAR cFileName[ MAX_PATH ];
TCHAR cAlternateFileName[ 14 ]; } WIN32_FIND_DATA;
El último es el nombre alternativo que usan los sistemas FAT
para nombrar los ficheros largos. Por ejemplo, "Escritorio"
puede aparecer como "ESCRIT~1".
Práctica.
Hacer un programa que imprima el nombre
y tamaño de los ficheros
del directorio actual que han sido modificados por última vez
un miércoles.
-
La entrada/salida solapada consiste en un método de poder
solapar entrada/salida con trabajo intenso de CPU en una
aplicación. Cuando efectuamos una petición
de lectura de un fichero con
ReadFile
por
ejemplo, sabemos que el hilo solicitante se queda bloqueado
hasta que los datos estén disponibles. En la entrada/salida
solapada, el hilo efectúa la petición de lectura y no
se bloquea. Cuando los datos estén listos es notificado de
modo asíncrono.
Para poder realizar la entrada/salida solapada necesitamos
especificar el flag FILE_FLAG_OVERLAPPED
en
CreateFile
. Además, en las funciones de lectura
y escritura usaremos una estructura del tipo:
typedef struct _OVERLAPPED {
DWORD Internal; DWORD InternalHigh; /* Reservado, no tocar. */
DWORD Offset; DWORD OffsetHigh; /* Desplazamiento donde leer/escribir */
HANDLE hEvent; /* NulL, o evento que queremos se active cuando acabe */
} OVERLAPPED;
De esta estructura no tocaremos ninguno de los cuatro primeros
campos. De hecho, el tercero y el cuarto especificarán el
desplazamiento del puntero de fichero.
El último parámetro tiene que
ver con el método como deseamos
ser avisados de que la entrada/salida
solapada ha acabado. Si hEvent es NulL, no seremos avisados.
Si no, tiene que ser un handle a un objeto de tipo evento
de los vistos en la sesión anterior.
Este objeto pasará a estar
señalado cuando haya acabado la entrada/salida.
Si lo dejamos a NulL,
¿cómo nos enteraremos? Pues podemos usar
la función GetOverlappedResult
.
Que dónde podéis
encontrar cómo se maneja... efectivamente, en el
resumen.
Las funciones de lectura y escritura, cuando estamos trabajando
con entrada/salida solapada, si la petición se va a posponer
devuelven fallo y el error ERROR_IO_PENDING
en
GetLastError
, aunque sabemos que esto no es
un error en sí.
Entrada/salida solapada.
Versión avanzada.
El colmo de la comodidad en esto de la entrada/salida solapada
es que cuando se realice la operación de entrada/salida,
automáticamente se invoque una
función
de rellamada.
Pues es posible. Para ello, solo hay que usar las funciones
de lectura y escritura extendidas:
BOOL ReadFileEx( HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
BOOL WriteFileEx( HANDLE hFile, LPCVOID lpBuffer, DWORD nNumberOfBytesToWrite,
LPOVERLAPPED lpOverlapped,
LPOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);
En la estructura OVERLAPPED
, que sigue siendo
necesaria, hay que dejar el campo hEvent a NulL. El prototipo
de la función de rellamada será:
VOID CALLBACK FunciOn( DWORD dwErrorCode,
DWORD dwNumberOfBytesTransfered, LPOVERLAPPED lpOverlapped);
Como en todas las funciones de rellamada, los parámetros
con los que se llamará variarán
y serán puestos por el sistema operativo.
Disponemos también de las versiones extendidas
SleepEx
, WaitForSingleObjectEx
y
WaitForMultipleObjectsEx
de sus correspondientes
versiones simples para poder detectar si ya se ha llamado
a la función de rellamada de la entrada/salida solapada.
Para saber más
¿Habéis aprendido a valeros por vosotros mismos con
esta asignatura? ¿No importa lo que os pidan porque aunque
no lo sepáis sabréis cómo buscarlo?
Esta puede ser la prueba de fuego.
Construid un ejecutable que mediante llamadas a la API de
Windows logre apagar acabar con la sesión actual de Windows
y apagar el ordenador automáticamente. En los ordenadores
más modernos el sistema realmente se apagará.
En los más
antiguos, se pedirá al usuario
que lo realice por sí mismo.
El programa en sí no debería ocupar más
de 30 líneas y es
una aplicación de consola.
Con la llegada de las últimas versiones de Windows, hacer
un programa como este se complica aún más. Se
debe lidiar con los nuevos mecanismos de seguridad que impiden
a los procesos comunes realizar según qué
acciones.
Aplicaciones relacionadas.
-
-
Funciones de biblioteca relacionadas.
-
-
LPEs.