Gruppierte Dateioperationen

MetaQuotes | 28 März, 2016

Einführung

Es ist kein Problem eine Datei zu lesen oder zu schreiben. Auch wenn Sie WinAPI dafür nutzen, wie es in dem Artikel Dateioperationen über WinAPI beschrieben ist. Aber was sollen wir machen, wenn wir nicht den genauen Namen der Datei kennen, nur seine Lage in einem bestimmten Ordner und seine Erweiterung? Natürlich, wir können den erforderlichen Namen manuell eingeben, als einen Parameter, aber was sollen wir machen, wenn es zehn oder mehr solcher Dateien gibt? Wir benötigen eine Methode der gruppierten Verarbeitung der Dateien vom gleichen Typ in dem angegebenen Ordner. Diese Aufgabe kann mit den in der kernel32.dll enthaltenen Funktionen FindFirstFile(), FindNextFile() und FindClose() effizient gelöst werden.



Funktion FindFirstFile()

Die Beschreibung der Funktion ist in msdn angegeben auf: http://msdn.microsoft.com/en-us/library/aa364418(VS.85).aspx.

HANDLE WINAPI FindFirstFile(
  __in   LPCTSTR lpFileName,
  __out  LPWIN32_FIND_DATA lpFindFileData
);

Aus dieser Beschreibung ergibt sich, dass die Funktion den Deskriptor der gefundenen und die Suchbedingungen erfüllenden Datei zurückgibt. Die Suchbedingung wie in der Variable lpFileName bestimmt, die den Pfad für die Suche nach der Datei und einen möglichen Namen der Datei enthält. Diese Funktion ist praktisch, da wir die Suche über eine Maske angeben können, zum Beispiel, finde Datei durch die Maske quot;C:\Ordner\*.txt". Die Funktion wird die erste in dem Ordner von "C:\folder" gefundene Datei mit der Erweiterung txt zurückgeben.

Das zurückgegebene Ergebnis ist vom Typ 'int' in MQL4. Um den Eingabe-Parameter zu übergeben, können wir den 'string' Typ verwenden. Jetzt müssen wir klären, was wir an die Funktion als zweiten Parameter übergeben, und wie der Parameter später verarbeitet wird. Die Funktion wir in etwa wie folgt importiert:

#import "kernel32.dll"
int  FindFirstFileA(string path, .some second parameter);
#import

Hier sehen wir die bereits bekannte Bibliothek der kernel32.dll. Allerdings ist Funktionsname angegeben als FindFirstFileA(), nicht als FindFirstFile(). dies kommt durch die Tatsache, dass viele Funktionen in dieser Bibliothek zwei Versionen haben: zum Arbeiten mit Strings in Unicode, ist der Buchstabe 'W' (FindFirstFileW) an den Namen angefügt, während für die Arbeit mit ANSI der Buchstabe 'A' (FindFirstFileA) angefügt ist..

Jetzt müssen wir den zweiten Parameter klären, der beschrieben wird als:

lpFindFileData [out] - Ein Zeiger (Pointer) zu der WIN32_FIND_DATA Struktur der Informationen über eine gefundene Datei oder ein Verzeichnis empfängt.

Es bedeutet, das es der Zeiger zu einer bestimmten Struktur von WIN32_FIND_DATA ist. In diesem Fall ist die Struktur ein bestimmter Bereich in dem PC RAM. Der Zeiger zu diesem Bereich (Adresse) wird an die Funktion übergeben. Wir können Speicher in MQL4 mit einem Datenarray zuordnen. Der Zeiger wird mit dem '&' Zeichen bestimmt. Wir müssen nur die Größe des erforderlichen Speichers in Byte kennen, um den Zeiger an ihn zu übergeben. Im Folgenden ist die Struktur-Beschreibung.

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, 

In MQL4 gibt es keine Typen DWORD, TCHAR oder FILETIME. DWORD ist bekannt, dass es 4 Byte belegt, wie int in MQL4, TCHAR hat eine interne Darstellung von einem Byte. Um die Gesamtgröße der Struktur WIN32_FIND_DATA in Bytes zu berechnen, müsse wir nur klären, was FILETIME ist.

typedef struct _FILETIME {
  DWORD dwLowDateTime;
  DWORD dwHighDateTime;
} FILETIME

