English Русский 中文 Español Deutsch 日本語
Operações de arquivo via WinAPI

Operações de arquivo via WinAPI

MetaTrader 4Exemplos | 8 fevereiro 2016, 10:07
961 0
MetaQuotes
MetaQuotes

Introdução

O MQL4 é projetado de tal forma que até mesmo programas escritos incorretamente são incapazes de deletar dados do disco rígido por engano. As funções usadas para a leitura de arquivo e escrita de operações podem funcionar somente nos seguintes diretórios (citação):

  • /HISTORY/ - especialmente para a função FileOpenHistory;
  • /EXPERTS/FILES - caso comum;
  • /TESTER/FILES - especialmente para teste.
Trabalhar com arquivos de outros diretórios é proibido.

Se você ainda precisar trabalhar fora dos diretórios (definidos por razões de segurança), você pode solicitar as funções do Windows OS. Para esse fim, as funções de API representadas na biblioteca kernel32.dll são amplamente usadas.


Funções de arquivo de kernel32.dll

É baseado no script encontrado no CodeBase no Operações de arquivo sem limitações. É um bom exemplo de como funções podem ser importadas para um 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

Essas funções são declaradas em msdn como obsoletas, mas elas ainda podem ser usadas, veja Elementos de programação de janelas obsoletas. Darei aqui a descrição das funções e parâmetros tirados diretamente daquela linha, como são descritos pelo autor do 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.


Função "Reading from File" ("Ler a partir do arquivo")

Deixe-nos considerar a função pretendida para ler a partir do arquivo. Seu parâmetro único é uma variável de string que contém o nome do arquivo. A função importada de _lopen(path,0) retorna o apontador para um arquivo aberto, para suas tarefas, é muito similar à função FileOpen() no 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);
  }

A função _lseek() tem sua análoga no MQL4 também. É FileSeek(). A função _lclose é usada para fechar arquivos, assim como a função FileClose(). A única função nova é _lread(handle, buffer, bytes) que lê a partir de um certo arquivo (o apontador que deve ser previamente recebido pela função of _lopen()) na variável faz 'buffer' do número dado de bytes. Você deveria usar uma constante string do comprimento necessário como a variável de "buffer". Nesse exemplo, podemos ver:

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

- a constante string 'char' é fornecida e tem o comprimento de um, ou seja, permite que apenas um byte seja lido nela. Ao mesmo tempo, o valor dessa constante não importa: "x", Z" e até mesmo " " (o caractere espaço). Você não irá conseguir ler mais bytes do que o que foi inicialmente definido para essa constante. Nesse caso, as tentativas de ler 2 ou mais bytes não terão sucesso. Além disso, o resultado da função _lread() é o número de bytes realmente lido. Se o arquivo tiver 20 bytes de tamanho e você tentar ler mais de 20 bytes em uma variável de 30 bytes de tamanho, a função irá retornar para 20. Se aplicarmos essa função consecutivamente, iremos mover o arquivo lendo um bloco de arquivos por outro. Por exemplo, um arquivo tem 22 bytes de tamanho. Começamos a leitura dele por blocos de 10 bytes. Então, depois de duas solicitações da função __lread(handle, buff, 10), dois bytes no final do arquivo permanecerão não lidos.


Na terceira solicitação, __lread(handle, buff, 10) irá retornar para 2, ou seja, os dois últimos bytes serão lidos. Na quarta solicitação, a função irá retornar para o valor zero - nenhum byte é lido, o apontador está no final do arquivo. É esse fato que dá base ao procedimento de leitura dos caracteres de um arquivo no ciclo:

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

Contanto que o resultado (o número de bytes lidos) seja maior do que zero, a função _lread(handle, char1, 1) é solicitada em ciclos. Como você pode ver, não há nada de complicado nessas funções. O valor do caractere lido é salvo na variável nomeada char1. Esse caractere é escrito a partir da variável string 'buffer' na próxima iteração. Depois que as operações forem finalizadas, a função, definida pelo usuário, ReadFile() retorna o conteúdo do arquivo lido nessa variável. Como você pode ver, isso não gera nenhuma dificuldade.


Função "Writing to file" ("Escrever para o arquivo")

Escrever é, de certo modo, até mais fácil do que ler. Você deve abrir um arquivo e escrever nele um arranjo de byte usando a função _lwrite (int handle, string buffer, int bytes). Aqui, handle é um apontador de arquivo obtido pela função _lopen(), o parâmetro 'buffer' é uma variável string, o parâmetro 'bytes' mostra quantos bytes deveriam ser escritos. Após a escrita o arquivo é fechado com a função _lclose(). Vamos considerar a função do 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);
  }

Entretanto, deveríamos realizar a verificação de erros. Em primeiro lugar, tentamos abrir um arquivo para escrita:

    int handle=_lopen (path,OF_WRITE);

(function _lopen() é solicitada com o parâmetro OF_WRITE).

Se a tentativa não der certo (handle < 0), ele tenta criar um arquivo com o nome específico:

       handle=_lcreat (path,0);

