English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes

Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes

MetaTrader 5Exemples | 15 novembre 2021, 13:36
258 0
MetaQuotes
Renat Fatkhullin

En fait, il n'y a pas beaucoup de développeurs qui se rappellent exactement comment écrire une bibliothèque DLL simple et quelles sont les caractéristiques de liaison de différents systèmes.

À l'aide de plusieurs exemples, je vais tenter de montrer l'ensemble du processus de création de la DLL simple en 10 minutes, ainsi que de discuter de certains détails techniques de notre implémentation de liaison. Nous utiliserons Visual Studio 2005/2008 ; ses versions Express sont gratuites et peuvent être téléchargées depuis le site Web Microsoft.

1. Création d'un projet DLL en C++ dans Visual Studio 2005/2008

Exécutez l'assistant d'application Win32 à l'aide du menu 'Fichier -> Nouveau', sélectionnez le type de projet comme 'Visual C++', choisissez le modèle 'Win32 Console Application' et définissez le nom du projet (par exemple, 'MQL5DLLSamples'). Sélectionnez un répertoire racine pour stocker le projet «Emplacement», au lieu de celui proposé par défaut, décochez la case «Créez un répertoire pour la solution» et cliquez sur «OK» :

Fig. 1. Assistant d'application Win32, création de projet DLL

À l'étape suivante, appuyez sur « Suivant » pour accéder à la page des paramètres :

Fig. 2. Assistant d'application Win32, paramètres du projet

Sur la dernière page, sélectionnez le type d'application 'DLL', en laissant les autres champs vides tels quels, et cliquez sur 'Terminer '. Ne définissez pas l'option 'Exporter les symboles', si vous ne souhaitez pas supprimer le code de démonstration ajouté automatiquement :

Fig. 3. Assistant d'application Win32, Paramètres d'application

En conséquence, vous aurez un projet vide :

Fig. 4. Le projet DLL vide préparé par Wizard

Pour simplifier les tests, il est préférable de spécifier dans les options 'Répertoire de sortie' la sortie des fichiers DLL directement vers '...\MQL5\Libraries' du terminal client - de plus, cela vous fera gagner beaucoup de temps :

Fig. 5. Répertoire de sortie DLL


2. Préparation à l'Ajout de Fonctions

Ajoutez la macro '_DLLAPI' à la fin du fichier stdafx.h, afin de pouvoir décrire commodément et facilement les fonctions exportées :

//+------------------------------------------------------------------+
//|                                                 MQL5 DLL Samples |
//|                   Copyright 2001-2010, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#pragma once

#include "targetver.h"

#define WIN32_LEAN_AND_MEAN             // Exclude rarely-used stuff from Windows headers
#include <windows.h>

//---
#define _DLLAPI extern "C" __declspec(dllexport)
//+------------------------------------------------------------------+

Les appels de fonctions importés par DLL dans MQL5 doivent avoir la convention d'appel stdcall et cdecl. Bien que stdcall et cdecl diffèrent dans la manière d'extraire les paramètres d'une pile, l'environnement d'exécution MQL5 peut utiliser les deux versions en toute sécurité en raison de l'enveloppe spéciale des appels DLL.

Le compilateur C++ utilise  __cdecl appelant par défaut, mais je recommande d’indiquer explicitement le mode __stdcall pour les fonctions exportées.

Une fonction d'exportation correctement écrite doit avoir la forme suivante :

_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  }

Dans un programme MQL5, la fonction doit être définie et appelée comme suit :

#import "MQL5DLLSamples.dll"
int  fnCalculateSpeed(int &res1,double &res2);
#import

//--- call
   speed=fnCalculateSpeed(res_int,res_double);

Après la compilation du projet, ce stdcall sera affiché dans le tableau d'exportation sous la forme _fnCalculateSpeed@8, où le compilateur ajoute un trait de soulignement et un nombre de octets, transmis via la pile. Un tel ornement permet de mieux contrôler la sécurité des appels de fonctions DLL du fait que le l'appelant sait exactement combien (mais pas le type de !) données qui doivent être placées dans la pile.

Si la taille finale du bloc de paramètres affiche une erreur dans la description de l'importation de la fonction DLL, la fonction ne sera pas appelée et le nouveau message apparaîtra dans le journal : 'Cannot find 'fnCrashTestParametersStdCall' in 'MQL5DLLSamples.dll'. Dans de tels cas, il est nécessaire de vérifier soigneusement tous les paramètres à la fois dans le prototype de fonction et dans la source DLL.

La recherche d’une description simplifiée sans ornement est utilisée pour la compatibilité au cas où le tableau d'exportation ne comporte pas le nom complet de la fonction. Des noms comme fnCalculateSpeed sont créés si les fonctions sont définies au format __cdecl.
_DLLAPI int fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  }


3. Méthodes de Transmission de Paramètres et Échange de Données

Examinons plusieurs variantes de paramètres transmises :

  1. Réception et transmission de variables simples
    Le cas des variables simples est facile - elles peuvent être transmises par valeur ou par référence en utilisant &.
    _DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
      {
       int    res_int=0;
       double res_double=0.0;
       int    start=GetTickCount();
    //--- simple math calculations
       for(int i=0;i<=10000000;i++)
         {
          res_int+=i*i;
          res_int++;
          res_double+=i*i;
          res_double++;
         }
    //--- set calculation results
       res1=res_int;
       res2=res_double;
    //--- return calculation time 
       return(GetTickCount()-start);
      }
        
    Appel depuis MQL5 :
    #import "MQL5DLLSamples.dll"
    int  fnCalculateSpeed(int &res1,double &res2);
    #import
    
    //--- calling the function for calculations
       int    speed=0;
       int    res_int=0;
       double res_double=0.0;
    
       speed=fnCalculateSpeed(res_int,res_double);
       Print("Time ",speed," msec, int: ",res_int," double: ",res_double);
    
    La sortie est :
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Time  16  msec, int:  -752584127  double:  17247836076609
  2. Réception et passage d'un tableau avec remplissage d'éléments

    Contrairement à d'autres programmes MQL5, la transmission du tableau est effectué via la référence directe au tampon de données sans accès aux informations exclusives sur les dimensions et les tailles. C'est pourquoi la dimension et la taille du tableau doivent être transmises séparément.

    _DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size)
      {
    //--- check for the input parameters
       if(arr==NULL || arr_size<1) return;
    //--- fill array with values
       for(int i=0;i<arr_size;i++) arr[i]=i;
      }
        
    Appel depuis MQL5 :
    #import "MQL5DLLSamples.dll"
    void fnFillArray(int &arr[],int arr_size);
    #import
    
    //--- call for the array filling
       int    arr[];
       string result="Array: "; 
       ArrayResize(arr,10);
       
       fnFillArray(arr,ArraySize(arr));
       for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" ";
       Print(result);
    
    La sortie est :
    MQL5DLL Test (GBPUSD,M1) 20:31:12 Array: 0 1 2 3 4 5 6 7 8 9 
  3. Passage et modification de chaînes
    Les chaînes Unicode sont transmises en utilisant des références directes à ses adresses de tampon sans transmettre aucune information supplémentaire.
    _DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
      {
       wchar_t *cp;
    //--- parameters check
       if(text==NULL || from==NULL || to==NULL) return;
       if(wcslen(from)!=wcslen(to))             return;
    //--- search for substring
       if((cp=wcsstr(text,from))==NULL)         return;
    //--- replace it
       memcpy(cp,to,wcslen(to)*sizeof(wchar_t));
      }
    
    Appel depuis MQL5 :
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string text,string from,string to);
    #import
    
    //--- modify the string
       string text="A quick brown fox jumps over the lazy dog"; 
       
       fnReplaceString(text,"fox","cat");
       Print("Replace: ",text);
    Le résultat :
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Replace:  A quick brown fox jumps over the lazy dog
    Il s'est avéré que la ligne n'avait pas changé ! C'est une erreur courante des débutants lorsqu'ils transmettent des copies d'objets (une chaîne est un objet), au lieu de s'y référer. La copie de la chaîne 'text' a été créée automatiquement et a été modifiée dans la DLL, puis elle a été supprimée automatiquement sans affecter l'original.

    Pour remédier à cette situation, il est nécessaire de transmettre une chaîne par référence. Pour faire celui-ci, modifiez simplement le bloc d'import en ajoutant & au paramètre "text":
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string &text,string from,string to);
    #import
    Après compilation et démarrage, nous obtiendrons le bon résultat :
    MQL5DLL Test (GBPUSD,M1) 19:58:31 Replace:  A quick brown cat jumps over the lazy dog

