Promuovi i tuoi progetti di sviluppo utilizzando le librerie EX5

--- | 17 dicembre, 2021


Introduzione

Un lettore sofisticato non ha bisogno di una spiegazione del motivo per nascondere le implementazioni di funzioni e classi nelle librerie. Chi tra voi è attivamente alla ricerca di nuove idee, potrebbe voler sapere che nascondere i dettagli di implementazione di classi/funzioni in un file .ex5 consentirà di condividere i propri algoritmi di know-how con altri sviluppatori, impostare progetti comuni e promuovere loro nel Web.

E mentre il team di MetaQuotes non risparmia alcuno sforzo per realizzare la possibilità di ereditarietà diretta delle classi della libreria ex5, noi lo implementeremo proprio ora.

Sommario

1. Esportazione e importazione di funzioni
2. Esportazione dell'implementazione nascosta di una classe
3. Inizializzazione delle variabili nel file .ex5
4. Ereditarietà delle classi di esportazione
5. Pubblicazione di librerie x5


1. Esportazione e importazione di funzioni

Questo è un metodo di base alla base dell'esportazione delle classi. Ci sono tre cose fondamentali che devono essere prese in considerazione affinché le tue funzioni siano disponibili per altri programmi:

  1. Il file da creare deve avere l'estensione .mq5 (non .mqh) per poter essere compilato in un file .ex5;
  2. Il file deve contenere la direttiva per il preprocessore della libreria #property;
  3. La parola chiave "export" deve essere inserita dopo le intestazioni delle funzioni esportate richieste
Example 1. Let us create a function to be used in other programs

//--- library.mq5
#property library
int libfunc (int a, int b) export
{
  int c=a+b;
  Print("a+b="+string(с));
  return(с);
}

Dopo aver compilato questo file, otterrai il file library.ex5 da cui libfunc può essere utilizzato in un altro programma.

Anche il processo di importazione delle funzioni è molto semplice. Viene eseguito utilizzando la direttiva #import per il preprocessore.

Example 2. We will use the export function libfunc() in our script

//--- uses.mq5
#import "library.ex5"
  int libfunc(int a, int b);
#import

void OnStart()
{
  libfunc(1, 2);
}

Tieni presente che il compilatore cercherà i file .ex5 nella cartella MQL5\Libraries. Quindi, se library.ex5 non si trova in quella cartella, dovrai specificare il relativo percorso.

Per esempio:

#import "..\Include\MyLib\library.ex5" // the file is located in the MQL5\Include\MyLib folder
#import "..\Experts\library.ex5" // the file is located in the MQL5\Experts\ folder

Per un uso futuro, le funzioni possono essere importate non solo nel file .mq5 di destinazione, ma anche nei file .mqh.

Per illustrare l'applicazione pratica, utilizziamo alcuni grafici.

Creeremo una libreria di funzioni per l'esportazione. Queste funzioni visualizzeranno oggetti grafici come Button, Edit, Label and Rectangle Label su un grafico, elimineranno gli oggetti dal grafico e reimposteranno i parametri di colore del grafico.

Questo può essere schematicamente mostrato come segue:

Schema di esportazione del metodo di classe

Il file completo Graph.mq5 si trova alla fine dell'articolo. Qui daremo solo un esempio di modello della funzione di disegno Edit.

//+------------------------------------------------------------------+
//| SetEdit                                                          |
//+------------------------------------------------------------------+
void SetEdit(long achart,string name,int wnd,string text,color txtclr,color bgclr,color brdclr,
             int x,int y,int dx,int dy,int corn=0,int fontsize=8,string font="Tahoma",bool ro=false) export
  {
   ObjectCreate(achart,name,OBJ_EDIT,wnd,0,0);
   ObjectSetInteger(achart,name,OBJPROP_CORNER,corn);
   ObjectSetString(achart,name,OBJPROP_TEXT,text);
   ObjectSetInteger(achart,name,OBJPROP_COLOR,txtclr);
   ObjectSetInteger(achart,name,OBJPROP_BGCOLOR,bgclr);
   ObjectSetInteger(achart,name,OBJPROP_BORDER_COLOR,brdclr);
   ObjectSetInteger(achart,name,OBJPROP_FONTSIZE,fontsize);
   ObjectSetString(achart,name,OBJPROP_FONT,font);
   ObjectSetInteger(achart,name,OBJPROP_XDISTANCE,x);
   ObjectSetInteger(achart,name,OBJPROP_YDISTANCE,y);
   ObjectSetInteger(achart,name,OBJPROP_XSIZE,dx);
   ObjectSetInteger(achart,name,OBJPROP_YSIZE,dy);
   ObjectSetInteger(achart,name,OBJPROP_SELECTABLE,false);
   ObjectSetInteger(achart,name,OBJPROP_READONLY,ro);
   ObjectSetInteger(achart,name,OBJPROP_BORDER_TYPE,0);
   ObjectSetString(achart,name,OBJPROP_TOOLTIP,"");
  }