Se essa função também retornar um apontador negativo, então a função WriteFile() será truncada. O código de teste nessa função é claro e não necessita de mais explicações. A função de início () mais simples permite que você verifique como o script File_Read_Write.mq4 funciona.

//+------------------------------------------------------------------+
//| 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);
  }
//+------------------------------------------------------------------+

Note que a barra invertida ("\") é escrita duas vezes, embora devesse ser escrita uma vez. A questão é que alguns caracteres especiais, como o caractere de alimentação de linha ("\n") ou caractere de tabulação ('\t") devem ser escritos usando a barra invertida. Caso esqueça disso, talvez tenha problemas ao especificar o caminho na variável de teste durante a execução do programa.

Por favor, escreva duas barras invertidas consecutivas na constante string, não apenas uma. Nesse caso, o compilador irá aceitá-lo sem ambiguidade e corretamente.


Tudo funciona, mas tem uma colher de alcatrão nesse barril de mel (provérbio russo): a função de script ReadFile() opera muito devagar para arquivos grandes.


A razão para tal leitura lenta do arquivo é que lemos a informação uma por um byte (caractere). Você pode ver na figura acima que o arquivo de tamanho 280.324 bytes exigiu 103 segundos. Esse tempo é tirado lendo um caractere 280.324 vezes. Você pode checar independentemente o tempo de trabalho para o script File Read Write.mq4 anexado ao artigo. Como podemos acelerar a leitura a partir do arquivo? A resposta é forçada sobre nós - a função deve ler, digamos, 50 caracteres, não um caractere por vez. Então, a quantidade de solicitações da função _lread() será reduzida em 50 vezes. Portanto, o tempo de leitura deve ser reduzido em 50 vezes também. Vamos verificar isso.

Nova função de leitura de um arquivo em blocos de 50 bytes cada

Modifique o código nomeando a nova função como xFiles,mq4. Compile e abra para execução. Será lembrado aqui que a importação de funções de DLLs deve estar habilitada nas configurações (Ctrl+O).



Assim, o tempo de execução do script modificado xFiles.mq4 foi de 2.047 milissegundos, o que é aproximadamente 2 segundos. Divida 103 segundos (o tempo de execução do script inicial) por 2 segundos e obtenha 103 / 2 = 51,5 vezes. Assim, o tempo de execução do programa realmente mudou em aproximadamente 50 vezes, como era esperado. Como o código foi modificado para alcançar isso?

As mudanças são pequenas:

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);
  }

Por favor, note que a variável string char50' agora é inicializada pela constante de 50 caracteres (caractere "x" e 49 espaços).


Agora podemos realizar a leitura a partir do arquivo de tal forma que lemos 50 bytes (caracteres) de uma vez nesta variável:

result=_lread (handle,char50,read_size);

Aqui: read_size = 50. Claro que, é provável que o tamanho do arquivo a ser lido não seja sempre um múltiplo de 50 bytes, o que significa que haverá vezes nas quais o valor dessa função será outro que não 50. Isto é um sinal para parar o ciclo. O último bloco de caracteres, lido para a variável, será cortado na quantidade de bytes realmente lidos.


Você pode mudar o tamanho do buffer a ser lido para o tamanho de N usando a função lread(), mas não se esqueça de fazer duas modificações:

  1. configure o valor de read_size para 'N';
  2. inicialize a variável string 'char50' de comprimento da constante de N (N<256).

Bem, aceleramos a operação de leitura. A última tarefa que falta é o processamento de erros de caminho não existente ao tentar escrever um arquivo. Na função WriteFile(), uma tentativa de criar um arquivo é feita, mas a situação não é processada, na qual não há pastas contendo o caminho para o nome do arquivo. Então, precisamos de outra função -

Função de criação de pasta

A função de criação de pasta também está disponível em kernel32.dll - CreateDirectory Function (Criar função de diretório) Deveria apenas ser observado que essa função tenta criar somente pastas baixas, ela não cria todas as pastas intermediárias no caminho se não estiverem presentes. Por exemplo, se tentarmos usar essa função para criar a pasta "C:\folder_A\folder_B", a tentativa somente terá sucesso se o caminho de "C:/folder_A" já existir antes da solicitação da função. Caso contrário, folder_B não será criada. Vamos adicionar a nova função à seção de importação:

#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

O primeiro parâmetro contém o caminho para criar uma nova pasta, enquanto o segundo parâmetro, atrr[], serve para especificar permissões para a pasta a ser criada e deve ser do tipo SECURITY_ATTRIBUTES (ATRIBUTOS DE SEGURANÇA). Não forneceremos nenhuma informação para o segundo parâmetro, mas apenas passaremos o arranjo vazio nomeado 'int'. Nesse caso, a pasta a ser criada irá receber todas as permissões da pasta principal. Entretanto, antes de tentar aplicar essa função, teremos que realizar tal operação como -

Separando o caminho

Na verdade, vamos ter que criar uma pasta de quarto nível, por exemplo, como essa:

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

