Come Scambiare i Dati: Una DLL per MQL5 in 10 minuti
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:
- 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
- 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
- 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
- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso