Download MetaTrader 5

Promote Your Development Projects Using EX5 Libraries

20 February 2012, 13:13
o_O
3
3 891

Introduction

A sophisticated reader does not need an explanation of the purpose of hiding function and class implementations in libraries. Those of you who are actively searching for new ideas may want to know that the hiding of the implementation details of classes/functions in an .ex5 file will enable you to share your know-how algorithms with other developers, set up common projects and promote them in the Web.

And while the MetaQuotes team spares no effort to bring about the possibility of direct inheritance of ex5 library classes, we are going to implement it right now.

Table of Contents

1. Export and Import of Functions
2. Export of the Hidden Implementation of a Class
3. Initialization of Variables in .ex5 File
4. Inheritance of Export Classes
5. Publishing of ex5 Libraries


1. Export and Import of Functions

This is a basic method underlying the export of classes. There are three key things that shall be taken into account for your functions to be available to other programs:

  1. The file to be created should have the extension .mq5 (not .mqh) in order to be compiled into .ex5 file;
  2. The file shall contain the #property library preprocessor directive;
  3. The key word "export" shall be put after the headers of the required exported functions
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(с);
}

After compiling this file, you will get the library.ex5 file from where the libfunc can then be used in another program.

The process of importing functions is also very simple. It is carried out using the #import  preprocessor directive.

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);
}

Bear in mind that the compiler will search for .ex5 files in the MQL5\Libraries folder. So if the library.ex5 is not located in that folder, you will have to specify the relative pathname.

E.g.:

#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

For your future use, functions can be imported not only into the target .mq5 file but also into .mqh files.

In order to illustrate the practical application, let us use some graphics.

We are going to create a library of functions for export. These functions will display graphical objects such as Button, Edit, Label and Rectangle Label on a chart, delete the objects from the chart and reset the color parameters of the chart.

This can be schematically shown as follows:

Class method export scheme

The complete file Graph.mq5 can be found at the end of the article. Here we will only give one template example of the drawing function 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,"");
  }

The import of the required functions and their use will be implemented in the target file 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); 
}

Following the run of the Expert Advisor, the objects will appear on the chart:

Example of using library objects

As can be seen, the process of exporting and importing functions is not at all difficult but be sure to read about certain limitations in the Help: export, import.


2. Export of the Hidden Implementation of a Class

Since classes in MQL5 cannot be exported directly as yet, we will have to resort to a somewhat fancy method. It is based on polymorphism and virtual functions. As a matter of fact, it is not the class itself that is returned from the ex5 module but a created object thereof. Let us call it the hidden implementation object.

The essence of the method is to divide the required class into two so that the declaration of functions and variables is open for public access and their implementation details are hidden in a closed .ex5 file.

This can be simply exemplified as below. There is CSpiro class which we would like to share with other developers without disclosing the implementation details. Assume, it contains variables, constructor, destructor and working functions.

In order to export the class, we shall do as follows:

  • Create a clone of the CSpiro class descendant. Let us call it ISpiro (the first letter C is replaced with I, as derived from the word "interface")
  • Leave all variables and dummy functions in the initial CSpiro class.
  • The function implementation details shall form a new ISpiro class.
  • Add to it the export function that will create an instance of the closed ISpiro.
  • Note! All required functions shall have the virtual prefix

As a result, we have two files:

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) { };
  };

Please note that all the function classes are declared with the key word 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);
  }
//+------------------------------------------------------------------+

As can be seen, the hidden class has been implemented in .mq5 file and contains the preprocessor command #property library. So all the rules set forth in the previous section have been observed.

Also note the scope resolution operator for the SetPoint function. It is declared both in the Graph library and CSpiro class. In order for the compiler to call the required function, we explicitly specify it using the :: action and give the file name.

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

We can now include the header file and import its implementation into our resulting Expert Advisor.

This can be schematically shown as follows:

Scheme for working with methods of the library classes

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
}

As a result, you will be able to change the object parameters in the chart and draw the chart of the object



Graphical object parameters


3. Initialization of Variables in .ex5 File

It is often the case that your ISuperClass uses variables from the include globals.mqh file. These variables can be included in a similar manner to be used in your other files.

E.g.:

Example 6. Public include file

//--- globals.mqh

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

The only instance of the _trade object is initialized in your program, yet it is used in the hidden ISuperClass class.

For this purpose, a pointer to the object you created shall be passed from the ISuperClass class to the .ex5 file.

It is easiest done when the object is received from the .ex5 file, as below:

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

Thus, all the required variables are initialized upon receipt of the object in its module.

In fact, there may be a lot of public global variables which may be of different types. Those who are not eager to change the header of the iSuperClass function all the time, should better create a special class aggregating all global variables and functions for working with it.

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; 
The ISuperClass.mq5 file will be implemented as follows:
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

The function call will now be transformed into a simplified and, what is most important, extensible form.

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. Inheritance of Export Classes

You must have already understood that this way of exporting objects implies that the direct and simple inheritance is out of the question. Export of the hidden implementation object suggests that the object itself is the last link of the inheritance chain and is the one that can ultimately be used.