L'importazione delle funzioni richieste e il loro utilizzo sarà implementato nel file di destinazione Spiro.mq5:

Example 3. Using imported functions

//--- Spiro.mq5 – the target file of the Expert Advisor

//--- importing some graphics functions
#import "Graph.ex5" 
  void SetLabel(long achart, string name, int wnd, string text, color clr, 
               int x, int y, int corn=0, int fontsize=8, string font="Tahoma");
  void SetEdit(long achart, string name, int wnd, string text, color txtclr, color bgclr, color brdclr, 
                 int x, int y, int dx, int dy, int corn=0, int fontsize=8, string font="Tahoma", bool ro=false);
  void SetButton(long achart, string name, int wnd, string text, color txtclr, color bgclr, 
                int x, int y, int dx, int dy, int corn=0, int fontsize=8, string font="Tahoma", bool state=false);
  void HideChart(long achart, color BackClr);
#import

//--- prefix for chart objects
string sID; 
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

void OnInit()
{
  HideChart(0, clrWhite);
  sID="spiro.";
  DrawParam();
}
//+------------------------------------------------------------------+
//| DrawParam                                                        |
//+------------------------------------------------------------------+
void DrawParam()
{
  color bgclr=clrWhite, clr=clrBlack;
//--- bigger radius    
  SetLabel(0, sID+"stR.", 0, "R", clr, 10, 10+3);
  SetEdit(0, sID+"R.", 0, "100", clr, bgclr, clr, 40, 10, 50, 20);
//--- smaller radius   
  SetLabel(0, sID+"str.", 0, "r", clr, 10, 35+3);
  SetEdit(0, sID+"r.", 0, "30", clr, bgclr, clr, 40, 35, 50, 20);
//--- distance to the center
  SetLabel(0, sID+"stD.", 0, "D", clr, 10, 60+3);
  SetEdit(0, sID+"D.", 0, "40", clr, bgclr, clr, 40, 60, 50, 20);
//--- drawing accuracy
  SetLabel(0, sID+"stA.", 0, "Alfa", clr, 10, 85+3); 
  SetEdit(0, sID+"A.", 0, "0.04", clr, bgclr, clr, 40, 85, 50, 20);
//--- drawing accuracy
  SetLabel(0, sID+"stN.", 0, "Rotor", clr, 10, 110+3); 
  SetEdit(0, sID+"N.", 0, "10", clr, bgclr, clr, 40, 110, 50, 20);
//--- draw button
  SetButton(0, sID+"draw.", 0, "DRAW", bgclr, clr, 39, 135, 51, 20); 
}

Dopo l'esecuzione dell'Expert Advisor, gli oggetti appariranno sul grafico:

Esempio di utilizzo di oggetti della libreria

Come si può vedere, il processo di esportazione e importazione delle funzioni non è affatto difficile, ma assicurati di leggere alcune limitazioni nella Guida: esportazione, importazione.


2. Esportazione dell'implementazione nascosta di una classe

Poiché le classi in MQL5 non possono ancora essere esportate direttamente, dovremo ricorrere a un metodo un po' fantasioso. Si basa sul polimorfismo e sulle funzioni virtuali. Infatti, non è la classe stessa che viene restituita dal modulo ex5, ma un suo oggetto creato. Chiamiamolo l'oggetto di implementazione nascosto.

L'essenza del metodo è dividere la classe richiesta in due in modo che la dichiarazione di funzioni e variabili sia aperta per l'accesso pubblico e i loro dettagli di implementazione siano nascosti in un file .ex5 chiuso.

Questo può essere semplicemente esemplificato come mostrato di seguito. C'è la classe CSpiro che vorremmo condividere con altri sviluppatori senza rivelare i dettagli di implementazione. Supponiamo che contenga variabili, costruttore, distruttore e funzioni di lavoro.

Per esportare la classe, faremo come segue:

Di conseguenza, abbiamo due file:

