English Русский 中文 Español Deutsch 日本語 Português 한국어 Français Türkçe
Come Scambiare i Dati: Una DLL per MQL5 in 10 minuti

Come Scambiare i Dati: Una DLL per MQL5 in 10 minuti

MetaTrader 5Esempi | 15 dicembre 2021, 17:02
112 0
MetaQuotes
Renat Fatkhullin

In effetti, non sono molti gli sviluppatori che ricordano esattamente come scrivere una semplice libreria DLL e quali sono le caratteristiche del collegamento di sistemi diversi.

Usando diversi esempi, cercherò di mostrare l'intero processo di creazione della semplice DLL in 10 minuti, oltre a discutere alcuni dettagli tecnici della nostra implementazione vincolante. Useremo Visual Studio 2005/2008; le sue versioni Express sono gratuite e possono essere scaricate dal sito Web Microsoft.

1. Creazione di un progetto DLL in C++ in Visual Studio 2005/2008

Eseguire la procedura guidata dell'applicazione Win32 utilizzando il menu "File -> Nuovo", selezionare il tipo di progetto come "Visual C++", scegliere il modello "Applicazione console Win32" e definire il nome del progetto (ad esempio, "MQL5DLLSamples"). Selezionare una directory principale per la memorizzazione del progetto "Posizione", invece di quella offerta di default, disabilitare la casella di controllo "Crea directory per soluzione" e fare clic su "OK":

Fig. 1. Win32 Application Wizard, creazione del progetto DLL

Nel passaggio successivo premere 'Next' per andare alla pagina delle impostazioni:

Fig. 2. Win32 Application Wizard, impostazioni del progetto

Nella pagina finale, seleziona il tipo di applicazione "DLL", lasciando vuoti gli altri campi così come sono, e fai clic su "Finish". Non impostare l'opzione "Esporta simboli", se non vuoi rimuovere il codice dimostrativo aggiunto automaticamente:

Fig. 3. Creazione guidata applicazione Win32, impostazioni Applicazione

Di conseguenza avrai un progetto vuoto:

Fig. 4. Il progetto DLL vuoto preparato da Wizard

Per semplificare i test, è meglio specificare nelle opzioni 'Output Directory' l'output dei file DLL direttamente in '...\MQL5\Libraries' del terminale client - inoltre, ti farà risparmiare molto tempo:

Fig. 5. Directory di output della DLL


2. Preparazione all'Aggiunta di Funzioni

Aggiungi la macro '_DLLAPI' alla fine del file stdafx.h, in modo da poter descrivere comodamente e facilmente le funzioni esportate:

//+------------------------------------------------------------------+
//|                                                 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)
//+------------------------------------------------------------------+

Le chiamate di funzioni importate dalla DLL in MQL5 dovrebbero avere la convenzione di chiamata stdcall e cdecl. Sebbene stdcall e cdecl differiscano nelle modalità di estrazione dei parametri da uno stack, l'ambiente di runtime MQL5 può utilizzare in sicurezza entrambe le versioni grazie allo speciale wrapper delle chiamate DLL.

Il compilatore C++ usa  __cdecl chiamando per impostazione predefinita, ma consiglio di specificare esplicitamente la modalità __stdcall per le funzioni esportate.

Una funzione di esportazione scritta correttamente deve avere la seguente forma:

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

In un programma MQL5, la funzione dovrebbe essere definita e chiamata come segue:

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

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

Dopo la compilazione del progetto, questa stdcall verrà visualizzata nella tabella di esportazione come _fnCalculateSpeed@8, dove il compilatore aggiunge un carattere di sottolineatura e il numero di byte, trasmessi attraverso lo stack. Tale decorazione permette di controllare meglio la sicurezza delle chiamate di funzioni DLL a causa del fatto che il chiamante sa esattamente quanti (ma non il tipo di!) dati che dovrebbero essere inseriti nello stack.

Se la dimensione finale del blocco di parametri ha un errore nella DLL descrizione dell'importazione della funzione, la funzione non verrà chiamata e il nuovo messaggio apparirà sul journal: 'Cannot find 'fnCrashTestParametersStdCall' in 'MQL5DLLSamples.dll'. In questi casi è necessario controllare attentamente tutti i parametri sia nel prototipo della funzione che nella sorgente della DLL.

La ricerca della descrizione semplificata senza decorazione viene utilizzata per compatibilità nel caso in cui la tabella di esportazione non contenga il nome completo della funzione. Nomi come fnCalculateSpeed vengono creati se le funzioni sono definite nel formato __cdecl.
_DLLAPI int fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  }


3. Metodi per Passare Parametri e Scambiare Dati