Es stellt sich heraus, dass FILETIME zwei DWORDs enthält, was bedeutet, dass DWORD 8 Bytes belegt. Tabellieren wir das alles:

Typ Größe in Bytes
DWORD 4
TCHAR 1
FILETIME 8

Jetzt können wir die Größe der Struktur von WIN32_FIND_DATA berechnen und visualisieren was und wo wir es gefunden haben.

Typ
Größe in Bytes Hinweis
dwFileAttributes 4Dateiattribute
ftCreationTime 8Datei/Ordner Zeitpunkt der Erstellung
ftLastAccessTime 8Zeitpunkt des letzten Zugriffs
ftLastWriteTime 8Zeitpunkt letztes Schreiben
nFileSizeHigh 4 Maximale Größe in Bytes
nFileSizeLow 4 Minimale Größe in Bytes
dwReserved0 4 Ist in der Regel nicht definiert und nicht verwendet
dwReserved1 4 Ist reserviert für die Zukunft
cFileName[MAX_PATH] 260 (MAX_PATH = 260)
Dateiname
cAlternateFileName[14] 14Alternativer Name in dem 8.3 Format

Also ist die Gesamtgröße der Stuktur: 4 + 8 + 8 + 8 + 4 + 4 + 4 +4 + 260 +14 = 318 Bytes.

Wie Sie aus der obigen Abbildung erkennenukönnen, beginnt der Dateiname mit dem 45ten Byte, die vorhergehenden 44 Bytes enthalten verschiedene zusätzliche Informationen. Es ist erforderlich an die Funktion FindFirstFile() eine Struktur mit einer MQL Größe als 310 Byte zu übergeben, als zweiten Parameter. Es wäre am komfortabelsten ein Array des 'int' Typs zu verwenden, das eine Größe nicht kleiner als das benötigte haben sollte. Sie dividieren 318 durch 4 (weil die interne Darstellung des 'int' Typs 4 Byte ist), erhalten 79,4, runden es auf den nächsten größeren Integer und sehen, dass wir ein Array aus 80 Elementen benötigen.

Der Funktion-Import selbst wird nun wie folgt aussehen:

#import "kernel32.dll"
int  FindFirstFileA(string path, int & answer[]);
#import

Hier verwenden wir die Version der Funktion mit dem Buchstaben 'A' am Ende des Namens, FindFirstFileA(), für ANSI-Kodierung. Das 'answer' Array wird durch einen Link übergeben und die dazu mit der Struktur von WIN32_FIND_DATA gefüllt werden. Ein beispielhafter Aufruf:

   int win32_DATA[80];
   int handle = FindFirstFileA(TerminalPath() + "\experts\*.mq4",win32_DATA);


Funktionen FindNextFileA() und FindClose()

Die Funktion FindNextFileA() empfängt , als ersten Parameter, den 'handle' der vorläufig durch die Funktion FindNextFileA() oder einen anderen, früheren Aufruf zu FinNextFileA() erhaltene Datei. Der zweite Parameter ist der gleiche. Die Funktion FindClose() schließt einfach die Suche. Aus diesem Grund wird das vollständige Erscheinungsbild des Datenimports durch Funktionen wie folgt sein:

#import "kernel32.dll"
int  FindFirstFileA(string path, int & answer[]);
bool FindNextFileA(int handle, int & answer[]);
bool FindClose(int handle);
#import

Jetzt müssen wir lediglich erfahren, wie man den Namen einer in das Array 'answer[]' geschrieben Datei extrahieren.



Abrufen des Dateinamen

Der Dateiname ist in dem Array beginnen ab dem 45. bis zu dem 304. Byte enthalten. Der 'int' Typ enthält 4 Byte, also enthält das Array 4 Zeichen, wenn wir davon ausgehen, dass das Array mit Zeichen ausgefüllt ist. Also, um auf das erste Zeichen im Dateinamen zu verweisen, sollten wir 44/4 = 11 Elemente von dem 'answer[]' Elemente des 'answer[]' Array überspringen. Der Dateiname ist innerhalb eie Kette aus 65 (260/4=65) Array Elementen, beginnend bei 'answer [11]' (Indexierung beginnt mit Null) und endend mit 'answer [76'].