Example 4. Hiding of the class implementation in the ex5 module

//--- Spiro.mqh – public file, the so called header file

//+------------------------------------------------------------------+
//| Class CSpiro                                                     |
//| Spirograph draw class                                       |
//+------------------------------------------------------------------+
class CSpiro
  {
public:
   //--- prefix of the chart objects
   string            m_sID;
   //--- offset of the chart center
   int               m_x0,m_y0;
   //--- color of the line
   color             m_clr;
   //--- chart parameters
   double            m_R,m_r,m_D,m_dAlfa,m_nRotate;

public:
   //--- constructor
                     CSpiro() { };
   //--- destructor
                    ~CSpiro() { };
   virtual void Init(int ax0,int ay0,color aclr,string asID) { };
   virtual void SetData(double aR,double ar,double aD,double adAlpha,double anRotate) { };

public:
   virtual void DrawSpiro() { };
   virtual void SetPoint(int x,int y) { };
  };

Da notare che tutte le classi di funzioni sono dichiarate con la parola chiave virtual.

//--- ISpiro.mq5 – hidden implementation file

#include "Spiro.mqh"

//--- importing some functions
#import "..\Experts\Spiro\Graph.ex5"
void SetPoint(long achart,string name,int awnd,int ax,int ay,color aclr);
void ObjectsDeleteAll2(long achart=0,int wnd=-1,int type=-1,string pref="",string excl="");
#import

CSpiro *iSpiro() export { return(new ISpiro); }
//+------------------------------------------------------------------+
//| Сlass ISpiro                                                     |
//| Spirograph draw class                                       |
//+------------------------------------------------------------------+
class ISpiro : public CSpiro
  {
public:
                     ISpiro() { m_x0=0; m_y0=0; };
                    ~ISpiro() { ObjectsDeleteAll(0,0,-1); };
   virtual void      Init(int ax0,int ay0,color aclr,string asID);
   virtual void      SetData(double aR,double ar,double aD,double adAlpha,double anRotate);

public:
   virtual void      DrawSpiro();
   virtual void      SetPoint(int x,int y);
  };
//+------------------------------------------------------------------+
//| Init                                                             |
//+------------------------------------------------------------------+
void ISpiro::Init(int ax0,int ay0,color aclr,string asID)
  {
   m_x0=ax0;
   m_y0=ay0;
   m_clr=aclr;
   m_sID=asID;
   m_R=0; 
   m_r=0; 
   m_D=0;
  }
//+------------------------------------------------------------------+
//| SetData                                                          |
//+------------------------------------------------------------------+
void ISpiro::SetData(double aR,double ar,double aD,double adAlpha,double anRotate)
  {
   m_R=aR; m_r=ar; m_D=aD; m_dAlfa=adAlpha; m_nRotate=anRotate;
  }
//+------------------------------------------------------------------+
//| DrawSpiro                                                        |
//+------------------------------------------------------------------+
void ISpiro::DrawSpiro()
  {
   if(m_r<=0) { Print("Error! r==0"); return; }
   if(m_D<=0) { Print("Error! D==0"); return; }
   if(m_dAlfa==0) { Print("Error! Alpha==0"); return; }
   ObjectsDeleteAll2(0,0,-1,m_sID+"pnt.");
   int n=0; double a=0;
   while(a<m_nRotate*2*3.1415926)
     {
      double x=(m_R-m_r)*MathCos(a)+m_D*MathCos((m_R-m_r)/m_r*a);
      double y=(m_R-m_r)*MathSin(a)-m_D*MathSin((m_R-m_r)/m_r*a);
      SetPoint(int(m_x0+x),int(m_y0+y));
      a+=m_dAlfa;
     }
   ChartRedraw(0);
  }
//+------------------------------------------------------------------+
//| SetPoint                                                         |
//+------------------------------------------------------------------+
void ISpiro::SetPoint(int x,int y)
  {
   Graph::SetPoint(0,m_sID+"pnt."+string(x)+"."+string(y),0,x,y,m_clr);
  }
//+------------------------------------------------------------------+

Come si può notare, la classe nascosta è stata implementata nel file .mq5 e contiene il comando del preprocessore #property library. Sono state quindi rispettate tutte le regole esposte nella sezione precedente.

