English Русский 中文 Deutsch 日本語 Português
Operaciones de archivo a través de WinAPI

Operaciones de archivo a través de WinAPI

MetaTrader 4Ejemplos | 20 abril 2016, 14:52
1 032 0
MetaQuotes
MetaQuotes

Introducción

MQL4 está diseñado de tal forma que los programas escritos de forma incorrecta no pueden borrar datos por error en el disco duro. Las funciones usadas para las operaciones de lectura y escritura solo pueden funcionar en las siguientes direcciones:

  • /HISTORY/<current broker> - especialmente para la función FileOpenHistory;
  • /EXPERTS/FILES - caso común;
  • /TESTER/FILES - especialmente para las pruebas.
No está permitido trabajar con archivos desde otros directorios.

Si necesitamos trabajar fuera de los directorios (definido por razones de seguridad), podemos llamar a las funciones del SO Windows. Para este propósito, se usan ampliamente las funciones de la API de la biblioteca kernel32.dll.


Funciones de archivo de kernel32.dll

Está basada en el script de la base de código en Operaciones con archivos sin limitaciones. Es un buen ejemplo de cómo pueden importarse las funciones a un programa MQL4.

// constants for function _lopen
#define OF_READ               0
#define OF_WRITE              1
#define OF_READWRITE          2
#define OF_SHARE_COMPAT       3
#define OF_SHARE_DENY_NONE    4
#define OF_SHARE_DENY_READ    5
#define OF_SHARE_DENY_WRITE   6
#define OF_SHARE_EXCLUSIVE    7
 
 
#import "kernel32.dll"
   int _lopen  (string path, int of);
   int _lcreat (string path, int attrib);
   int _llseek (int handle, int offset, int origin);
   int _lread  (int handle, string buffer, int bytes);
   int _lwrite (int handle, string buffer, int bytes);
   int _lclose (int handle);
#import

Estas funciones se declaran en msdn como obsoletas, pero todavía pueden usarse, véase Elementos de programación en Windows obsoletos. Daré aquí la descripción de las funciones y parámetros tomadas directamente de dicho hilo, ya que son descritas por el autor del script, mandor:

// _lopen  : It opens the specified file. It returns: file descriptor.
// _lcreat : It creates the specified file. It returns: file descriptor.
// _llseek : It places the pointer in the open file. It returns: 
// the new shift of the pointer.
// _lread  : It reads the given number of bytes from the open file. 
// It returns: the number of the read bytes; 0 - if it is the end of the file.
// _lwrite : It writes the data from buffer into the specified file. It returns: 
// the number of written bytes.
// _lclose : It closes the specified file. It returns: 0.
// In case of unsuccessfully completion, all functions return the value of 
// HFILE_ERROR=-1.
 
// path   : String that defines the path and the filename.
// of     : The way of opening.
// attrib : 0 - reading or writing; 1 - only reading; 2 - invisible, or 
// 3 - system file.
// handle : File descriptor.
// offset : The number of bytes, by which the pointer shifts.
// origin : It indicates the initial point and the shifting direction: 0 - 
// forward from the beginning; 1 - from the current position; 2 - backward from the end of the file.
// buffer : Receiving/writing buffer.
// bytes  : The number of bytes to read.
 
// Methods of opening (parameter 'of'):
// int OF_READ            =0; // Open file for reading only
// int OF_WRITE           =1; // Open file for writing only
// int OF_READWRITE       =2; // Open file in the read/write mode
// int OF_SHARE_COMPAT    =3; // Open file in the mode of common 
// shared access. In this mode, any process can open this given 
// file any amount of times. At the attempt to open this file in any other
// mode, the function returns HFILE_ERROR.
// int OF_SHARE_DENY_NONE =4; // Open file in the mode of common access 
// without disabling the reading/writing by another process. At the attempt to open 
// this file in the mode of OF_SHARE_COMPAT, the function returns HFILE_ERROR.
// int OF_SHARE_DENY_READ =5; // Open file in the mode of common access with 
// disabling the reading by another process. At the attempt to open this file 
// with the flags of OF_SHARE_COMPAT and/or OF_READ, or OF_READWRITE, the function 
// returns HFILE_ERROR.
// int OF_SHARE_DENY_WRITE=6; // The same, but with disabling the writing.
// int OF_SHARE_EXCLUSIVE =7; // Disable for this current and for all other processes 
// to access to this file in the modes of reading/writing. The file in this mode can be 
// opened only once (with the current process). All other attempts 
// to open the file will fail.