4. Capture d'exceptions dans les fonctions DLL

Pour éviter que le terminal ne s'écrase, chaque appel de DLL est protégé automatiquement par un Emballage d'Exception non géré. Ce mécanisme permet de se protéger de la plupart des erreurs standard (erreurs d'accès mémoire, division par zéro, etc.)

Pour voir comment fonctionne le mécanisme, créons le code suivant :

_DLLAPI void __stdcall fnCrashTest(int *arr)
  {
//--- wait for receipt of a zero reference to call the exception
   *arr=0;
  }

et appelez-le depuis le terminal client :

#import "MQL5DLLSamples.dll"
void fnCrashTest(int arr);
#import

//--- call for the crash (the execution environment will catch the exception and prevent the client terminal crush)
   fnCrashTest(NULL);
   Print("You won't see this text!");
//---

En conséquence, il tentera d'écrire à l'adresse zéro et générera une exception. Le terminal client l’attrapera, l'enregistrera dans le journal et poursuivra son travail :

MQL5DLL Test (GBPUSD,M1) 20:31:12 Access violation write to 0x00000000

5. Emballeur d'appels DLL et perte de vitesse sur les appels

Comme déjà décrit ci-dessus, chaque fonction d’appel de DLL est enroulée dans un emballage spécial afin d'assurer la sécurité. Cette liaison masque le code de base, remplace la pile, prend en charge les conventions stdcall / cdecl et surveille les exceptions dans les fonctions appelées.

Ce volume de travaux n'entraîne pas de retard considérable d'appel de fonction.

6. La construction finale

Rassemblons tous les exemples de fonctions DLL ci-dessus dans le fichier 'MQL5DLLSamples.cpp' et les exemples MQL5 dans le script 'MQL5DLL Test.mq5'. Le projet final pour Visual Studio 2008 et le script en MQL5 sont joints à l'article.

//+------------------------------------------------------------------+
//|                                                 MQL5 DLL Samples |
//|                   Copyright 2001-2010, MetaQuotes Software Corp. |
//|                                        https://www.metaquotes.net |
//+------------------------------------------------------------------+
#include "stdafx.h"

//+------------------------------------------------------------------+
//| Passing and receving of simple variables                         |
//+------------------------------------------------------------------+
_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
  {
   int    res_int=0;
   double res_double=0.0;
   int    start=GetTickCount();
//--- simple math calculations
   for(int i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     }
//--- set calculation results
   res1=res_int;
   res2=res_double;
//--- return calculation time
   return(GetTickCount()-start);
  }
//+------------------------------------------------------------------+
//| Filling the array with values                                    |
//+------------------------------------------------------------------+
_DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size)
  {
//--- check input variables
   if(arr==NULL || arr_size<1) return;
//--- fill array with values
   for(int i=0;i<arr_size;i++) arr[i]=i;
  }
//+------------------------------------------------------------------+
//| The substring replacement of the text string                     |
//| the string is passed as direct reference to the string content   |
//+------------------------------------------------------------------+
_DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
  {
   wchar_t *cp;
//--- parameters checking
   if(text==NULL || from==NULL || to==NULL) return;
   if(wcslen(from)!=wcslen(to))             return;
//--- search for substring
   if((cp=wcsstr(text,from))==NULL)         return;
//--- replace it 
   memcpy(cp,to,wcslen(to)*sizeof(wchar_t));
  }
//+------------------------------------------------------------------+
//| Call for the crush                                               |
//+------------------------------------------------------------------+
_DLLAPI void __stdcall fnCrashTest(int *arr)
  {
//--- wait for receipt of a zero reference to call the exception
   *arr=0;
  }
//+------------------------------------------------------------------+

