- Métodos de almacenamiento de la información: texto y binario
- Escritura y lectura de archivos en modo simplificado
- Abrir y cerrar archivos
- Gestión de descriptores de archivo
- Seleccionar una codificación para el modo texto
- Escritura y lectura de arrays
- Escritura y lectura de estructuras (archivos binarios)
- Escritura y lectura de variables (archivos binarios)
- Escritura y lectura de variables (archivos de texto)
- Gestión de la posición en un expediente
- Obtención de las propiedades de un archivo
- Forzar escritura de caché en disco
- Eliminación de un archivo y comprobación de su existencia
- Copia y desplazamiento de archivos
- Búsqueda de archivos y carpetas
- Trabajar con carpetas
- Cuadro de diálogo de selección de archivos o carpetas
Gestión de descriptores de archivo
Dado que necesitamos recordar constantemente los archivos abiertos y liberar los descriptores locales al salir de las funciones, sería eficiente confiar toda la rutina a objetos especiales.
Este enfoque es bien conocido en programación y se denomina RAII (Resource Acquisition Is Initialization, por sus siglas en inglés). El uso de RAII facilita el control de los recursos y garantiza que se encuentran en el estado correcto. En concreto, esto es especialmente efectivo si la función que abre el archivo (y crea un objeto propietario para él) sale desde varios lugares diferentes.
El ámbito de aplicación de RAII no se limita a los archivos. En la sección Plantillas de tipos de objeto hemos creado la clase AutoPtr, que administra un puntero a un objeto. Este era otro ejemplo de este concepto, ya que un puntero es también un recurso (memoria) y es muy fácil perderlo, además de que consume muchos recursos liberarlo en varias ramas diferentes del algoritmo.
Una clase envolvente de archivos también puede ser útil de otra manera. La API de archivos no proporciona una función que permita obtener el nombre de un archivo mediante un descriptor (a pesar de que esa relación existe sin duda internamente). Al mismo tiempo, dentro del objeto, podemos almacenar este nombre e implementar nuestro propio enlace al descriptor.
En el caso más simple necesitamos alguna clase que almacene un descriptor de archivo y lo cierre automáticamente en el destructor. En el archivo FileHandle.mqh se muestra un ejemplo de aplicación.
class FileHandle
|
Dos constructores, así como un operador de asignación sobrecargado, garantizan que un objeto esté vinculado a un archivo (descriptor). El segundo constructor permite pasar una referencia a una variable local (del código de llamada), que además obtendrá un nuevo descriptor. Será una especie de alias externo para el mismo descriptor, que podrá utilizarse de la forma habitual en otras llamadas a funciones.
Pero también se puede prescindir de un alias. Para estos casos, la clase define el operador '~', que devuelve el valor de la variable interna handle.
int operator~() const
|
Por último, lo más importante para lo que se implementó la clase es el destructor inteligente:
~FileHandle()
|
En él, tras varias comprobaciones, se llama a FileClose para la variable controlada handle. La cuestión es que el archivo puede cerrarse explícitamente en otra parte del programa, aunque esto ya no es necesario con esta clase. Como resultado, el descriptor puede dejar de ser válido en el momento en que se llama al destructor cuando la ejecución del algoritmo abandona el bloque en el que está definido el objeto FileHandle. Para averiguarlo, se realiza una llamada ficticia a la función FileGetInteger. Es ficticia porque no hace nada útil. Si el código de error interno sigue siendo 0 después de la llamada, el descriptor es válido.
Podemos omitir todas estas comprobaciones y escribir simplemente lo siguiente:
~FileHandle()
|
Si el descriptor está dañado, FileClose no devolverá ninguna advertencia. No obstante, hemos añadido comprobaciones para poder emitir información de diagnóstico.
Vamos a probar la clase FileHandle en acción. El script de prueba se llama FileHandle.mq5.
const string dummy = "MQL5Book/dummy";
|
Según la salida en el registro, todo funciona según lo previsto:
FileHandle::~FileHandle: Automatic close for handle: 2
|
Sin embargo, si hay muchos archivos, crear una copia del objeto de seguimiento para cada uno de ellos puede convertirse en un inconveniente. Para estas situaciones tiene sentido diseñar un único objeto que recoja todos los descriptores en un contexto determinado (por ejemplo, dentro de una función).
Dicha clase se implementa en el archivo FileHolder.mqh y se muestra en el script FileHolder.mq5. Una copia del propio FileHolder crea a petición objetos observadores auxiliares de la clase FileOpener, que comparte características comunes con FileHandle, especialmente el destructor, así como el campo handle.
Para abrir un archivo a través de FileHolder debe utilizar su método FileOpen (su firma repite la firma de la función estándar FileOpen).
class FileHolder
|
Todos los objetos de FileOpener se suman en el array files para hacer un seguimiento de su vida útil. En el mismo lugar, los elementos cero marcan los momentos de registro de los contextos locales (bloques de código) en los que se crean los objetos FileHolder. El constructor FileHolder se encarga de ello.
FileHolder()
|
Como sabemos, durante la ejecución de un programa, éste entra en bloques de código anidados (llama a funciones). Si requieren la administración de descriptores de archivo locales, los objetos FileHolder (uno por bloque o menos) deben describirse allí. Según las reglas de la pila (primero en entrar, último en salir), todas esas descripciones se suman en files y luego se liberan en orden inverso a medida que el programa abandona los contextos. El destructor es llamado en cada uno de estos momentos.
~FileHolder()
|
Su tarea consiste en eliminar los últimos objetos FileOpener del array hasta el primer elemento cero encontrado, lo cual indica el límite del contexto (más adelante en el array hay descriptores de otro contexto externo).
Puede estudiar toda la clase por su cuenta.
Veamos su uso en el script de prueba FileHolder.mq5. Además de la función OnStart, dispone de SubFunc. Las operaciones con archivos se realizan en ambos contextos.
const string dummy = "MQL5Book/dummy";
|
No hemos cerrado ningún manejador manualmente, las instancias de FileHolder lo harán automáticamente en los destructores.
He aquí un ejemplo de salida de registro:
OnStart enter
|