Función "Reading from File"

Vamos a considerar la función cuya finalidad es leer del archivo. Su único parámetros es una variable string que contiene el nombre del archivo. La función importada de _lopen(path,0) devuelve el puntero a un archivo abierto y, para sus tareas, es muy parecida a la función FileOpen() en MQL4.

//+------------------------------------------------------------------+
//|   read the file and return a string with its contents            |
//+------------------------------------------------------------------+
string ReadFile (string path) 
  {
    int handle=_lopen (path,OF_READ);           
    if(handle<0) 
      {
        Print("Error opening file ",path); 
        return ("");
      }
    int result=_llseek (handle,0,0);      
    if(result<0) 
      {
        Print("Error placing the pointer" ); 
        return ("");
      }
    string buffer="";
    string char1="x";
    int count=0;
    result=_lread (handle,char1,1);
    while(result>0) 
      {
        buffer=buffer+char1;
        char1="x";
        count++;
        result=_lread (handle,char1,1);
     }
    result=_lclose (handle);              
    if(result<0)  
      Print("Error closing file ",path);
    return (buffer);
  }

La función _lseek() también tiene su análoga en MQL4. Es FileSeek(). La función _lclose se usa para cerrar archivos, como la función FileClose(). La única función nueva es _lread(handle, buffer, bytes) que lee del archivo en cuestión (el puntero en el que debe ser previamente recibida por la función _lopen()) en la variable 'buffer' el número de bytes dados. Debemos usar una constante string de la longitud necesaria como variable 'buffer'. En este ejemplo, podemos ver:

    string char1="x";
    result=_lread (handle,char1,1);

- la constante string 'char' tiene una longitud de uno, es decir, permite leer solo un byte en ella. Al mismo tiempo, el valor de esta constante no importa: puede ser "x", "Z" e incluso " " (el carácter espacio). No podremos leer más bytes en ella de lo que se definió inicialmente para esta constante. En tal caso, los intentos de leer 2 o más bytes no tendrán éxito. Además el resultado de la función _Iread() es el número de bytes realmente leídos. Si el archivo tiene una longitud de 20 bytes e intentamos leer en una variable de 30 bytes de longitud más de 20 bytes, la función devolverá 20. Se aplicamos de forma consecutiva esta función, moveremos a lo largo del archivo leyendo un bloque de archivos por otro. Por ejemplo, un archivo de 22 bytes de longitud. Comenzamos a leerlo en bloques de 10 bytes. Luego, después de dos llamadas a la función _Iread()(handle, buff, 10), quedarán sin leer dos bytes al final del archivo.


En la tercera llamada, __lread(handle, buff, 10) devolverá 2, es decir, se leerán los dos últimos bytes. En la cuarta llamado, la función devolverá el valor cero, no se leerá ningún byte y el puntero estará al final del archivo. Es este hecho lo que subyace al procedimiento de lectura de caracteres en un archivo en el ciclo:

    while(result>0) 
      {
        buffer=buffer+char1;
        char1="x";
        count++;
        result=_lread (handle,char1,1);
     }

Siempre y cuando el resultado (el número de bytes leídos) sea superior a cero, se llamara a la función _Iread(handle, char1, 1) en ciclos. Como podemos ver, no hay nada complejo en estas funciones. El valor del carácter leído se guarda en la variable char1. Este carácter se escribe desde la variable string 'buffer' en la siguiente iteración. Al finalizar las operaciones, la función definida por el usuario ReadFile() devuelve los contenidos del archivo leído en esta variable. Como puede ver, no tiene ninguna dificultad.


Función "Writing to File"

Escribir es, en cierto sentido, incluso más fácil que leer. Debemos abrir un archivo y escribir en él en una matriz de bytes usando la función _Iwrite (int handle, string buffer, int bytes). Aquí, handle es un puntero de archivo obtenido por la función _Iopen(), el parámetro 'buffer' es una variable string y el parámetro 'bytes' muestra cuántos bytes deben escribirse. Al escribir, el archivo se cierra con la función _Iclose(). Vamos a ver la función del autor WriteFile():

