English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Se débarrasser des DLL auto-produites

Se débarrasser des DLL auto-produites

MetaTrader 5Exemples | 12 janvier 2022, 17:11
195 0
---
---


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

1. La mémoire est essentielle

  • 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.

Tailles des différents types de données dans MQL5


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.

Exemple de copie de 4 octets à l'aide de memcpy

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.

Présentation de la structure composé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.

La structure de présentation du résultat de la demande en mémoire

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.

Présentation de la chaîne terminée par NULL en mémoire

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

Qui est qui dans MQL5.community ? Qui est qui dans MQL5.community ?
Le site MQL5.com se souvient très bien de vous tous ! Combien de vos fils de discussion sont épiques, quelle est la popularité de vos articles et combien de fois vos programmes dans la base de code sont téléchargés - ce n'est qu'une petite partie de ce qui est retenu sur MQL5.com. Vos réalisations sont disponibles dans votre profil, mais qu'en est-il de l'image globale ? Dans cet article, nous allons montrer le tableau général des réalisations de tous les membres de la MQL5.community.
La transformation de Box-Cox La transformation de Box-Cox
Cet article a pour but de familiariser ses lecteurs avec la transformation de Box-Cox. Les questions concernant son utilisation sont abordées et quelques exemples sont donnés permettant d'évaluer l'efficacité de la transformation avec des séquences aléatoires et des cotations réelles.
Fondamentaux de la statistique Fondamentaux de la statistique
Chaque trader travaille en utilisant certains calculs statistiques, même s'il est partisan de l'analyse fondamentale. Cet article vous présente les fondements de la statistique, ses éléments de base et montre l'importance des statistiques dans la prise de décision.
Quelques conseils pour les nouveaux clients Quelques conseils pour les nouveaux clients
Une sagesse proverbiale souvent attribuée à diverses personnes célèbres dit : « Celui qui ne fait pas d'erreurs ne fait jamais rien. » À moins que vous ne considériez l'oisiveté comme une erreur, cette affirmation est difficile à contredire. Mais vous pouvez toujours analyser les erreurs passées (les vôtres et celles des autres) pour minimiser le nombre de vos erreurs futures. Nous allons tenter de passer en revue les situations qui peuvent se présenter lors de l'exécution de travaux dans le service du même nom.