Da notare inoltre l'operatore di risoluzione dell'ambito per la funzione SetPoint. È dichiarato sia nella libreria Graph che nella classe CSpiro. Affinché il compilatore chiami la funzione richiesta, la specifichiamo esplicitamente utilizzando l'azione :: e diamo il nome del file.

  Graph::SetPoint(0, m_sID+"pnt."+string(x)+"."+string(y), 0, x, y, m_clr);

Ora possiamo includere il file di intestazione e importare la sua implementazione nel nostro Expert Advisor risultante.

Questo può essere schematicamente mostrato come segue:

Schema per lavorare con i metodi delle classi della libreria

Example 5. Using export objects

//--- Spiro.mq5 - the target file of the Expert Advisor

//--- importing some functions
#import "Graph.ex5" 
  void SetLabel(long achart, string name, int wnd, string text, color clr,
               int x, int y, int corn=0, int fontsize=8, string font="Tahoma");
  void SetEdit(long achart, string name, int wnd, string text, color txtclr, color bgclr, color brdclr, 
              int x, int y, int dx, int dy, int corn=0, int fontsize=8, string font="Tahoma", bool ro=false);
  void SetButton(long achart, string name, int wnd, string text, color txtclr, color bgclr, 
                int x, int y, int dx, int dy, int corn=0, int fontsize=8, string font="Tahoma", bool state=false);
  void HideChart(long achart, color BackClr);
#import

//--- including the chart class
#include <Spiro.mqh> 

//--- importing the object
#import "ISpiro.ex5"
  CSpiro *iSpiro();
#import

//--- object instance
CSpiro *spiro; 
//--- prefix for chart objects
string sID; 
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
{
  HideChart(0, clrWhite);
  sID="spiro.";
  DrawParam();
//--- object instance created
  spiro=iSpiro(); 
//--- initializing the drawing
  spiro.Init(250, 200, clrBlack, sID);
//--- setting the calculation parameters
  spiro.SetData(100, 30, 40, 0.04, 10);
//--- drawing
  spiro.DrawSpiro(); 
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
  delete spiro; // deleting the object
}

Di conseguenza, sarai in grado di modificare i parametri dell'oggetto nel grafico e disegnare il grafico dell'oggetto



Parametri oggetto grafico


3. Inizializzazione delle variabili nel file .ex5

Capita spesso che la tua ISuperClass utilizzi variabili dal file include globals.mqh. Queste variabili possono essere incluse in modo simile per essere utilizzate negli altri file.

Per esempio:

Example 6. Public include file

//--- globals.mqh

#include <Trade\Trade.mqh>
//--- instance of the trade function object
extern CTrade *_trade; 

L'unica istanza dell'oggetto _trade è inizializzata nel tuo programma, ma è usata nella classe nascosta ISuperClass.

A tal fine, un puntatore all'oggetto creato deve essere passato dalla classe ISuperClass al file .ex5.

È più semplice quando l'oggetto viene ricevuto dal file .ex5, come di seguito:

Example 7. Initialization of variables upon creation of the object

//--- ISuperClass.mq5 –hidden implementation file

#property library
CSuperClass *iSuperClass(CTrade *atrade) export
{
//--- saving the pointer
   _trade=atrade; 
//--- returning the object of the hidden implementation of ISuperClass of the open CSuperClass class
  return(new ISuperClass); 
}
//... the remaining code

Pertanto, tutte le variabili richieste vengono inizializzate alla ricezione dell'oggetto nel suo modulo.

In effetti, potrebbero esserci molte variabili globali pubbliche che possono essere di tipi diversi. Coloro che non sono desiderosi di cambiare continuamente l'intestazione della funzione iSuperClass devono creare una classe speciale che aggrega tutte le variabili e le funzioni globali per lavorare con essa.

Example 8. Public include file

//--- globals.mqh
#include <Trade\Trade.mqh>

//--- trade "object"
extern CTrade *_trade; 
//--- name of the Expert Advisor of the system
extern string _eaname; 

//+------------------------------------------------------------------+
//| class __extern                                                   |
//+------------------------------------------------------------------+
class __extern // all extern parameters for passing between the ex5 modules are accumulated here
{
public:
//--- the list of all public global variables to be passed
//--- trade "object"
  CTrade *trade; 
//--- name of the Expert Advisor of the system
  string eaname; 
    
public:
  __extern() { };
  ~__extern() { };

//--- it is called when passing the parameters into the .ex5 file
  void Get() { trade=_trade; eaname=_eaname; };  // getting the variables