//+------------------------------------------------------------------+
//|  write the buffer contents to the given path                     |
//+------------------------------------------------------------------+
void WriteFile (string path, string buffer) 
  {
    int count=StringLen (buffer); 
    int result;
    int handle=_lopen (path,OF_WRITE);
    if(handle<0) 
      {
        handle=_lcreat (path,0);
        if(handle<0) 
          {
            Print ("Error creating file ",path);
            return;
          }
        result=_lclose (handle);
     }
    handle=_lopen (path,OF_WRITE);               
    if(handle<0) 
      {
        Print("Error opening file ",path); 
        return;
      }
    result=_llseek (handle,0,0);          
    if(result<0) 
      {
        Print("Error placing pointer"); 
        return;
      }
    result=_lwrite (handle,buffer,count); 
    if(result<0)  
        Print("Error writing to file ",path," ",count," bytes");
    result=_lclose (handle);              
    if(result<0)  
        Print("Error closing file ",path);
  }

No obstante, debemos realizar una comprobación de errores. En primer lugar, intentamos abrir un archivo para escritura:

    int handle=_lopen (path,OF_WRITE);

(Se llama a la función _lopen() con el parámetro de OF_WRITE).

Si falla el intento (handle < 0), intenta crear un archivo con el nombre indicado:

        handle=_lcreat (path,0);

Si esta función devuelve también un puntero negativo, se truncará la función WriteFile(). El código restante en esta función está claro sin que sea necesario dar más explicaciones. La función más sencilla start() nos permite comprobar cómo funciona el script File_Read_Write.mq4.

//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
    string buffer=ReadFile("C:\\Text.txt");
    int count=StringLen(buffer);
    Print("Bytes counted:",count);
    WriteFile("C:\\Text2.txt",buffer);   
//----
   return(0);
  }
//+------------------------------------------------------------------+