//+------------------------------------------------------------------+
//|                                                 MQL5DLL Test.mq5 |
//|                        Copyright 2010, MetaQuotes Software Corp. |
//|                                              https://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2010, MetaQuotes Software Corp."
#property link      "https://www.mql5.com"
#property version   "1.00"
//---
#import "MQL5DLLSamples.dll"
int  fnCalculateSpeed(int &res1,double &res2);
void fnFillArray(int &arr[],int arr_size);
void fnReplaceString(string text,string from,string to);
void fnCrashTest(int arr);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//--- calling the function for calculations
   int    speed=0;
   int    res_int=0;
   double res_double=0.0;

   speed=fnCalculateSpeed(res_int,res_double);
   Print("Time ",speed," msec, int: ",res_int," double: ",res_double);
//--- call for the array filling
   int    arr[];
   string result="Array: "; 
   ArrayResize(arr,10);
   
   fnFillArray(arr,ArraySize(arr));
   for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" ";
   Print(result);
//--- modifying the string
   string text="A quick brown fox jumps over the lazy dog"; 
   
   fnReplaceString(text,"fox","cat");
   Print("Replace: ",text);
//--- and finally call a crash
//--- (the execution environment will catch the exception and prevent the client terminal crush)
   fnCrashTest(NULL);
   Print("You won't see this text!");
//---
  }
//+------------------------------------------------------------------+

Merci pour votre intérêt! Je suis prêt à répondre à toutes les questions.

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/18

Fichiers joints |
mql5dll_test.mq5 (1.83 KB)
mql5dllsamples.zip (4.62 KB)
Échange de Données entre les Indicateurs : C'est facile Échange de Données entre les Indicateurs : C'est facile
Nous souhaitons créer un tel environnement, qui donnerait accès aux données d'indicateurs attachés à un graphique, et aurait les propriétés suivantes : absence de copie de données ; modification minimale du code des méthodes disponibles, si nous devons les utiliser ; Le code MQL est préférable (bien sûr, nous devons utiliser des DLL, mais nous n'utiliserons qu'une douzaine de chaînes de code C++). L'article décrit une méthode simple pour élaborer un environnement de programme pour le terminal MetaTrader, qui fournirait des moyens d'accès aux tampons d'indicateurs d'autres programmes MQL.
L'Histogramme des prix (Profile du Marché) et son implémentation  en MQL5 L'Histogramme des prix (Profile du Marché) et son implémentation en MQL5
Le Profile du Marché a été élaboré par le brillant penseur Peter Steidlmayer. Il a suggéré l’utilisation de la représentation alternative de l'information sur les mouvements de marché « horizontaux » et « verticaux » qui conduit à un ensemble de modèles complètement différent. Il a assumé qu'il existe une impulsion sous-jacente du marché ou un modèle fondamental appelé cycle d'équilibre et de déséquilibre. Dans cet article, j’examinerai l'Histogramme des Prix - un modèle simplifié de profil de marché, et décrirai son implémentation dans MQL5.
Dessiner les Émissions de l'Indicateur en MQL5 Dessiner les Émissions de l'Indicateur en MQL5
Dans cet article, nous allons traiter l'émission d'indicateurs - une nouvelle approche de l'étude de marché. Le calcul de l'émission est basé sur l'intersection de différents indicateurs : de plus en plus de points de couleurs et de formes différentes apparaissent après chaque tick. Ils forment de nombreux groupes comme des nébuleuses, des nuages, des pistes, des lignes, des arcs, etc. Ces formes contribuent à détecter les ressorts et les forces invisibles qui affectent le mouvement des prix du marché.
Combinatoires et probabilités pour le trading (Partie IV) : Logique de Bernoulli Combinatoires et probabilités pour le trading (Partie IV) : Logique de Bernoulli
Dans cet article, j'ai décidé de mettre en avant le célèbre schéma de Bernoulli et de montrer comment il peut être utilisé pour décrire des tableaux de données liées au trading. Tous ces éléments seront ensuite utilisés pour créer un système de trading auto-adaptatif. Nous chercherons également un algorithme plus générique, dont un cas particulier est la formule de Bernoulli, et nous lui trouverons une application.