Consideriamo diverse varianti dei parametri passati:

  1. Ricezione e passaggio di variabili semplici
    Il caso delle variabili semplici è facile: possono essere passate per valore o per riferimento usando &.
    _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);
      }
        
    Chiamata da 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);
    
    L'output è:
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Time  16  msec, int:  -752584127  double:  17247836076609
  2. Ricezione e passaggio di un array con riempimento di elementi

    A differenza di altri programmi MQL5, il passaggio dell'array viene eseguito tramite il riferimento diretto al buffer di dati senza accesso alle informazioni proprietarie sulle dimensioni e sulle dimensioni. Ecco perché la dimensione dell'array e la dimensione dovrebbero essere passate separatamente.

    _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;
      }
        
    Chiamata da 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);
    
    L'output è:
    MQL5DLL Test (GBPUSD,M1) 20:31:12 Array: 0 1 2 3 4 5 6 7 8 9 
  3. Passaggio e modifica delle stringhe
    Le stringhe unicode vengono passate utilizzando riferimenti diretti ai relativi indirizzi del buffer senza passare alcuna informazione aggiuntiva.
    _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));
      }
    
    Chiamata da 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);
    Il risultato è:
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Replace:  A quick brown fox jumps over the lazy dog
    Abbiamo scoperto che la linea non era cambiata! Questo è un errore comune dei neofiti quando trasmettono copie di oggetti (una stringa è un oggetto), invece di fare riferimento ad essi. È stata creata automaticamente la copia della stringa 'testo' che è stata modificata nella DLL, quindi è stata rimossa automaticamente senza alterare l'originale.

    Per rimediare a questa situazione, è necessario passare una stringa per riferimento. Per farlo è sufficiente modificare il blocco di importazione aggiungendo & al parametro "testo":
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string &text,string from,string to);
    #import
    Dopo la compilazione e l'avvio otterremo il risultato giusto:
    MQL5DLL Test (GBPUSD,M1) 19:58:31 Replace:  A quick brown cat jumps over the lazy dog

4. Cattura di eccezioni nelle funzioni DLL

Per evitare che il terminale si schiacci, ogni chiamata DLL è protetta automaticamente da Unhandled Exception Wrapping. Questo meccanismo permette di proteggere dalla maggior parte degli errori standard (errori di accesso alla memoria, divisione per zero, ecc.)

Per vedere come funziona il meccanismo, creiamo il seguente codice:

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

e chiamiamolo dal terminale del cliente:

#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!");
//---

Di conseguenza, proverà a scrivere sull'indirizzo zero e genererà un'eccezione. Il client terminal lo catturerà, lo registrerà nel journal e continuerà il suo lavoro:

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

5. Chiamate wrapper di DDL e perdita di velocità in chiamata

Come già descritto sopra, ogni chiamata di funzioni DLL è racchiusa in un wrapper speciale per garantire la sicurezza. Questo legame maschera il codice base, sostituisce lo stack, supporta gli accordi stdcall / cdecl e controlla le eccezioni all'interno delle funzioni chiamate.

Questo volume di lavori non comporta un ritardo significativo nella chiamata della funzione.

6. La costruzione finale

Raccogliamo tutti gli esempi di funzioni DLL di cui sopra nel file 'MQL5DLLSamples.cpp' e gli esempi MQL5 nello script 'MQL5DLL Test.mq5'. Il progetto finale per Visual Studio 2008 e lo script in MQL5 sono allegati all'articolo.

//+------------------------------------------------------------------+
//|                                                 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!");
//---
  }
//+------------------------------------------------------------------+

Grazie per il tuo interesse! Sono pronto a rispondere a qualsiasi domanda.

Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/18

File allegati |
mql5dll_test.mq5 (1.83 KB)
mql5dllsamples.zip (4.62 KB)
Scambio di Dati tra Indicatori: È Facile Scambio di Dati tra Indicatori: È Facile
Vogliamo creare un tale ambiente che fornisca l'accesso ai dati degli indicatori allegati a un grafico e che abbia le seguenti proprietà: assenza di copia dei dati; modifica minima del codice dei metodi disponibili, se è necessario utilizzarli; È preferibile il codice MQL (ovviamente dobbiamo usare DLL, ma useremo solo una dozzina di stringhe di codice C++). L'articolo descrive un metodo semplice per sviluppare un ambiente di programma per il terminale MetaTrader, che fornirebbe i mezzi per accedere ai buffer degli indicatori da altri programmi MQL.
L'Istogramma dei Prezzi (Market Profile) e la sua implementazione in MQL5 L'Istogramma dei Prezzi (Market Profile) e la sua implementazione in MQL5
The Market Profile è stato sviluppato dal geniale pensatore Peter Steidlmayer. Ha suggerito di utilizzare la rappresentazione alternativa delle informazioni sui movimenti di mercato "orizzontali" e "verticali" che porta a un insieme completamente diverso di modelli. Ha assunto che ci sia un impulso sottostante del mercato o un modello fondamentale chiamato ciclo di equilibrio e squilibrio. In questo articolo considererò Price Histogram - un modello semplificato di Market Profile e descriverò la sua implementazione in MQL5.
Disegnare le Emissioni di Indicatori in MQL5 Disegnare le Emissioni di Indicatori in MQL5
In questo articolo, prenderemo in considerazione l'emissione di indicatori: un nuovo approccio alla ricerca di mercato. Il calcolo dell'emissione si basa sull'intersezione di diversi indicatori: dopo ogni tick, compaiono sempre più punti con colori e forme differenti. Formano numerosi ammassi come nebulose, nuvole, tracce, righe, archi, ecc. Queste forme aiutano a rilevare le molle e le forze invisibili che influenzano il movimento dei prezzi di mercato.
Strategie d'Ordine. Expert Advisor Multiuso Strategie d'Ordine. Expert Advisor Multiuso
Questo articolo è incentrato sulle strategie che utilizzano attivamente gli ordini in sospeso, un metalinguaggio che può essere creato per descrivere formalmente tali strategie e l'uso di un Expert Advisor multiuso il cui funzionamento si basa su tali descrizioni