Vemos que la barra inclinada ("\") se escribe dos veces, aunque debe ser una vez. La cuestión es que algunos caracteres especiales, como el carácter de salto de línea ("\n") o el carácter de tabulación ("\t") deben escribirse usando la barra inclinada. Si olvidamos estos puede que tengamos problemas al especificar la ruta en la variable de prueba durante la ejecución del programa.

Por favor, escriba dos barras inclinadas consecutivas en la constante string, no solo una. En tal caso, el compilador lo aceptaría correctamente sin ambigüedades.


Todo funciona, pero hay una cucharada de alquitrán en este barril de miel: la función de script ReadFile() funciona de forma muy lenta con grandes archivos.


La razón de dicha lectura tan lenta del archivo está en que leemos la información byte a byte (carácter). Podemos ver en la figura anterior que el archivo alcanzó un tamaño de 280.324 bytes y necesitó 103 segundos. Esta vez se hace leyendo 1 carácter 280.324 veces. Podemos comprobar inmediatamente el tiempo de trabajo para el script File Read Write.mq4 adjunto al artículo. ¿Cómo podemos aumentar la velocidad de lectura del archivo? La respuesta nos viene impuesta: la función de leer, digamos, 50 caracteres, no un solo carácter cada vez. Luego, la cantidad de llamadas de la función _Iread() se reducirá 50 veces. Por tanto, el tiempo de lectura debe reducirse 50 veces también. Vamos a comprobarlo.

Nueva función para la lectura de un archivo en bloques de 50 bytes

Cambiamos el código nombrando la nueva versión como xFiles.mq4. Lo compilamos y ejecutamos. Recordamos aquí que la importación de funciones de una DLL debe habilitarse en la configuración (Ctrl+O).

De este modo, el tiempo de ejecución del script modificado xFiles.mq4 fue de 2.047 milisegundos, que son unos 2 segundos aproximadamente. Dividimos 103 segundos (el tiempo de ejecución del script inicial) por 2 segundos y obtenemos 103 / 2 = 51.5 veces. De este modo, el tiempo de ejecución del programa ha cambiado realmente 50 veces, tal y como era de esperar. ¿Cómo se ha modificado el código para lograrlo?

Los cambios son pequeños:

string ReadFile (string path) 
  {
    int handle=_lopen (path,OF_READ);
    int read_size = 50;           
    string char50="x                                                 ";
 
    if(handle<0) 
      {
        Print("Error opening file ",path); 
        return ("");
      }
    int result=_llseek (handle,0,0);      
    if(result<0) 
      {
        Print("Error placing the pointer" ); 
        return ("");
      }
    string buffer="";
    int count=0;
    int last;
    
    result=_lread (handle,char50,read_size);
    int readen;
    while(result>0 && result == read_size) 
      {
        buffer=buffer + char50;
        count++;
        result=_lread (handle,char50,read_size);
        last = result;
     }
    Print("The last read block has the size of, in bytes:", last);
    char50 = StringSubstr(char50,0,last);
    buffer = buffer + char50;    
    result=_lclose (handle);              
    if(result<0)  
      Print("Error closing file ",path);
    return (buffer);
  }

Recuerde que la variable string 'char50' ahora es inicializada por la constante de 50 caracteres (el carácter "x" y 49 espacios).


Ahora podemos realizar la lectura de un archivo de tal forma que puedan leerse 50 bytes (Caracteres) al mismo tiempo en esta variable:

result=_lread (handle,char50,read_size);

Aquí: read_size = 50. Por supuesto, no es probable que el tamaño del archivo a leer sea siempre múltiplo de 50 bytes, lo que significa que habrá un momento en que el valor de ejecución de esta función sea distinto a 50. Esta es una señal para detener el ciclo. El último bloque de caracteres, leer en la variable, será recortado hasta la cantidad de bytes realmente leídos.


Podemos cambiar el tamaño del búfer a leer al tamaño de N usando la función Iread(), pero no olvidemos hacer dos modificaciones:

  1. establecer el valor de read_size para 'N';
  2. inicializar la variable string 'char50' de la longitud constante de N (N<256).

Bueno, hemos acelerado la operación de lectura. La última tarea que queda es procesar el error de la ruta no existente al intentar escribir en un archivo. En la función WriteFile(), se realiza un intento de crear un archivo, pero la situación no es procesada al no haber una carpeta que contenga la ruta hacia el nombre del archivo. Por tanto, necesitamos otra función -

Función de creación de la carpeta

La función de creación de la carpeta está también disponible en kernel32.dll - Función CreateDirectory. Solo debe recordarse que esta función intenta crear las carpetas inferiores, no crea todas las carpetas intermedias de la ruta, si no están presentes. Por ejemplo, si intentamos usar esta función para crear la carpeta "C:\folder_A\folder_B", el intento solo tendrá éxito si la ruta de "C:\folder_A\" ya ha existido antes de la llamada de la función. De lo contrario, no se creará folder_B. Vamos a añadir la nueva función a la sección de importación:

#import "kernel32.dll"
   int _lopen  (string path, int of);
   int _lcreat (string path, int attrib);
   int _llseek (int handle, int offset, int origin);
   int _lread  (int handle, string buffer, int bytes);
   int _lwrite (int handle, string buffer, int bytes);
   int _lclose (int handle);
   int CreateDirectoryA(string path, int atrr[]);
#import

El primer parámetro contiene la ruta para crear una nueva carpeta, mientras que el segundo parámetro, atrr[], sirve para especificar permisos para crear la carpeta y debe ser del tipo ATRIBUTOS DE SEGURIDAD. No proporcionaremos ninguna información para el segundo parámetro, sino que solo pasaremos la matriz vacía llamada 'int'. En este caso, la carpeta a crear heredará todos los permisos de la carpeta raíz. No obstante, antes de intentar usar esta función, tendremos que realizar la operación:

Descomposición de la ruta

De hecho, vamos a crear una carpeta de nivel 4, por ejemplo, como esta:

"C:\folder_A\folder_B\folder_C\folder_D"

Aquí podemos llamar a folder_D una carpeta de 4º nivel, ya que hay tres niveles de carpeta por encima de ella. El disco 'C:' contiene "folder_A", la carpeta "C:\folder_A\" contiene "folder_B", la carpeta "C:\folder_A\folder_B\" contiene "folder_C"; y así sucesivamente. Esto significa que tenemos que descomponer toda la ruta del archivo en una matriz de subcarpetas. Vamos a llamar a la función necesaria ParsePath():

//+------------------------------------------------------------------+
//| break apart the path  into an array of subdfolders               |
//+------------------------------------------------------------------+
bool ParsePath(string & folder[], string path)
   {
   bool res = false;
   int k = StringLen(path);
   if (k==0) return(res);
   k--;
 
   Print("Parse path=>", path);
   int folderNumber = 0;
//----
   int i = 0;
   while ( k >= 0 )
      {
      int char = StringGetChar(path, k);
      if ( char == 92) //  back slash "\"
         {
         if (StringGetChar(path, k-1)!= 92)
            {
            folderNumber++;
            ArrayResize(folder,folderNumber);
            folder[folderNumber-1] = StringSubstr(path,0,k);
            Print(folderNumber,":",folder[folderNumber-1]);
            }
         else break;         
         }
      k--;   
      }
   if (folderNumber>0) res = true;   
//----
   return(res);   
   }   
//+------------------------------------------------------------------+

El delimitador entre carpetas es el carácter '\' que tiene el valor 92 en la codificación ANSI. La función toma 'path' como argumento y rellena la matriz llamada folder[], pasada a la función, con los nombres de las rutas encontradas, comenzando con la inferior y finalizando con la más alta. En nuestro ejemplo, la matriz contendrá los siguientes valores:

folder[0] = "C:\folder_A\folder_B\folder_C\folder_D";
folder[1] = "C:\folder_A\folder_B\folder_C";
folder[2] = "C:\folder_A\folder_B";
folder[3] = "C:\folder_A";
folder[4] = "C:";

Si queremos escribir un archivo con el nombre "C:\folder_A\folder_B\folder_C\folder_D\test.txt", podemos dividir la ruta especificada en el nombre del archivo test.txt y la estructura de subcarpetas "C:\folder_A\folder_B\folder_C\folder_D" que contiene este archivo. Si el programa no consigue crear un archivo en esta ruta, en primer lugar debe intentarse crear una carpeta del nivel inferior, C:\folder_A\folder_B\folder_C\folder_D".

Si también fracasa el intento de crear esta carpeta, lo más probable es que la carpeta raíz "C:\folder_A\folder_B\folder_C" esté ausente. Por tanto, crearemos las carpetas de niveles mayores hasta que logremos un mensaje sobre la finalización con éxito de la función CreateDirectoryA(). Por esta razón necesitamos una función que rellene la matriz string 'folder[]' con los nombres de las carpetas en orden ascendente. El primer índice cero contiene la carpeta del nivel inferior y el directorio raíz está en el índice de la última matriz.

Ahora podemos ensamblar la función que crea todas las carpetas intermedias necesarias en la ruta dada:

//+------------------------------------------------------------------+
//|  It creates all necessary folders and subfolders                 |
//+------------------------------------------------------------------+
bool CreateFullPath(string path)
   {
   bool res = false;
   if (StringLen(path)==0) return(false);
   Print("Create path=>",path);
//----
   string folders[];
   if (!ParsePath(folders, path)) return(false);
   Print("Total subfolders:", ArraySize(folders));
   
   int empty[];
   int i = 0;
   while (CreateDirectoryA(folders[i],empty)==0) i++;
   Print("Create folder:",folders[i]);
   i--;
   while (i>=0) 
      {
      CreateDirectoryA(folders[i],empty);
      Print("Created folder:",folders[i]);
      i--;
      }
   if (i<0) res = true;   
//----
   return(res);
   }

Ahora solo queda realizar un pequeño cambio en la función WriteFile() teniendo en cuenta la posibilidad de crear una nueva carpeta.

//+------------------------------------------------------------------+
//|  write the buffer contents to the given path                     |
//+------------------------------------------------------------------+
void WriteFile (string path, string buffer) 
  {
    int count=StringLen (buffer); 
    int result;
    int handle=_lopen (path,OF_WRITE);
    if(handle<0) 
      {
        handle=_lcreat (path,0);
        if(handle<0) 
          {
            Print ("Error creating file ",path);
            if (!CreateFullPath(path))
               {
               Print("Failed creating folder:",path);
               return;
               }
            else handle=_lcreat (path,0);   
          }
        result=_lclose (handle);
        handle = -1;
     }
    if (handle < 0) handle=_lopen (path,OF_WRITE);               
    if(handle<0) 
      {
        Print("Error opening file ",path); 
        return;
      }
    result=_llseek (handle,0,0);          
    if(result<0) 
      {
        Print("Error placing the pointer"); 
        return;
      }
    result=_lwrite (handle,buffer,count); 
    if(result<0)  
        Print("Error writing to file ",path," ",count," bytes");
    result=_lclose (handle);              
    if(result<0)  
        Print("Error closing file ",path);
    return;        
  }

La lógica de funcionamiento de la función modificada se indica en la figura siguiente.


Recuerde que después de cerrar el archivo nuevo creado, establecemos la variable descriptora del archivo 'handle' con un valor negativo.

        result=_lclose (handle);
        handle = -1;

Esto se hace para comprobar el valor de 'handle' una línea por debajo y abrir el archivo para leer solo si se falló en la primera apertura.

    if (handle < 0) handle=_lopen (path,OF_WRITE);

Esto nos permitirá evitar situaciones en las que se abran múltiples archivos por error y luego se dejen sin cerrar. En tales casos, el sistema operativo informará de que se ha superado la cantidad máxima permitida de archivos abiertos y no nos permitirá abrir nuevos archivos.

Vamos a modificar la función start() para comprobar las nuevas características:

//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
    int start = GetTickCount();
    string buffer=ReadFile("C:\\Text.txt");
 
    int middle = GetTickCount();
    int count=StringLen(buffer);
 
    Print("Bytes read:",count);
 
    WriteFile("C:\\folder_A\\folder_B\\folder_C\\folder_D\\Text2.txt",buffer);   
    int finish = GetTickCount();
    Print("File size is ",count," bytes. Reading:",(middle-start)," ms. Writing:",(finish-middle)," ms.");
//----
   return(0);
  }
//+------------------------------------------------------------------+

y a ejecutar el script xFiles.mq4.


Conclusión

No es muy difícil usar las funciones de WinAPI, pero debemos recordar el lado opuesto a dejar el "sandbox":

Antes de ejecutar una aplicación desconocida con la extensión ex4 (sin el código fuente MQL4), que requiere importar funciones de DLL externas, piense en las posibles consecuencias.

Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/1540

Archivos adjuntos |
xFiles.mq4 (8.59 KB)
Asesores expertos basados en sistemas de trading populares y alquimia de la optimización de robots de trading (Parte VII) Asesores expertos basados en sistemas de trading populares y alquimia de la optimización de robots de trading (Parte VII)
En este artículo, el autor da un ejemplo de asesor experto que cumple los requisitos establecidos en las Reglas del Campeonato de Trading Automatizado de 2008.
Cambiar los parámetros externos de los programas de MQL4 sin reiniciar Cambiar los parámetros externos de los programas de MQL4 sin reiniciar
El artículo describe un método para cambiar los parámetros externos de los programas de MQL4 sobre la marcha, sin reiniciar.
Operaciones con grupos de archivos Operaciones con grupos de archivos
Algunas veces es necesario realizar las mismas operaciones con un grupo de archivos. Si tenemos una lista de archivos en un grupo, esto no es un problema. Sin embargo, si necesitamos hacer esta lista nosotros mismos, surge la pregunta: &quot;¿C&oacute;mo podemos hacerlo?&quot; El art&iacute;culo propone hacerlo mediante las funciones FindFirstFile() y FindNextFile() incluidas en kernel32.dll.
Notas de aficionado: ZigZag Notas de aficionado: ZigZag
Seguramente, un iluso que pensara operar cerca de los extremos visitaría cada aprendiz de trader cuando es viera una polilínea "enigmática" por primera vez. Es tan simple, ciertamente. Este es el máximo. Y este es el mínimo. Una bonita imagen en el historial. ¿Y qué ocurre en la práctica? Se dibuja un rayo. Parecerá que este es el pico. Es el momento de vender. Y ahora vamos hacia abajo. Pero, demonios, no es así. El precio se mueve traicioneramente hacia abajo. ¡Sooo! Es una nimiedad, no un indicador. Y nos deshacemos de él.