In the general case you can create an "emulation" of inheritance by writing an additional intermediate class. And here we will of course need polymorphism and virtuality.

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); }; 
};

The only issue here is access to variables of CSuperClass. As can be seen, they are not present in declaration of the descendant and are located in the variable _base. Usually it does not affect the usability provided that there is a header class SuperClass.mqh.

Naturally, if you are mainly focused on know-how functions, you do not have to create a wrapper of ISuperClass with regard to them in advance. It will suffice to export those know-how functions and let the outside developers create their own wrapper classes which will then be easy to inherit.

Thus, when preparing your developments for other developers, you should care to create a whole set of necessary export functions, .mqh and .ex5 files and classes:
  1. Export of class independent functions
  2. Header .mqh files and their .ex5 implementations
  3. Initialization of variables in the .ex5 files


5. Publishing of ex5 Libraries

In November 2011, MetaQuotes started to provide access to a files repository. More on that can be found in the announcement.

This repository allows you to store your developments and, what is more important, to provide access thereto for other developers. This tool you will enable you to easily publish new versions of your files to ensure fast access to them for the developers who may be using these files.

Moreover, the company website gives you an opportunity to offer your own function libraries in the Market on a commercial basis or free of charge.


Conclusion

You now know how to create ex5 libraries with export of their functions or class objects and can apply your knowledge in practice. All these resources will allow you to establish a closer cooperation with other developers: to work on common projects, promote them in the Market or provide access to ex5 library functions.


Translated from Russian by MetaQuotes Software Corp.
Original article: https://www.mql5.com/ru/articles/362

Attached files |
spiro.zip (3.91 KB)
Last comments | Go to discussion (3)
Rashid Umarov
Rashid Umarov | 30 Mar 2012 at 11:18


You may be interested to know that MetaEditor 5 has the special command that allows you to generate include files of the exported functions in a current file.


Generating Include Files

MetaEditor has a special feature for generating include files (*.mqh) on the basis of exported functions of the current file. To start this function, you should execute the "Generate Include File Generate Include File" command of the "Tools" menu.

The Process of Generation

The generation of a mqh file is performed in the following way:

  • In the currently open file, all functions with the export post modifier are searched for.
  • An mqh file that contains the #import directive and the description of the functions found is generated.
  • If no exported functions are found, the corresponding dialog is displayed.
  • If the source file is located in the /Libraries folder, then the module name in the #import directive will contain the path relatively to the /Libraries directory. If the file is located in another directory, #import will include only the name of the ex5 file.

If the current file is an include file (*.mqh), the command of generation is disabled.

The Path of Generation

A folder where mqh files are generated depends on location of the source files:

  • If a source file is in the /Libraries directory or in one of its subfolders, the include file will be generated in the root of the /Include folder.
  • If a source file is in another folder, the include file will be generated in the same place.

Using MQH Files

The function of generating include files allows to easily share your functions with other users without exposing the source code. To do it, it's enough to provide the generated mqh file and the ex5 file where the functions are implemented.



Gabor Torma
Gabor Torma | 27 Oct 2012 at 10:04

Where is the source of "3. Initialization of Variables in .ex5 File"? (SuperClass.mqh; SuperClass.mq5, etc...)
Moses Olawale  Ajayi
Moses Olawale Ajayi | 27 Oct 2012 at 12:03
VincentX:

Where is the source of "3. Initialization of Variables in .ex5 File"? (SuperClass.mqh; SuperClass.mq5, etc...)
i ask thesame question
Step on New Rails: Custom Indicators in MQL5 Step on New Rails: Custom Indicators in MQL5

I will not list all of the new possibilities and features of the new terminal and language. They are numerous, and some novelties are worth the discussion in a separate article. Also there is no code here, written with object-oriented programming, it is a too serous topic to be simply mentioned in a context as additional advantages for developers. In this article we will consider the indicators, their structure, drawing, types and their programming details, as compared to MQL4. I hope that this article will be useful both for beginners and experienced developers, maybe some of them will find something new.

Here Comes the New MetaTrader 5 and MQL5 Here Comes the New MetaTrader 5 and MQL5

This is just a brief review of MetaTrader 5. I can't describe all the system's new features for such a short time period - the testing started on 2009.09.09. This is a symbolical date, and I am sure it will be a lucky number. A few days have passed since I got the beta version of the MetaTrader 5 terminal and MQL5. I haven't managed to try all its features, but I am already impressed.

Using text files for storing input parameters of Expert Advisors, indicators and scripts Using text files for storing input parameters of Expert Advisors, indicators and scripts

The article describes the application of text files for storing dynamic objects, arrays and other variables used as properties of Expert Advisors, indicators and scripts. The files serve as a convenient addition to the functionality of standard tools offered by MQL languages.

How to create an indicator of non-standard charts for MetaTrader Market How to create an indicator of non-standard charts for MetaTrader Market

Through offline charts, programming in MQL4, and reasonable willingness, you can get a variety of chart types: "Point & Figure", "Renko", "Kagi", "Range bars", equivolume charts, etc. In this article, we will show how this can be achieved without using DLL, and therefore such "two-for-one" indicators can be published and purchased from the Market.