Se débarrasser des DLL auto-produites
Vous écrivez toujours vos DLLs ?
Alors nous allons vers vous !
Introduction
Il arrive toujours un moment où le langage MQL5 fonctionnel ne suffit pas pour accomplir les tâches. Dans ce cas, un programmeur MQL5 doit utiliser des outils supplémentaires. Par exemple, il est possible de travailler avec une base de données, d'utiliser des sockets de communication ou d'utiliser des fonctions du système d'exploitation. Un programmeur MQL5 doit également composer avec diverses API pour étendre les possibilités du programme MQL5 qu'il utilise. Mais pour plusieurs raisons, le programmeur ne peut pas accéder aux fonctions requises directement à partir de MQL5, car il ne connaît pas les éléments suivants :
- Comment transférer un type de données complexe (par exemple, une structure) vers une fonction API ;
- Comment utiliser le pointeur renvoyé par la fonction API.
Par conséquent, le programmeur est obligé d'utiliser un langage de programmation différent et de créer une DLL intermédiaire pour travailler avec la fonctionnalité requise. Bien que MQL5 ait la possibilité de présenter différents types de données et de les transférer vers l'API, il ne peut malheureusement pas résoudre le problème de l'extraction des données du pointeur accepté.
Dans cet article, nous allons mettre les points sur les « i » et montrer des mécanismes simples de transfert et de réception de types de données complexes et de travail avec des indices de retour.
Contenu
- Obtenir les indices
- Copie de zones de mémoire
2. Transfert des structures vers des fonctions API
- Transformation des structures à l'aide de MQL5
- Exemple de transfert de structure pour les sockets
3. Travailler avec les pointeurs des fonctions de l'API
- Exemples de fichier de mappage de mémoire,
- Exemple pour MySQL
4. Lecture de chaînes terminées par NULL à partir de fonctions API
1. La mémoire est essentielle
Comme vous le savez peut-être, toute variable (y compris les variables de types de données complexes) a son adresse spécifique, à partir de laquelle cette variable est stockée en mémoire. Cette adresse est une valeur entière de quatre octets (de type int) égale à l'adresse du premier octet de cette variable.
Et si tout est bien défini, il est possible de travailler avec cette zone mémoire. Bibliothèque en langage C (msvcrt.dll) contient la fonction memcpy. Son objectif est l'élément manquant, qui lie MQL5 et diverses bibliothèques API et offre de grandes possibilités pour un programmeur.
Faisons appel aux connaissances de nos ancêtres
La fonction Memcpy copie le nombre spécifié d'octets d'un tampon à un autre et renvoie le pointeur vers un tampon récepteur.
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
En d'autres termes, une zone de mémoire d'une taille de cnt octets commençant à l'adresse src est copiée dans la zone de mémoire commençant à l'adresse dst.
Les données situées à l'adresse src peuvent être de différents types. Il peut s'agir d'une variable char d'un octet, d'un nombre double de huit octets, d'un tableau, de toute structure et de tout volume de mémoire. Cela signifie que vous pouvez librement transmettre des données d'une zone à une autre, si vous connaissez des adresses et une taille.
Comment ça marche
Le diagramme 1 montre les tailles comparatives de certains types de données.
La fonction Memcpy est nécessaire pour copier les données d'une zone de mémoire à une autre.
La figure 2 montre la copie de quatre octets.
Dans MQL5, cela se présente comme suit.
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)); }
Il convient de noter que différents types de données (de même taille cnt) peuvent être utilisés comme zones de mémoire sur lesquelles pointent dst et src. Par exemple, le pointeur src peut faire référence à une variable double (cnt=8 octets) et dst peut faire référence au tableau ayant la taille équivalente char[8] ou int[2].
L'idée qu'un programmeur se fait de la mémoire à ce moment-là n'a aucune importance. Peu importe qu'il s'agisse d'un tableau char[8] ou d'une seule variable longue ou structure { int a1 ; int a2 ; }.
Les données de mémoire peuvent être considérées comme des données de différents types. Par exemple, il est possible de transférer un tableau de cinq octets vers la structure {int i; char c;} ou vice versa. Cette relation offre une opportunité de travailler directement avec les fonctions API.
Examinons les versions de l'application memcpy dans l'ordre défini.
Obtenir les indices
Dans l'exemple 1, nous avons montré que la fonction memcpy renvoie l'adresse de la variable dst.
Cette propriété peut être utilisée pour obtenir une adresse de n'importe quelle variable (y compris les tableaux d'autres types complexes). Pour ce faire, il suffit de spécifier la même variable en tant que paramètres source et récepteur. En cnt il est possible de transférer 0, car la copie réelle n'est pas nécessaire.
Par exemple, nous pouvons obtenir l'adresse d'une variable double et d'un tableaux court :
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); }
L'adresse reçue peut ensuite être transférée à la fonction API requise ou en tant que paramètre de structure et également en tant que paramètre de la même fonction memcpy.
Copier les tableaux
Comme vous le savez, un tableau est un morceau de mémoire dédié. La taille de la mémoire dédiée dépend du type d'éléments et de leur quantité. Par exemple, si le type d'éléments du tableau court et le nombre d'éléments est de 10, un tel tableau occupe 20 octets en mémoire (car la taille courte est de 2 octets).
Mais ces 20 octets sont également affichés sous forme de tableaux composés de 20 char ou 5 int. Dans tous les cas, ils occupent les mêmes 20 octets en mémoire.
Pour copier les tableaux, il est nécessaire de faire les choses suivantes :
- Allouer la quantité requise d'éléments (pas moins de cnt octets résultants) pour la mémoire dst ;
- Spécifiez le nombre d'octets dans cnt qui doivent être copiés à partir de src.
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. Transfert des structures vers des fonctions API
Supposons que vous deviez transférer le pointeur de structure rempli vers l'API. Le langage MQL5 fixe des limites pour la transmission des structures. Au début de l'article j'ai déclaré que la mémoire peut être présentée différemment. Cela signifie que la structure requise peut être copiée dans le type de données pris en charge par MQL5. En général, un tableau est un type qui convient aux structures. Par conséquent, nous devrons obtenir un tableau à partir d'une structure, puis transférer un tableau vers la fonction API.
La possibilité de copier la mémoire à l'aide des structures est décrite dans la section documentation. Nous ne pouvons pas utiliser la fonction memcpy, car il est impossible de transférer les structures en tant que paramètres, copier les structures est le seul moyen dans ce cas.
La figure 3 montre la représentation de la structure constituée de 5 variables de types différents et son équivalent présenté sous forme de tableau de 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; }
De cette manière simple, nous avons copié la structure dans le tableau d'octets.
Considérons la fonction de création de socket pour rendre cet exemple plus pratique.
int connect(SOCKET s, const struct sockaddr *name, int namelen);
Dans cette fonction, le deuxième paramètre est problématique, car il accepte le pointeur de la structure. Mais nous savons déjà quoi en faire. Alors, commençons.
1. Écrivons la fonction de connexion pour l'importation par la méthode autorisée dans MQL5 :
int connect(int s, uchar &name[], int namelen);
2. Observons la structure requise dans la documentation :
struct sockaddr_in { short sin_family; u_short sin_port; in_addr sin_addr; // additional 8 byte structure char sin_zero[8]; };
3. Création d'une structure avec un tableau de taille similaire :
struct ref_sockaddr_in { uchar c[2+2+8+8]; };
4. Après avoir rempli la structure sockaddr_in requise, nous la transférons dans le tableau d'octets et la soumettons en tant que paramètre de connexion.
Vous trouverez ci-dessous la section de code réalisée selon ces étapes.
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 }
Comme vous pouvez le constater, vous n'avez pas du tout besoin de créer votre DLL. Les structures sont transférées directement à l'API.
3. Travailler avec les pointeurs des fonctions API
Dans la plupart des cas, les fonctions API renvoient un pointeur vers les données : structures et tableaux. MQL5 n'est pas adapté pour extraire les données, la fonction memcpy peut être utilisée ici.
Exemple de travail avec des tableaux de mémoire à partir du fichier de mappage de la mémoire (MMF)
Lorsqu'on travaille avec MMF, on utilise la fonction qui renvoie un pointeur vers un tableau de mémoire dédié.
int MapViewOfFile(int hFile, int DesiredAccess, int OffsetHigh, int OffsetLow, int NumOfBytesToMap);
La lecture des données de ce tableau est exécutée par simple copie du nombre d'octets requis par la fonction memcpy.
L'écriture des données dans le tableau est effectuée par la même utilisation de memcpy.
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); }
Comme vous pouvez le constater, il n'est pas si difficile de travailler avec des pointeurs pour le tableau de mémoire. Et surtout, vous n'avez pas besoin de créer votre DLL supplémentaire pour cela.
Exemple de travail avec les structures renvoyées pour MySQL
L'un des problèmes urgents lorsqu'on travaille avec MySQL a été d'obtenir des données à partir de celui-ci. La fonction mysql_fetch_row renvoie le tableau de chaînes. Chaque chaîne est un tableau de champs. Ainsi, cette fonction renvoie le pointeur vers le pointeur. Notre tâche consiste à extraire toutes ces données du pointeur renvoyé.
La tâche est un peu compliquée par le fait que les champs sont de différents types de données, y compris des binaires. Cela signifie qu'il sera impossible de les présenter sous forme de tableau de chaînes. Les fonctions mysql_num_rows, mysql_num_fields, mysql_fetch_lengths sont utilisées pour obtenir des informations sur les chaînes et la taille des champs.
La figure 4 montre la structure de présentation du résultat en mémoire.
Les adresses du début de trois chaînes sont rassemblées dans le tableau. Et l'adresse du début du tableau (dans l'exemple = 94) est ce que la fonction mysql_fetch_row va retourner.
Voici l'exemple du code permettant de récupérer des données à partir d'une demande de base de données.
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. Lecture de chaînes terminées par NULL à partir de fonctions API
Certaines fonctions API renvoient le pointeur vers la chaîne mais ne nous montrent pas la longueur de cette chaîne. Dans cette situation, nous traitons des chaînes qui se terminent par zéro. Ce zéro permet de déterminer la fin de la chaîne. Cela signifie que sa taille peut être spécifiée.
La bibliothèque C (msvcrt.dll) possède déjà la fonction qui copie le contenu de la chaîne terminée par NULL du pointeur approprié vers une autre chaîne. La taille de la chaîne est définie par la fonction. Il est préférable d'utiliser un tableau d'octets comme récepteur, car les API renvoient souvent des chaînes multi-octets au lieu d'Unicode.
strcpy - copie les chaînes terminées par NULL
char *strcpy(char *dst, const char *src); dst - the pointer to the destination string src - the pointer to the Null-terminated source string
En fait, il s'agit d'un cas particulier de la fonction memcpy. Le système arrête la copie sur le zéro trouvé dans une chaîne. Cette fonction sera toujours utilisée lorsque vous travaillez avec de tels pointeurs.
Par exemple, il existe plusieurs fonctions dans l'API de MySQL qui renvoient les pointeurs vers des chaînes. Et obtenir des données à partir d'eux en utilisant strcpy est une tâche triviale.
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)); }
Conclusion
Ainsi, l'utilisation de trois mécanismes de base pour travailler avec la mémoire (copier les structures, obtenir des pointeurs et leurs données sur memcpy et obtenir des chaînes strcpy) couvre pratiquement toutes les tâches lors de l'utilisation de diverses fonctions API.
Avertissement. Il peut être dangereux de travailler avec memcpy et strcpy, à moins qu'une quantité suffisante de données n'ait été allouée au tampon du récepteur. Il faut donc faire attention à la taille des montants alloués à la réception des données.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/364
- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation