Den Ballast selbstgemachter "dynamischer Programmbibliotheken" loswerden
Schreiben Sie immer noch ihre eigenen DLL?
Dann sind Sie hier richtig!
Einleitung
Irgendwann ist es immer so weit, dass der Funktionsumfang der Programmiersprache dem MQL5-Programmierer nicht mehr genügt und er sich anderer Hilfsmittel bedienen muss. Sei es, dass er mit einer Datenbank arbeiten oder Programmschnittstellen verwenden oder auf Funktionen des jeweiligen Betriebssystems zurückgreifen muss. Zur Erweiterung der Möglichkeiten des verwendeten MQL5-Programms ist er gezwungen, sich mit verschiedenen Programmierschnittstellen (API) zu befassen. Aber aus verschiedenen Gründen kann er nicht unmittelbar aus MQL5 über die benötigten Funktionen verfügen, da er weder weiß,
- wie die komplexe Datenart (z. B. eine Struktur) in eine API-Funktion übersetzt wird, noch
- wie mit dem von der API-Funktion ausgegebenen Adresszeiger umzugehen ist.
Deshalb ist er genötigt, sich einer anderen Programmiersprache zu bedienen sowie eine zwischengelagerte DLL mit dem für die Arbeit erforderlichen Funktionsumfang anzulegen. Obwohl MQL5 die Möglichkeit bietet, unterschiedliche Datenarten abzubilden und sie an eine Programmierschnittstelle (API) weiterzugeben, ist diese Programmiersprache nicht in der Lage, das Problem des Auszugs von Daten aus einer zugelassenen Speicheradresse zu lösen.
In diesem Beitrag wollen wir auf jedes „i“ einen Punkt setzen und einige einfache Mechanismen für die Übertragung und den Empfang komplexer Datenarten sowie für die Arbeit mit den ausgegebenen Speicheradressen vorstellen.
Inhalt
1. Speicherplatz - unser Ein und Alles
- Erfassen der Speicheradressen
- Kopieren der Speicherbereiche
2. Übertragung der Gerüste/Strukturen an die API-Funktionen
- Umwandeln der Gerüste/Strukturen mit den in MQL5 vorhandenen Mitteln
- Beispielhafte Übertragung eines Datengerüsts für Programmschnittstellen
3. Arbeiten mit Speicheradressen aus API-Funktionen
- Beispielhafte Speicherplatzzuordnungsdateien (Memory Mapping File)
- Ein Beispiel für MySQL
4. Auslesen der auf NULL endenden Zeichenfolgen aus API-Funktionen
1. Speicherplatz - unser Ein und Alles
Bekanntermaßen verfügt jede Variable (einschließlich solcher komplexer Datenarten) über eine konkrete Adresse, unter der sie im Speicher abgelegt wird. Bei dieser Adresse handelt es sich um eine ganze Vierbyte-Zahl (der Art „int“), deren Wert gleich der Adresse des ersten Bytes dieser Adresse ist.
Sobald alles vollständig festgelegt worden ist, kann mit besagtem Speicherbereich gearbeitet werden. In der Bibliothek der Programmiersprache C (msvcrt.dll) gibt es die Funktion memcpy. Sie ersetzt das fehlende Element, durch das MQL5 und die unterschiedlichen API-Bibliotheken zu einem Ganzen verbunden werden. Zudem verschafft sie dem Programmierer umfassende Möglichkeiten.
Greifen wir auf das Wissen unserer Ahnen zurück
Die Funktion Memcpy kopierte die angegebene Anzahl Bytes aus einem Zwischenspeicher (Puffer) in einen anderen und gibt die Speicheradresse für den empfangenden Zwischenspeicher aus.
void *memcpy(void *dst, const void *src, int cnt); dst - pointer to the receiver buffer src - pointer to the source buffer cnt - number of bytes for copying
Anders gesagt: der mit der Adresse src beginnende cnt-Byte große Speicherbereich wird in den mit der Adresse dst beginnenden Speicherbereich kopiert.
Die unter der Adresse src befindlichen Daten können ganz unterschiedlich sein. Es kann sich um ein Byte umfassende Variablen der Art char, um eine aus acht Bytes bestehende Ziffer der Art double, ein Datenfeld (Array), irgendein Datengerüst oder einen beliebigen Speicherbereich handeln. Das bedeutet, dass , wenn die Adresse und die Größe bekannt sind, die Übertragung von Daten aus einem Speicherbereich in einen anderen problemlos ausgeführt werden kann.
Wie funktioniert das?
In der Abbildung 1 werden die ungefähren Größen einiger Datenarten im Vergleich zueinander dargestellt.
Der Zweck der Funktion Memcpy besteht darin, Daten aus einem Speicherbereich in einen anderen zu kopieren.
In Abbildung 2 ist der Kopiervorgang für vier Bytes zu sehen.
In MQL5 sieht das wie folgt aus:
Example 1. Using memcpy #import "msvcrt.dll" int memcpy(int &dst, int &src, int cnt); #import//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { int dst, src=4, cnt=sizeof(int); int adr=memcpy(dst, src, cnt); Print("dst value="+string(dst)+" Address dst="+string(adr)); }
Es darf nicht vergessen werden, dass es sich bei dem Speicherplatz, der von dst und src ausgewiesen wird, um völlig andere Datenarten handeln kann (Hauptsache ihre Größe entspricht der von cnt). Die Speicheradresse src kann sich beispielsweise auf eine Variable der Art double (mit cnt = 8 Byte) beziehen, während dst auf ein Datenfeld vergleichbarer Größe, char[8] oder int[2], verweist.
Für den Speicherplatz ist es unerheblich, welche Vorstellung der Programmierer in diesem Augenblick von ihm hat. Gleich, ob es sich um das Datenfeld (Array) char[8] oder lediglich um eine Variable der Art long oder ein Datengerüst der Art { int a1; int a2; } handeln soll.
Das heißt, es können nicht nur gleichartige Daten untereinander kopiert werden. So können wir zum Beispiel ein Datenfeld aus fünf Bytes in ein Datengerüst der Art {int i; char c;} übertragen und umgekehrt. Eben diese Beziehung ermöglicht die unmittelbare Arbeit mit API-Funktionen.
Wir betrachten die Verwendungsmöglichkeiten der Funktion memcpy der Reihe nach.
Erfassen der Speicheradressen
Im ersten Beispiel haben wir gezeigt, dass die Funktion memcpy die Adresse der Variablen dst ausgibt.
Diese Eigenschaft kann zur Erfassung der Adresse jeder beliebigen Variablen (einschließlich derjenigen von Datenfeldern und weiteren komplexen Datenarten) genutzt werden. Es reicht, ein und dieselbe Variable sowohl als Quelle als auch als Empfänger auszuweisen. An cnt können wir „0“ weitergeben, da der tatsächliche Kopiervorgang nicht zwingend ausgeführt werden muss.
Wir erhalten beispielsweise die Adresse für eine Variable der Art double und ein Datenfeld der Art short:
Example 2. Getting pointers to the variable #import "msvcrt.dll" int memcpy(short &dst[], short &src[], int cnt); int memcpy(double &dst, double &src, int cnt); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { short src[5]; //--- getting src array address (i.е., the address of the first element) int adr=memcpy(src, src, 0); double var; //--- getting var variable address adr=memcpy(var, var, 0); }
Die erfasste Adresse kann anschließend entweder als Gerüstparameter oder als Parameter eben derselben Funktion memcpy an die gewünschte API-Funktion weitergegeben werden.
Kopieren von Datenfeldern
Wie wir wissen, handelt es sich bei einem Datenfeld (Array) um einen abgegrenzten Speicherplatzbereich. Die Größe des abgegrenzten Speicherplatzes hängt von der Art und der Anzahl der zu speichernden Elemente ab. Mit 10 Elementen der Art short nimmt ein solches Datenfeld beispielsweise Speicherplatz im Umfang von 20 Byte ein (da short eine Größe von 2 Byte aufweist).
Diese 20 Byte könnten jedoch auch von einem Datenfeld aus 20 Elementen der Art char oder aus 5 der Art int belegt sein. Wie dem auch sei, im Speicher sind 20 Byte belegt.
Zum Kopieren der Datenfelder sind erforderlich:
- die Abgrenzung der erforderlichen Anzahl Elemente für den Speicher dst (mindestens die Anzahl der resultierenden cnt Byte);
- die Festlegung der Anzahl der aus src zu kopierenden Bytes in cnt.
Example 3. Copying the arrays #import "msvcrt.dll" int memcpy(double &dst[], double &src[], int cnt); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { double src[5]; //--- calculating the number of bytes!!! int cnt=sizeof(double)*ArraySize(src); double dst[]; ArrayResize(dst, 5); //--- the array has been copied from src to dst memcpy(dst, src, cnt); }
2. Übertragung der Gerüste/Strukturen an die API-Funktionen
Angenommen, wir müssen ein gefülltes Datengerüst an eine API-Funktion weitergeben. Die Programmiersprache MQL5 erlegt uns bei der Übertragung von Datengerüsten Beschränkungen auf. Zu Beginn dieses Beitrages wurde festgestellt, dass es unterschiedliche Vorstellungen von dem Speicherplatz geben kann. Das bedeutet, dass jedes gewünschte Gerüst in jede von MQL5 unterstützte Datenart kopiert werden kann. Üblicherweise handelt es sich bei der für Datengerüste oder -strukturen geeigneten Art um ein Datenfeld (Array). Deshalb müssen wir aus dem Gerüst zunächst ein Datenfeld machen, das wir dann an die API-Funktion weitergeben.
In der Rubrik Dokumente wird eine Variante zum Kopieren von Speicherinhalten mittels Datengerüsten vorgestellt. Da wir hier Gerüste nicht als Parameter übertragen können, kommt die Verwendung der Funktion memcpy nicht infrage, und uns bleibt nur das Kopieren der Gerüste.
In der Abbildung 3 sehen wir die Darstellung eines Datengerüstes aus 5 Variablen unterschiedlicher Art sowie dessen Entsprechung in Form eines Datenfeldes der Art char.
Example 4. Copying the structures by means of MQL5 struct str1 { double d; // 8 bytes long l; // 8 bytes int i[3]; // 3*4=12 bytes }; struct str2 { uchar c[8+8+12]; // str1 structure size }; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { str1 src; src.d=-1; src.l=20; //--- filling the structure parameters ArrayInitialize(src.i, 0); str2 dst; //--- turning the structure into the byte array dst=src; }
Auf so unspektakuläre Weise haben wir das Gerüst in ein Datenfeld kopiert.
Um ein etwas „lebendigeres“ Beispiel zu erhalten, betrachten wir die Funktion zur Erstellung einer Programmschnittstelle (Socket).
int connect(SOCKET s, const struct sockaddr *name, int namelen);
In dieser Funktion liegt das Problem in dem zweiten Parameter, der die Speicheradresse des Datengerüstes aufnimmt. Aber wir wissen ja bereits, wie damit umzugehen ist. Also los.
1. Für den Import schreiben wir die Funktion „connect“ in der in MQL5 verfügbaren Form:
int connect(int s, uchar &name[], int namelen);
2. Wir sehen uns die erforderliche Funktion in den „Dokumenten“ an:
struct sockaddr_in { short sin_family; u_short sin_port; in_addr sin_addr; // additional 8 byte structure char sin_zero[8]; };
3. Wir legen ein Gerüst mit einem Datenfeld vergleichbarer Größe an:
struct ref_sockaddr_in { uchar c[2+2+8+8]; };
4. Nach Ausfüllen des erforderlichen Datengerüstes sockaddr_in übersetzen wir es in ein Byte-Datenfeld, das wir als Parameter an die Importfunktionconnect weitergeben.
Es folgt der anhand dieser Schritte angelegte Programmcode.
Example 5. Referring of the client socket to the server #import "Ws2_32.dll" ushort htons(ushort hostshort); ulong inet_addr(char &cp[]); int connect(int s, char &name[], int namelen); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- connecting the host after the socket initialization char ch[]; StringToCharArray("127.0.0.1", ch); //--- preparing the structure sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(1000); //--- copying the structure to the array ref_sockaddr_in ref=addrin; //--- connecting the host res=connect(asock, ref.c, sizeof(addrin)); //--- further work with the socket }
Wie wir sehen, muss für die Arbeit mit Programmstellen keine einzige dynamische Programmbibliothek erstellt werden. Datengerüste können direkt an die Programmierschnittstelle übertragen werden.
3. Arbeiten mit Speicheradressen aus API-Funktionen
Häufig werden von den API-Funktionen die Speicheradressen von Daten in Form von Datengerüsten oder Datenfeldern ausgegeben. Mit den Möglichkeiten von MQL5 können diese Daten nicht extrahiert werden, hier hilft die Funktion memcpy.
Beispiel für die Arbeit mit Speicherdatenfeldern aus einer Speicherplatzzuordnungsdatei (MMF)
Bei der Arbeit mit einer MMF kommt eine Funktion zum Einsatz, die die Speicherplatzadresse zu dem angegebenen Speicherdatenfeld ausgibt.
int MapViewOfFile(int hFile, int DesiredAccess, int OffsetHigh, int OffsetLow, int NumOfBytesToMap);
Das Auslesen der Daten aus diesem Datenfeld erfolgt durch einfaches Kopieren der erforderlichen Anzahl Bytes mithilfe der Funktion memcpy.
Zur Aufzeichnung der Daten in einer Datei wird dieselbe Funktion in ähnlicher Weise eingesetzt.
Example 6. Recording and reading data from MMF memory #import "kernel32.dll" int OpenFileMappingW(int dwDesiredAccess, int bInheritHandle, string lpName); int MapViewOfFile(int hFileMappingObject, int dwDesiredAccess, int dwFileOffsetHigh, int dwFileOffsetLow, int dwNumberOfBytesToMap); int UnmapViewOfFile(int lpBaseAddress); int CloseHandle(int hObject); #import "msvcrt.dll" int memcpy(uchar &Destination[], int Source, int Length); int memcpy(int Destination, int &Source, int Length); int memcpy(int Destination, uchar &Source[], int Length); #import #define FILE_MAP_ALL_ACCESS 0x000F001F //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- opening the memory object int hmem=OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, "Local\\file"); //--- getting pointer to the memory int view=MapViewOfFile(hmem, FILE_MAP_ALL_ACCESS, 0, 0, 0); //--- reading the first 10 bytes from the memory uchar src[10]; memcpy(src, view, 10); int num=10; //--- recording the 4 byte int number to the memory beginning memcpy(view, num, 4); //--- unmapping the view UnmapViewOfFile(view); //--- closing the object CloseHandle(hmem); }
Wie wir sehen, gibt es keinerlei Schwierigkeiten bei der Arbeit mit den Speicheradressen in Bezug auf das Speicherdatenfeld. Und die Hauptsache: wir müssen dazu keine zusätzlichen eigenen dynamischen Programmbibliotheken (DLL) anlegen.
Beispiel für die Arbeit mit für MySQL ausgegebenen Datengerüsten
Eine der drängendsten Schwierigkeiten bei der Arbeit mit MySQL bestand darin, Daten aus MySQL zu beziehen. Die Funktion mysql_fetch_row liefert ein Datenfeld mit Zeichenfolgen (Strings). Wobei jede Zeichenfolge wiederum ein Datenfeld aus Feldern ist. Somit gibt diese Funktion die Speicheradressen von Speicheradressen aus. Unsere Aufgabe besteht darin, als diese Daten aus der erhaltenen Speicheradresse zu extrahieren.
Das wird dadurch etwas erschwert, dass es sich bei den Feldern um unterschiedliche Datenarten einschließlich binärer handelt. Das bedeutet, es nicht gelingen wird, sie als Zeichenfolgendatenfeld der Art string wiederzugeben. Zum Abrufen der Angaben zu den Zeichenfolgen und Feldgrößen gibt es die Funktionen mysql_num_rows, mysql_num_fields und mysql_fetch_lengths.
Die Abbildung 4 zeigt den Aufbau der Abbildung des Ergebnisses im Speicher.
Die Adressen des Anfangs dreier Zeichenfolgen sind in einem Datenfeld versammelt. Und die Adresse des Anfangs dieses Datenfeldes (in unserem Beispiel 94) ist das, was die Funktion mysql_fetch_row ausgibt.
Es folgt ein Beispielcode für den Bezug der Daten aus der Datenbankabfrage.
Example 7. Getting data from MySQL #import "libmysql.dll" int mysql_real_query(int mysql, uchar &query[], int length); int mysql_store_result(int mysql); int mysql_field_count(int mysql); uint mysql_num_rows(int result); int mysql_num_fields(int result); int mysql_fetch_lengths(int result); int mysql_fetch_row(int result); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- ... preliminarily initialized mysql data base //--- request for getting all the strings from the table string query="SELECT * FROM table"; uchar aquery[]; StringToCharArray(query, aquery); //--- sending the request err=mysql_real_query(mysql, aquery, StringLen(query)); int result=mysql_store_result(mysql); //--- in case it contains the strings if (result>0) { ulong num_rows=mysql_num_rows(result); int num_fields=mysql_num_fields(result); //--- getting the first string pointer int r=0, row_ptr=mysql_fetch_row(result); while(row_ptr>0) { //--- getting the pointer to the current string columns lengths int len_ptr=mysql_fetch_lengths(result); int lens[]; ArrayResize(lens, num_fields); //--- getting the sizes of the string fields memcpy(lens, len_ptr, num_fields*sizeof(int)); //--- getting the data fields int field_ptr[]; ArrayResize(field_ptr, num_fields); ArrayInitialize(field_ptr, 0); //--- getting the pointers to the fields memcpy(field_ptr, row_ptr, num_fields*sizeof(int)); for (int f=0; f<num_fields; f++) { ArrayResize(byte, lens[f]); ArrayInitialize(byte, 0); //--- copy the field to the byte array if (field_ptr[f]>0 && lens[f]>0) memcpy(byte, field_ptr[f], lens[f]); } r++; //--- getting the pointer to the pointer to the next string row_ptr=mysql_fetch_row(result); } } }
4. Auslesen auf NULL endender Zeichenfolgen aus API-Funktionen
Einige API-Funktionen geben die Speicheradresse einer Zeichenfolge aus, ohne uns über die Länge dieser Zeichenfolge zu informieren. In einer solchen Situation haben wir es mit Zeichenfolgen zu tun, die auf Null enden. Anhand dieser Null wird auch das Ende der jeweiligen Zeichenfolge bestimmt. Das bedeutet, dass ihre Länge angegeben werden kann.
In der Bibliothek C (msvcrt.dll) ist bereits eine Funktion vorhanden, die den Inhalt auf NULL endender Zeichenfolgen aus diesen in andere Zeichenfolgen kopiert. Dabei bestimmt die die Ausgangszeichenfolge ihre Größe selbst. Es ist es besser, ein Byte-Datenfeld als Empfänger zu verwenden, da die Programmierschnittstelle häufig Zeichenfolgen in Multibyte ausgibt und nicht in Unicode.
strcpy kopiert auf NULL endenden Zeichenfolgen
char *strcpy(char *dst, const char *src); dst - the pointer to the destination string src - the pointer to the Null-terminated source string
Eigentlich handelt es sich um einen Sonderfall der Funktion memcpy. Denn das System selbst bricht den Kopiervorgang bei der gefundenen Null in der Zeichenfolge ab. Diese Funktion muss stets bei der Arbeit mit eben diesen Speicheradressen verwendet werden.
In der Programmierschnittstelle von MySQL zum Beispiel gibt es einige Funktionen, die Speicheradressen von Zeichenfolgen ausgeben. Auch der Abruf von Daten aus ihnen ist mithilfe der Funktion strcpy ein Kinderspiel.
Example 8. Getting the strings from the pointers #import "libmysql.dll" int mysql_init(int mysql); int mysql_real_connect(int mysql, uchar &host[], uchar &user[], uchar &password[], uchar &DB[], uint port, uchar &socket[], int clientflag); int mysql_get_client_info(); int mysql_get_host_info(int mysql); int mysql_get_server_info(int mysql); int mysql_character_set_name(int mysql); int mysql_stat(int mysql); #import "msvcrt.dll" int strcpy(uchar &dst[], int src); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { uchar byte[]; ArrayResize(byte, 300); int ptr; string st; //--- pointer to the string ptr=mysql_get_client_info(); if (ptr>0) strcpy(byte, ptr); Print("client_info="+CharArrayToString(byte)); //--- initializing the base int mysql=mysql_init(mysql); //--- transferring the strings to the byte arrays uchar ahost[]; StringToCharArray("localhost", ahost); uchar auser[]; StringToCharArray("root", auser); uchar apwd[]; StringToCharArray("", apwd); uchar adb[]; StringToCharArray("some_db", adb); uchar asocket[]; StringToCharArray("", asocket); //--- connecting the base int rez=mysql_real_connect(mysql, ahost, auser, apwd, adb, port, asocket, 0); //--- determining the connection and the base status ptr=mysql_get_host_info(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_host_info="+CharArrayToString(byte)); ptr=mysql_get_server_info(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_server_info="+CharArrayToString(byte)); ptr=mysql_character_set_name(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_character_set_name="+CharArrayToString(byte)); ptr=mysql_stat(mysql); if (ptr>0) strcpy(byte, ptr); Print("mysql_stat="+CharArrayToString(byte)); }
Fazit
Somit sind durch die Verwendung der drei Grundverfahren für die Arbeit mit Speicherplatz, das Kopieren von Datengerüsten, der Bezug von Speicheradressen und ihren Daten mittels memcpy sowie der Bezug von Zeichenfolgen mit strcpy, imgrunde alle bei der Arbeit mit den unterschiedlichen API-Funktionen anfallenden Aufgaben abgedeckt.
Achtung. Die Arbeit mit den Funktionen memcpy und strcpy ist nicht ganz ungefährlich, wenn dem empfangenden Zwischenspeicher keine ausreichende Datenmenge zugeordnet wird. Achten Sie deshalb aufmerksam auf die Größe der für den Dateneingang ausgesonderten Bereiche.
Übersetzt aus dem Russischen von MetaQuotes Ltd.
Originalartikel: https://www.mql5.com/ru/articles/364
- Freie Handelsapplikationen
- Über 8.000 Signale zum Kopieren
- Wirtschaftsnachrichten für die Lage an den Finanzmärkte
Sie stimmen der Website-Richtlinie und den Nutzungsbedingungen zu.