
Comment Échanger des Données : Une DLL pour MQL5 en 10 minutes
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 :
- 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
- 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
- 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





- 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