- 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
Abrir y cerrar archivos
Para escribir y leer datos de un archivo, la mayoría de las funciones de MQL5 requieren que primero se abra el archivo. Para ello existe la función FileOpen. Una vez realizadas las operaciones necesarias, el archivo abierto debe cerrarse mediante la función FileClose. El hecho es que un archivo abierto puede, dependiendo de las opciones aplicadas, estar bloqueado para el acceso desde otros programas. Además, las operaciones de archivo se almacenan en memoria (caché) por razones de rendimiento, y si no se cierra el archivo, es posible que no se carguen físicamente nuevos datos en él durante algún tiempo. Esto es especialmente crítico si los datos que se escriben están a la espera de un programa externo (por ejemplo, al integrar un programa MQL con otros sistemas). Descubriremos una forma alternativa de vaciar el búfer en el disco en la descripción de la función FileFlush.
Un número entero especial denominado descriptor se asocia con un archivo abierto en un programa MQL. Lo devuelve la función FileOpen. Todas las operaciones relacionadas con el acceso o la modificación del contenido interno de un archivo requieren que se especifique este identificador en las funciones de la API correspondientes. Las funciones que operan sobre el archivo completo (copiar, borrar, mover, comprobar existencia) no necesitan descriptor. No es necesario abrir el archivo para realizar estos pasos.
int FileOpen(const string filename, int flags, const short delimiter = '\t', uint codepage = CP_ACP)
int FileOpen(const string filename, int flags, const string delimiter, uint codepage = CP_ACP)
La función abre un archivo con el nombre especificado, en el modo especificado por el parámetro flags. El parámetro filename puede contener subcarpetas delante del nombre real del archivo. En este caso, si el archivo se abre para escritura y la jerarquía de carpetas requerida aún no existe, se creará.
El parámetro flags debe contener una combinación de constantes que describan el modo requerido de trabajar con el archivo. La combinación se realiza mediante las operaciones de OR a nivel de bits. A continuación se muestra una tabla con las constantes disponibles.
Identificador |
Valor |
Descripción |
---|---|---|
FILE_READ |
1 |
El archivo se abre para lectura |
FILE_WRITE |
2 |
El archivo se abre para escritura |
FILE_BIN |
4 |
Modo binario de lectura-escritura, sin conversión de datos de cadena a cadena |
FILE_CSV |
8 |
Archivo de tipo CSV; los datos que se escriben se convierten en texto del tipo apropiado (Unicode o ANSI, véase más abajo) y, al leerlos, se realiza la conversión inversa del texto al tipo requerido (especificado en la función de lectura); un registro CSV es una sola línea de texto, delimitada por caracteres de nueva línea (normalmente CRLF); dentro del registro CSV, los elementos están separados por un carácter delimitador (parámetro delimiter); |
FILE_TXT |
16 |
Archivo de texto sin formato, similar al modo CSV, pero no se utiliza carácter delimitador (se ignora el valor del parámetro delimiter). |
FILE_ANSI |
32 |
Cadenas de tipo ANSI (caracteres de un byte) |
FILE_UNICODE |
64 |
Cadenas de tipo Unicode (caracteres de doble byte) |
FILE_SHARE_READ |
128 |
Acceso de lectura compartido desde varios programas |
FILE_SHARE_WRITE |
256 |
Acceso de escritura compartido por varios programas |
FILE_REWRITE |
512 |
Permiso para sobrescribir un archivo (si ya existe) en funciones FileCopy y FileMove |
FILE_COMMON |
4096 |
Ubicación del archivo en la carpeta compartida de todos los terminales de cliente /Terminal/Common/Files (la bandera se utiliza al abrir archivos (FileOpen), copiar archivos (FileCopy, FileMove) y comprobar la existencia de archivos (FileIsExist)) |
Al abrir un archivo, debe especificarse una de las banderas FILE_WRITE, FILE_READ o su combinación.
Las banderas FILE_SHARE_READ y FILE_SHARE_WRITE no sustituyen ni anulan la necesidad de especificar las banderas FILE_READ y FILE_WRITE.
El entorno de ejecución del programa MQL siempre almacena en búfer los archivos para su lectura, lo que equivale a añadir implícitamente la bandera FILE_READ. Debido a esto, FILE_SHARE_READ debe utilizarse siempre para trabajar correctamente con archivos compartidos (incluso si se sabe que otro proceso tiene abierto un archivo de sólo escritura).
Si no se especifica ninguna de las banderas FILE_CSV, FILE_BIN, FILE_TXT, se asume FILE_CSV como la prioridad más alta. Si se especifica más de una de estas tres banderas, se aplica la de mayor prioridad pasada (se enumeran más arriba en orden descendente de prioridad).
Para los archivos de texto, el modo por defecto es FILE_UNICODE.
El parámetro delimiter que sólo afecta a CSV puede ser del tipo ushort o string. En el segundo caso, si la longitud de la cadena es superior a 1, sólo se utilizará su primer carácter.
El parámetro codepage sólo afecta a los archivos abiertos en modo texto (FILE_TXT o FILE_CSV), y sólo si se selecciona el modo FILE_ANSI para las cadenas. Si las cadenas se almacenan en Unicode (FILE_UNICODE), la página de códigos no es importante.
Si tiene éxito, la función devuelve un descriptor de archivo, un número entero positivo. Es único sólo dentro de un programa MQL en particular; no tiene sentido compartirlo con otros programas. Para seguir trabajando con el archivo, el descriptor se pasa a las llamadas a otras funciones.
En caso de error, el resultado es INVALID_HANDLE (-1). La esencia del error debe aclararse a partir del código devuelto por la función GetLastError.
Todos los ajustes del modo de funcionamiento realizados en el momento de abrir el archivo permanecen inalterados mientras el archivo esté abierto. Si es necesario cambiar el modo, hay que cerrar el archivo y volver a abrirlo con los nuevos parámetros.
Para cada archivo abierto, el entorno de ejecución del programa MQL mantiene un puntero interno, es decir, la posición actual dentro del archivo. Inmediatamente después de abrir el archivo, el puntero se sitúa al principio (posición 0). En el proceso de escritura o lectura, la posición se desplaza adecuadamente, según la cantidad de datos transmitidos o recibidos de varias funciones de archivo. También es posible influir directamente en la posición (retroceder o avanzar). Todas estas oportunidades se analizarán en las secciones siguientes.
FILE_READ y FILE_WRITE en varias combinaciones le permiten lograr varios escenarios:
- FILE_READ - abrir un archivo sólo si existe; en caso contrario, la función devuelve un error y no se crea ningún archivo nuevo.
- FILE_WRITE - crear un nuevo archivo si no existe ya, o abrir un archivo existente; su contenido se borra y el tamaño se pone a cero.
- FILE_READ|FILE_WRITE - abrir un archivo existente con todo su contenido o crear un nuevo archivo si aún no existe.
Como puede ver, algunos escenarios son inaccesibles únicamente debido a las banderas. En concreto, no se puede abrir un archivo para escribir sólo si ya existe. Para ello se pueden utilizar funciones adicionales, como por ejemplo FileIsExist. Además, no será posible restablecer «automáticamente» un archivo abierto para una combinación de lectura y escritura: en este caso, MQL5 siempre deja el contenido.
Para añadir datos a un archivo, no sólo hay que abrir el archivo en modo FILE_READ|FILE_WRITE, sino también desplazar la posición actual dentro del archivo hasta el final llamando a FileSeek.
La descripción correcta del acceso compartido al archivo es un requisito previo para la correcta ejecución de File Open. Este aspecto se gestiona del siguiente modo:
- Si no se especifica ninguna de las banderas FILE_SHARE_READ y FILE_SHARE_WRITE, el programa actual obtiene acceso exclusivo al archivo si lo abre primero. Si el mismo archivo ya ha sido abierto antes por alguien (por otro programa o por el mismo programa), la llamada a la función fallará.
- Cuando la bandera FILE_SHARE_READ está activada, el programa permite peticiones posteriores para abrir el mismo archivo para lectura. Si en el momento de la llamada a la función el archivo ya está abierto para su lectura por otro programa o por el mismo, y esta bandera no está activada, la función fallará.
- Cuando la bandera FILE_SHARE_WRITE está activada, el programa permite peticiones posteriores para abrir el mismo archivo para escritura. Si en el momento de la llamada a la función el archivo ya ha sido abierto para escritura por otro programa o por el mismo y esta bandera no está activada, la función fallará.
El acceso compartido se comprueba no sólo en relación con otros programas MQL o procesos externos a MetaTrader 5, sino también en relación con el mismo programa MQL si vuelve a abrir el archivo.
Por lo tanto, el modo menos conflictivo implica que se especifiquen ambas banderas, pero sigue sin garantizar que el archivo se abra si a alguien ya se le ha emitido un descriptor a él sin compartir. No obstante, deben seguirse normas más estrictas en función de las lecturas o escrituras previstas.
Por ejemplo, al abrir un archivo para su lectura, tiene sentido dejar la posibilidad de que otros lo lean. Además, probablemente pueda permitir que otros escriban en él, si se trata de un archivo que se está reabasteciendo (por ejemplo, un diario). Sin embargo, cuando se abra un archivo para escritura, no merece la pena dejar el acceso de escritura a otros: esto llevaría a una superposición de datos impredecible.
void FileClose(int handle)
La función cierra un archivo previamente abierto por su manejador.
Una vez cerrado el archivo, su manejador en el programa deja de ser válido: si se intenta llamar a cualquier función de archivo sobre él, se producirá un error. No obstante, puede utilizar la misma variable para almacenar un manejador diferente si vuelve a abrir el mismo archivo u otro diferente.
Cuando el programa termina, los archivos abiertos se cierran de manera forzosa y el búfer de escritura, si no está vacío, se escribe en el disco. No obstante, se recomienda cerrar los archivos explícitamente.
Cerrar un archivo cuando haya terminado de trabajar con él es una regla importante que se debe seguir. Esto se debe no sólo al almacenamiento en caché de la información que se escribe, que puede permanecer en la RAM durante algún tiempo y no guardarse en el disco (como ya se ha mencionado anteriormente) si no se cierra el archivo. Además, un archivo abierto consume recursos internos del sistema operativo, y no hablamos de espacio en disco. El número de archivos abiertos simultáneamente es limitado (quizá varios cientos o miles, dependiendo de la configuración de Windows). Si muchos programas mantienen un gran número de archivos abiertos, dicho límite puede alcanzarse y los intentos de abrir nuevos archivos fallarán.
A este respecto es deseable protegerse de la posible pérdida de descriptores utilizando una clase envoltorio que abriría un archivo y recibiría un descriptor al crear un objeto, y el descriptor se liberaría y el archivo se cerraría automáticamente en el destructor.
Crearemos una clase envoltorio después de probar las funciones puras FileOpen y FileClose.
Pero antes de profundizar en los detalles del archivo, preparemos una nueva versión de la macro para ilustrar una salida de nuestras funciones al registro de llamadas. La nueva versión era necesaria porque, hasta ahora, macros como PRT y PRTS (utilizadas en secciones anteriores) «absorbían» los valores de retorno de las funciones durante la impresión. Por ejemplo, escribimos:
PRT(FileLoad(filename, read)); |
En este caso, el resultado de la llamada a FileLoad se envía al registro, pero no es posible obtenerlo en la cadena de código de llamada. A decir verdad, no lo necesitábamos. Pero ahora la función FileOpen devolverá un descriptor de archivo y deberá ser almacenado en una variable para posteriores manipulaciones del mismo.
Hay dos problemas con las macros antiguas: en primer lugar, se basan en la función Print, que consume los datos pasados (enviándolos al registro) pero no devuelve nada por sí misma. En segundo lugar, cualquier valor de una variable con resultado sólo puede obtenerse de una expresión, y una llamada a Print no puede formar parte de una expresión debido a que tiene el tipo void.
Para resolver estos problemas, necesitamos una función auxiliar de impresión que devuelva un valor imprimible. Y empaquetaremos su llamada en una nueva macro PRTF:
#include <MQL5Book/MqlError.mqh>
|
Utilizando el operador mágico de conversión de cadenas '#', obtenemos un descriptor detallado del fragmento de código (expresión A) que se pasa como primer argumento a ResultPrint. La expresión en sí (el argumento de la macro) se evalúa (si existe una función, se llama a ella) y su resultado se pasa como segundo argumento a ResultPrint. A continuación entra en juego la función Print habitual y, por último, se devuelve el mismo resultado al código de llamada.
Para no buscar en la Ayuda la decodificación de los códigos de error, se ha preparado una macro E2S que utiliza la enumeración MQL_ERROR con todos los errores MQL5. Se encuentra en el archivo de encabezado MQL5/Include/MQL5Book/MqlError.mqh. La nueva macro y la función ResultPrint se definen en el archivo PRTF.mqh, junto a los scripts de prueba.
En el script FileOpenClose.mq5 vamos a intentar abrir diferentes archivos y, en particular, el mismo archivo se abrirá varias veces en paralelo. Esto suele evitarse en los programas reales. Para la mayoría de las tareas basta con un único manejador de un archivo concreto en una instancia del programa.
Uno de los archivos, MQL5Book/rawdata, debe existir ya puesto que fue creado por un script de la sección Escritura y lectura de archivos en modo simplificado. Se creará otro archivo durante la prueba.
Elegiremos el tipo de archivo FILE_BIN. Trabajar con FILE_TXT o FILE_CSV sería similar en esta fase.
Reservemos un array para los descriptores de archivo de forma que al final del script cerremos todos los archivos a la vez.
En primer lugar, abramos MQL5Book/rawdata en modo lectura sin acceso compartido. Suponiendo que el archivo no lo está usando ninguna aplicación de terceros, podemos esperar que el manejador se reciba correctamente.
void OnStart()
|
Si intentamos abrir de nuevo el mismo archivo, nos encontraremos con un error porque ni la primera ni la segunda llamada permiten compartir.
ha[1] = PRTF(FileOpen(rawdata, FILE_BIN | FILE_READ)); // -1 / CANNOT_OPEN_FILE(5004) |
Vamos a cerrar el primer manejador, a abrir de nuevo el archivo, pero con permisos de lectura compartida, y nos aseguraremos de que la reapertura ahora funciona (aunque también necesita permitir la lectura compartida):
FileClose(ha[0]);
|
Abrir un archivo para escribir (FILE_WRITE) no funcionará, porque las dos llamadas anteriores de FileOpen sólo permiten FILE_SHARE_READ.
ha[2] = PRTF(FileOpen(rawdata, FILE_BIN | FILE_READ | FILE_WRITE | FILE_SHARE_READ));
|
Ahora vamos a intentar crear un nuevo archivo MQL5Book/newdata. Si lo abre como sólo lectura, no se creará el archivo.
const string newdata = "MQL5Book/newdata";
|
Para crear un archivo, debe especificar el modo FILE_WRITE (la presencia de FILE_READ no es crítica aquí, pero hace que la llamada sea más universal: como recordamos, en esta combinación, la instrucción garantiza que o bien se abrirá el archivo antiguo, si existe, o bien se creará uno nuevo).
ha[3] = PRTF(FileOpen(newdata, FILE_BIN | FILE_READ | FILE_WRITE)); // 3 / ok |
Vamos a intentar escribir algo en un nuevo archivo utilizando la función FileSave ya conocido. Actúa como un «reproductor externo», ya que trabaja con el archivo saltándose nuestro descriptor, de forma muy similar a como podría hacerlo otro programa MQL o una aplicación de terceros.
long x[1] = {0x123456789ABCDEF0};
|
Esta llamada falla porque el manejador se abrió sin permisos de compartición. Cierre y vuelva a abrir el archivo con el máximo de «permisos».
FileClose(ha[3]);
|
Esta vez FileSave funciona conforme a lo esperado.
PRTF(FileSave(newdata, x)); // true |
Puede buscar en la carpeta MQL5/Files/MQL5Book/ y encontrar allí el archivo newdata, de 8 bytes de longitud.
Tenga en cuenta que después de cerrar el archivo, su descriptor se devuelve a la reserva de descriptores libres, y la próxima vez que se abra un archivo (tal vez otro archivo), el mismo número vuelve a entrar en juego.
Para un cierre ordenado, cerraremos explícitamente todos los archivos abiertos.
for(int i = 0; i < ArraySize(ha); ++i)
|