 //--- it is called in the .ex5 file
  void Set() { _trade=trade; _eaname=eaname; };  // setting the variables
                                                       
};
//--- getting the variables and pointer for passing the object into the .ex5 file
__extern *_GetExt() { _ext.Get(); return(GetPointer(_ext)); } 

//--- the only instance for operation
extern __extern _ext; 
Il file ISuperClass.mq5 verrà implementato come segue:
Example 9.

//--- ISuperClass.mq5 –hidden implementation file

#property library
CSuperClass *iSuperClass(__extern *aext) export
{
//--- taking in all the parameters
  aext.Set();
//--- returning the object
  return(new ISuperClass); 
}
//--- ... the remaining code

La chiamata di funzione verrà ora trasformata in una forma semplificata e, cosa più importante, estensibile.

Example 10. Using export objects in the presence of public global variables

//--- including global variables (usually located in SuperClass.mqh)
#include "globals.mqh"    

//--- including the public header class
#include "SuperClass.mqh" 
//--- getting the hidden implementation object
#import "ISuperClass.ex5"
  CSuperClass *iSuperClass();
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
//--- creating the hidden implementation object providing for the passing of all parameters
  CSuperClass *sc=iSuperClass(_GetExt()); 
  //--- ... the remaining code
}


4. Ereditarietà delle classi di esportazione

Avrai già capito che questo modo di esportare gli oggetti implica che l'ereditarietà diretta e semplice è fuori discussione. L'esportazione dell'oggetto di implementazione nascosto suggerisce che l'oggetto stesso è l'ultimo anello della catena di ereditarietà ed è quello che può essere utilizzato alla fine.

Nel caso generale, è possibile creare una "emulazione" dell'ereditarietà scrivendo una classe intermedia aggiuntiva. E qui avremo ovviamente bisogno di polimorfismo e virtualità.

Example 11. Emulation of inheritance of hidden classes

//--- including the public header class
#include "SuperClass.mqh" 

//--- getting the hidden implementation object
#import "ISuperClass.ex5"
  CSuperClass *iSuperClass();
#import

class _CSuperClass
{
public:
//--- instance of the hidden implementation object
  CSuperClass *_base;
public:
//--- constructor
  _CSuperClass() {  _base=iSuperClass(_GetExt()); };
//--- destructor
  ~_CSuperClass() { delete _base; };
//--- further followed by all functions of the base CSuperClass class
//--- working function called from the hidden implementation object
  virtual int func(int a, int b) { _base.func(a,b); }; 
};

L'unico problema qui è l'accesso alle variabili di CSuperClass. Come si vede, non sono presenti nella dichiarazione del discendente e si trovano nella variabile _base. Di solito non influisce sull'usabilità a condizione che sia presente una classe di intestazione SuperClass.mqh.

Naturalmente, se ti concentri principalmente sulle funzioni di know-how, non devi creare in anticipo un wrapper di ISuperClass riguardo ad esse. Sarà sufficiente esportare quelle funzioni di know-how e lasciare che gli sviluppatori esterni creino le proprie classi wrapper che saranno poi facili da ereditare.

Pertanto, quando prepari i tuoi sviluppi per altri sviluppatori, devi preoccuparti di creare un intero set di funzioni di esportazione necessarie, file e classi .mqh e .ex5:
  1. Esportazione di funzioni indipendenti della classe
  2. File di intestazione .mqh e le loro implementazioni .ex5
  3. Inizializzazione delle variabili nei file .ex5


5. Pubblicazione di librerie x5

Nel novembre 2011, MetaQuotes ha iniziato a fornire l'accesso a un repository di file. Maggiori informazioni possono essere trovate nell'annuncio.

Questo repository ti consente di archiviare i tuoi sviluppi e, cosa più importante, di fornirne l'accesso ad altri sviluppatori. Questo strumento ti consentirà di pubblicare facilmente nuove versioni dei tuoi file per garantire un accesso rapido ad essi per gli sviluppatori che potrebbero utilizzare questi file.

Inoltre, il sito aziendale offre l'opportunità di offrire le proprie librerie di funzioni nel Market a titolo commerciale o gratuito.


Conclusione

Ora sai come creare le librerie ex5 con l'esportazione delle loro funzioni o oggetti di classe e puoi applicare le tue conoscenze nella pratica. Tutte queste risorse ti permetteranno di stabilire una più stretta collaborazione con altri sviluppatori: lavorare su progetti comuni, promuoverli nel Market o fornire accesso alle funzioni della libreria ex5.