So können wir aus dem Array 'answer[]' den Dateinamen in Blöcken von 4 Zeichen erhalten. Die 'int' Zahl stellt eine Reihe von 32 Bits dat, die ihrerseits 4 Blöcke mit je 8 Bits darstellt.

Das jüngste Byte steht auf der rechten Seite, das älteste auf der linken. Bits sind in aufsteigender Reihenfolge nummeriert, das heißt, Bits von dem 1ten bis zu dem 8ten ergeben das jüngste Byte. Wir können die benötigten Bytes mit bitweisen Operationen entnehmen. Um den Wert des jüngste Byte zu erhalten, müssen wir die Nullwerte aller Bits füllen, von dem 9ten bis zu dem 32ten. Dies wird ausgeführt mit der logischen Operation AND.

int a = 234565;
int b = a & 0x000000FF;

Hier ist 0x000000FF das 32-bit integer das Null-Werte an allen Stellen hat, beginnend von dem 9ten, während die Stellen 1 bis 8 gleich Eins sind. Somit wird die erhaltene Anzahl b nur ein Byte (das jüngste) Byte der Zahl a enthalten. Wir ändern das erhaltene Byte (den Zeichencode) in einen ein-Zeichen String mit der Funktion CharToStr().

Ok, wir haben das erste Zeichen erhalten. Wie können wir das nächste erhalten? Sehr einfach: Wir machen eine bitweise 8-Bit Verschiebung nach rechts, und das zweite Bit ersetzt das jüngste Bit. Dann werden wir die bereits bekannte logische AND Operation anwenden.

int a = 234565;
int b = (a >>8) & 0x000000FF;

Wie Sie sich vorstellen können, wird das dritte Bit durch Verschiebung von 16-Bit erhalten, während das älteste durch die Verschiebung von 24-Bit erhalten wird. Somit können wir 4 Zeichen aus einem Element des Array vom 'int' Typ entnehmen. Im Folgenden wird gezeigt, wie Sie die ersten 4 Zeichen des Dateinamen aus dem 'answer[]' Array erhalten:

   string text="";
   
   int pos = 11;
   int curr = answer[pos];
      {
      text = text + CharToStr(curr & 0x000000FF)
         +CharToStr(curr >> 8 & 0x000000FF)
         +CharToStr(curr >> 16 & 0x000000FF)
         +CharToStr(curr >> 24 & 0x000000FF);
      }
    Print("text = ", text);

Erstellen wir eine separate Funktion, die einen Text-String von dem übergebenen Array mit dem Namen 'buffer' zurückgibt.

//+------------------------------------------------------------------+
//|  read text from buffer                                           |
//+------------------------------------------------------------------+ 
string bufferToString(int buffer[])
   {
   string text="";
   
   int pos = 10;
   for (int i=0; i<64; i++)
      {
      pos++;
      int curr = buffer[pos];
      text = text + CharToStr(curr & 0x000000FF)
         +CharToStr(curr >> 8 & 0x000000FF)
         +CharToStr(curr >> 16 & 0x000000FF)
         +CharToStr(curr >> 24 & 0x000000FF);
      }
   return (text);
   }

Somit ist das Problem des Abrufens des Dateinamens von der Struktur gelöst.



Die Liste Aller Expert Advisors mit Ihrem Quellcode erhalten

Ein einfaches Skript demonstriert die Eigenschaften der obigen Funktionen:

//+------------------------------------------------------------------+
//|                                                CheckFindFile.mq4 |
//|                      Copyright © 2008, MetaQuotes Software Corp. |
//|                                       https://www.metaquotes.net/ |
//+------------------------------------------------------------------+
#property copyright "Copyright © 2008, MetaQuotes Software Corp."
#property link      "https://www.metaquotes.net/"
 
#property show_inputs
 
#import "kernel32.dll"
int  FindFirstFileA(string path, int& answer[]);
bool FindNextFileA(int handle, int& answer[]);
bool FindClose(int handle);
#import
 
 
//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   int win32_DATA[79];
   
   int handle = FindFirstFileA(TerminalPath() + "\experts\*.mq4",win32_DATA);
   Print(bufferToString(win32_DATA));
   ArrayInitialize(win32_DATA,0);
 
   while (FindNextFileA(handle,win32_DATA))
      {
      Print(bufferToString(win32_DATA));
      ArrayInitialize(win32_DATA,0);
      }
 
   if (handle>0) FindClose(handle);
   
//----
   return(0);
  }
  
//+------------------------------------------------------------------+
//|  read text from buffer                                           |
//+------------------------------------------------------------------+ 
string bufferToString(int buffer[])
   {
   string text="";
   
   int pos = 10;
   for (int i=0; i<64; i++)
      {
      pos++;
      int curr = buffer[pos];
      text = text + CharToStr(curr & 0x000000FF)
         +CharToStr(curr >> 8 & 0x000000FF)
         +CharToStr(curr >> 16 & 0x000000FF)
         +CharToStr(curr >> 24 & 0x000000FF);
      }
   return (text);
   }  
//+------------------------------------------------------------------+

Hier, nach den FindFirstFileA() und FindNextFileA() Funktionsaufrufen, ist das Array win32_DATA (Struktur WIN32_FIND_DATA) geändert zu einem auf einen "leer (empty)" Status geändert, nämlich, alle Elemente des Array sind mit Nullen gefüllt:

   ArrayInitialize(win32_DATA,0);

Wenn wir dies nicht machen, kann es passieren, dass die "Fetzen" des Dateinamens des vorherigen Aufrufs die Verarbeitung durchdringen, und wir nur Geschwafel erhalten.



Beispiel-Umsetzung: Erstellen von Quellcode Sicherungen

EI einfaches Beispiel, das praktische Merkmale des obigen veranschaulicht, ist die Erstellung von Quellcode-Sicherungen in einem speziellen Verzeichnis. Dazu müssen wir die Funktionen dieses Artikels mit denen in dem Artikel Dateioperationen über WinAPI kombinieren. Wir erhalten dann das einfache, im Folgenden angegebene Skript backup.mq4. Sie finden den vollständigen Code in der angehangenen Datei. Hier ist nur seine start() Funktion, die alle beschriebenen Funktionen beider Artikel verwendet:

//+------------------------------------------------------------------+
//| script program start function                                    |
//+------------------------------------------------------------------+
int start()
  {
//----
   string expert[1000]; // must be enough 
   string EAname="";    // EA name
   int EAcounter = 0;   // EAs counter   
   int win32_DATA[80];  
   
   int handle = FindFirstFileA(TerminalPath() + "\experts\*.mq4",win32_DATA);
   EAname = bufferToString(win32_DATA);
   expert[0] = EAname;
   ArrayInitialize(win32_DATA,0);
   
   int i=1;
   while (FindNextFileA(handle,win32_DATA))
      {
      EAname = bufferToString(win32_DATA);
      expert[i] = EAname;
      ArrayInitialize(win32_DATA,0);
      i++;
      if (i>=1000) ArrayResize(expert,2000); // now it will surely be enough
      }
 
   ArrayResize(expert, i);
   int size = i;
   
   if (handle>0) FindClose(handle);
 
   for (i = 0 ; i < size; i++) 
      {
      Print(i,":  ",expert[i]);   
      string backupPathName = backup_folder + "experts\\" + expert[i];
      string originalName = TerminalPath() + "\\experts\\" + expert[i];
      string buffer=ReadFile(originalName);
      WriteFile(backupPathName,buffer);   
      }
   if (size > 0 ) Print("There are ",size," files were copied to folder "+backup_folder+"experts\\");   
//----
   return(0);
  }
//+------------------------------------------------------------------+



Fazit

Es wurde gezeigt, wie man Operationen mit einer Gruppe von Dateien des gleichen Typs durchführt. Sie können lesen und Dateien mit den obigen Funktionen verarbeiten und Ihre eigene Logik umsetzen. Sie können zeitnahe Sicherungen mit einem Expert Advisor machen oder zwei Sätze Dateien in verschiedenen Ordner synchronisieren, Kurse aus Datenbanken importieren, und so weiter. Es ist empfohlen die Verwendung von DLLs zu steuern und nicht die Verbindung zu Bibliotheken von Drittanbieter-Dateien mit der Erweiterung ex4 zu aktivieren. Sie müssen sicherstellen, dass das von Ihnen gestartete Programm nicht die Daten auf Ihrem PC beschädigen wird.