Aqui chamaremos a pasta folder_D de uma pasta de quarto nível. Disk 'C:' contém "folder_A", a pasta "C:\folder_A\" contém "folder_B", folder "C:\folder_A\folder_B\" contém "folder_C"; e assim por diante. Significa que temos que separar todo o caminho para o arquivo em arranjo de subpastas. Vamos nomear a função necessária como 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);   
   }   
//+------------------------------------------------------------------+

O delimitador entre pastas é o caractere "\" que tem o valor de 92 no código ANSI. A função pega o 'caminho' como seu argumento e preenche o arranjo nomeado como pasta [], passado para a função, com os nomes de caminhos encontrados, começando com o mais baixo e terminando com o mais alto. No nosso exemplo, o arranjo conterá os seguintes 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:";

Se quisermos escrever um arquivo nomeado como "C:\folder_A\folder_B\folder_C\folder_D\test.txt", podemos dividir o caminho especificado em um nome de arquivo test.txt e a estrutura de subpastas "C:\folder_A\folder_B\folder_C\folder_D" que contém esse arquivo. Se o programa não conseguir criar um arquivo nesse caminho, em primeiro lugar, deveríamos tentar criar uma pasta de nível mais baixo, C:\folder_A\folder_B\folder_C\folder_D".

Se a tentativa de criar essa pasta também não der certo, a pasta principal de "C:\folder_A\folder_B\folder_C" está ausente. Então, iremos criar pastas de níveis cada vez mais altos até que tenhamos uma mensagem sobre a realização completa bem sucedida da função CreateDirectoryA(). É por isso que precisamos de uma função que preencha o arranjo de string 'folder[]' com nomes de pasta na ordem ascendente. O primeiro índice zero contém a pasta de nível mais baixo, o diretório raiz está no último índice de arranjo.

Agora, podemos montar a própria função que cria todas as pastas intermediárias necessárias no caminho dado:

//+------------------------------------------------------------------+
//|  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);
   }

Agora falta apenas fazer uma pequena mudança na função WriteFile() considerando a possibilidade de criar uma nova pasta.

//+------------------------------------------------------------------+
//|  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;        
  }

A lógica de como a função modificada funciona é dada na figura abaixo.


Por favor, note que depois que o arquivo recém-criado foi fechado, configuramos a variável de descritor de arquivo 'handle' para um valor negativo.

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

Isso é feito para verificar o valor de 'handle' uma linha abaixo e abrir o arquivo para leitura somente se a primeira abertura falhar.


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

Isso vai nos permitir evitar situações onde múltiplos arquivos são abertos por engano e então deixados sem ser fechados. Em tais casos, o sistema operacional informa sobre exceder a quantidade máxima permitida de arquivos abertos e não nos permite abrir novos arquivos.

Vamos modificar a função de início () para verificar novas 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);
  }
//+------------------------------------------------------------------+

e abrir o scrpit xFiles.mq4 para execução.



Conclusão

Não é muito difícil usar as funções de WinAPI, mas você deve se lembrar do lado reverso de deixar a "sandbox":

Antes de abrir um aplicativo executável desconhecido com a extensão de ex4 (sem o código fonte MQL4) que requer importar funções de DLLs externas, por favor, pense nas possíveis consequências.



Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/1540

Arquivos anexados |
xFiles.mq4 (8.59 KB)
Expert Advisors baseado em sistemas de trading populares e alquimia da otimização de robô de trading (Parte II) Expert Advisors baseado em sistemas de trading populares e alquimia da otimização de robô de trading (Parte II)
Nesse artigo, o autor dá um exemplo de Expert Advisor que cumpre as exigências declaradas em Rules of the Automated Trading Championship 2008.
Modificando os parâmetros externos dos programas MQL4 sem reiniciá-los Modificando os parâmetros externos dos programas MQL4 sem reiniciá-los
O artigo descreve um método para modificar os parâmetros externos de programas MQL4 enquanto estão em funcionamento, sem reiniciá-los.
Operações de arquivo agrupadas Operações de arquivo agrupadas
Às vezes é necessário realizar operações idênticas com um grupo de arquivos. Se você tem uma lista de arquivos incluída em um grupo, então isso não é problema. Entretanto, se você precisar fazer essa lista, então surge uma questão: "Como posso fazer isso?" O artigo propõe fazer isso usando funções FindFirstFile() e FindNextFile() incluídas no kernel32.dll.
Observações de Layman: ZigZag... Observações de Layman: ZigZag...
Certamente, um pensamento visionário de trade próximo do extremo visitou o aprendizado de cada trader quando ele viu a polilinha "enigmática" pela primeira vez. Na verdade, é muito simples. Aqui está o máximo. E aqui está o mínimo. Um belo quadro no histórico. E o que é na prática? Um raio é desenhado. Deveria parecer como sendo o pico! É hora de vender. E agora abaixamos. Mas, oh não! O preço traiçoeiramente está se movendo para cima. Ah! É uma brincadeira, não um indicador. E você joga fora!