English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Используйте EX5-библиотеки для продвижения своих разработок

Используйте EX5-библиотеки для продвижения своих разработок

MetaTrader 5Примеры | 6 января 2012, 10:21
3 967 18
---
---

Введение

Для искушенного читателя не нужно объяснять назначение возможности сокрытия реализации функций и классов в библиотеках. Для тех, кто находится в активном поиске, скажем, что с помощью сокрытия реализации функций/классов в ex5-файл вы сможете делиться своими ноу-хау алгоритмами с другими программистами, создавать общие проекты и продвигать их в сети.

Пока команда MetaQuotes всеми силами приближает возможность прямого наследования классов из ex5‑библиотек, мы реализуем эту возможность уже сейчас.

Содержание

1. Экспорт и импорт функций
2. Экспорт скрытой реализации классов
3. Инициализация переменных в ex5-файле
4. Наследование экспортных классов
5. Публикация ex5-библиотек


1. Экспорт и импорт функций

Это базовая возможность, на которой основан и экспорт классов. Для того чтобы ваши функции были доступны другим программам, необходимо сделать три главные вещи:

  1. Создаваемый файл должен иметь расширение mq5 (а не mqh), чтобы он мог скомпилироваться в ex5-файл.
  2. В файле должен стоять команда препроцессора #property library
  3. После заголовков требуемых экспортируемых функции поставить ключевое слово export
Пример 1. Создаем функцию для использования в других программах

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

После компиляции этого файла вы получаете файл library.ex5, из которого функцию libfunc можете импортировать в другой программе.

Процесс импорта тоже очень прост. Он выполняется с помощью команды препроцессора #import.

Пример 2. Используем экспортную функцию libfunc() в скрипте

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

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

Необходимо учесть, что ex5-файлы компилятор начинает искать из папки MQL5\Libraries. Если файл library.ex5 находится не в ней, то придется прописать относительный путь к нему.

Например:

#import "..\Include\MyLib\library.ex5" // файл находится в папке MQL5\Include\MyLib
#import "..\Experts\library.ex5" // файл находится в папке MQL5\Experts\

Импортировать функции можно не только в конечный mq5-файл, но и в mqh-файлы для дальнейшей работы.

В качестве наглядного практического примера сделаем работу с графикой.

Создадим библиотеку функций на экспорт. Эти функции будут выводить на указанный чарт графические объекты, а именно: Button, Edit, Label, Rectangle Label, также функцию очистки графика от объектов и сброс цветовых параметров чарта.

Схематически это можно представить следующим образом:

Схема экспорта методов класса

Полный файл Graph.mq5 можете взять внизу статьи. Здесь приведем только один шаблонный пример функции для рисования 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,"");
  }

В конечном файле Spiro.mq5 сделаем импорт требуемых функций и используем их:

Пример 3. Использование импортируемых функций

//--- Spiro.mq5 – конечный файл эксперта

//--- импортируем некоторые функции графики
#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

//--- префикс для объектов чарта
string sID; 
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+

void OnInit()
{
  HideChart(0, clrWhite);
  sID="spiro.";
  DrawParam();
}
//+------------------------------------------------------------------+
//| DrawParam                                                        |
//+------------------------------------------------------------------+
void DrawParam()
{
  color bgclr=clrWhite, clr=clrBlack;
//--- больший радиус   
  SetLabel(0, sID+"stR.", 0, "R", clr, 10, 10+3);
  SetEdit(0, sID+"R.", 0, "100", clr, bgclr, clr, 40, 10, 50, 20);
//--- меньший радиус   
  SetLabel(0, sID+"str.", 0, "r", clr, 10, 35+3);
  SetEdit(0, sID+"r.", 0, "30", clr, bgclr, clr, 40, 35, 50, 20);
//--- расстояние от центра
  SetLabel(0, sID+"stD.", 0, "D", clr, 10, 60+3);
  SetEdit(0, sID+"D.", 0, "40", clr, bgclr, clr, 40, 60, 50, 20);
//--- точность отрисовки
  SetLabel(0, sID+"stA.", 0, "Alfa", clr, 10, 85+3); 
  SetEdit(0, sID+"A.", 0, "0.04", clr, bgclr, clr, 40, 85, 50, 20);
//--- точность отрисовки
  SetLabel(0, sID+"stN.", 0, "Rotor", clr, 10, 110+3); 
  SetEdit(0, sID+"N.", 0, "10", clr, bgclr, clr, 40, 110, 50, 20);
//--- кнопка отрисовки
  SetButton(0, sID+"draw.", 0, "DRAW", bgclr, clr, 39, 135, 51, 20); 
}

В результате запуска эксперта на чарте появятся объекты:

Пример использования объектов библиотеки

Как видите, экспорт и импорт функций не является сложным, но о некоторых их ограничениях обязательно прочтите в справке: export, import.


2. Экспорт скрытой реализации классов

Прямого экспорта классов в MQL5 пока что нет, поэтому приходится прибегать к немного изощренному способу. Он основан на свойстве полиморфизма и виртуальности функций. В действительности из ex5-модуля возвращается не сам класс, а его созданный объект. Назовем его объект скрытой реализации.

Суть метода заключается в том, чтобы разделить требуемый класс на два класса таким образом, чтобы объявление функций и переменных было в открытом доступе, а их реализация в закрытом ex5-файле.

Покажем, как это делается на простом примере. У нас имеется класс CSpiro, работой которого мы хотим поделиться с другими программистами, но его реализацию не желаем показывать. Пусть в нем имеются переменные, конструктор, деструктор и рабочие функции.

Для создания экспорта нам необходимо сделать следующее:

  • Создать клон-потомка от CSpiro класса. Назовем его ISpiro (первая буква класса заменена с C на I, от слова interface)
  • Все переменные и пустышки функции оставить в исходном CSpiro.
  • Все реализации функций вынести в новый ISpiro класс.
  • В нем же добавить экспортную функцию, которая создаст экземпляр закрытого ISpiro.
  • Важно! Все требуемые функции должны иметь приставку virtual

В результате имеем два файла:

Пример 4. Сокрытие реализации класса в ex5-модуле

//--- Spiro.mqh – открытый файл для общего доступа, так называемый "заголовочный" файл

//+------------------------------------------------------------------+
//| Class CSpiro                                                     |
//| Класс рисования спирографа                                       |
//+------------------------------------------------------------------+
class CSpiro
  {
public:
   //--- префикс объектов чарта
   string            m_sID;
   //--- смещение центра графика
   int               m_x0,m_y0;
   //--- цвет линии
   color             m_clr;
   //--- параметры графика
   double            m_R,m_r,m_D,m_dAlfa,m_nRotate;

public:
   //--- конструктор
                     CSpiro() { };
   //--- деструктор
                    ~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) { };
  };

Обратите внимание, что все функции класса объявлены с ключевым словом virtual.

//--- ISpiro.mq5 – файл скрытой реализации

#include "Spiro.mqh"

//--- импортируем некоторые функции
#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                                                     |
//| Класс рисования спирографа                                       |
//+------------------------------------------------------------------+
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("Ошибка! r==0"); return; }
   if(m_D<=0) { Print("Ошибка! D==0"); return; }
   if(m_dAlfa==0) { Print("Ошибка! 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);
  }
//+------------------------------------------------------------------+

Как вы можете заметить, скрытый класс реализован в mq5-файле и в нем присутствует команда препроцессора #property library. То есть, по всем правилам, как описано в предыдущем разделе.

Также обратите внимание на разрешение контекста видимости функции SetPoint. Ее объявление присутствует как в библиотеке Graph так и в CSpiro. Чтобы компилятор вызвал нужную нам функцию мы открыто говорим ему про это с помощью операции :: и указываем имя файла.

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

Теперь в наш итоговый эксперт подключим заголовочный файл и импортируем его реализацию.

Схематически это можно изобразить следующим образом:

Схема работы с методами классов библиотеки

Пример 5. Использование экспортных объектов

//--- Spiro.mq5 - конечный файл эксперта

//--- импортируем некоторые функции
#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

//--- подключаем класс графика
#include <Spiro.mqh> 

//--- импортируем объект
#import "ISpiro.ex5"
  CSpiro *iSpiro();
#import

//--- экземпляр объекта
CSpiro *spiro; 
//--- префикс для объектов чарта
string sID; 
//+------------------------------------------------------------------+
//| Expert initialization function                                   |
//+------------------------------------------------------------------+
void OnInit()
{
  HideChart(0, clrWhite);
  sID="spiro.";
  DrawParam();
//--- создали экземпляр объекта
  spiro=iSpiro(); 
//--- инициализируем отрисовку
  spiro.Init(250, 200, clrBlack, sID);
//--- задаем параметры расчета
  spiro.SetData(100, 30, 40, 0.04, 10);
//--- отрисовываем
  spiro.DrawSpiro(); 
}
//+------------------------------------------------------------------+
//| Expert deinitialization function                                 |
//+------------------------------------------------------------------+
void OnDeinit(const int reason)
{
  delete spiro; // удаляем объект
}

В результате на чарте вы сможете менять параметры объекта и строить его график



Параметры графического объекта


3. Инициализация переменных в ex5-файле

Часто встречается ситуация, когда ваш ISuperClass использует переменные из подключаемого файла globals.mqh. Эти переменные аналогично подключаются и используются в ваших других файлах.

Например:

Пример 6. Общий подключаемый файл

//--- globals.mqh

#include <Trade\Trade.mqh>
//--- экземпляр объекта торговых функций
extern CTrade *_trade; 

Единственный экземпляр объекта _trade инициализируется в вашей программе, а используется в скрытом классе ISuperClass.

Для этого в ex5-файл с ISuperClass необходимо передать указатель на созданный вами объект.

Проще всего выполнить это в момент получения объекта из ex5-файла, то есть здесь:

Пример 7. Инициализация переменных при создании объекта

//--- ISuperClass.mq5 –файл скрытой реализации

#property library
CSuperClass *iSuperClass(CTrade *atrade) export
{
//--- сохранили указатель
   _trade=atrade; 
//--- вернули объект скрытой реализации ISuperClass открытого класса CSuperClass
  return(new ISuperClass); 
}
//... остальной код

Таким образом, при получении объекта мы инициализируем в его модуле все требуемые переменные.

В реальности общих глобальных переменных бывает много. При этом они могут быть разных типов. Все время менять заголовок функции iSuperClass нет желания, поэтому лучше всего сделать специальный класс-агрегатор всех глобальных переменных и функции для работы с ним.

Пример 8. Общий подключаемый файл

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

//--- торговый "объект"
extern CTrade *_trade; 
//--- имя эксперта системы
extern string _eaname; 

//+------------------------------------------------------------------+
//| class __extern                                                   |
//+------------------------------------------------------------------+
class __extern // здесь собраны все extern параметры для передачи между ex5-модулями
{
public:
//--- список всех общих глобальных переменных, которые будете передавать
//--- торговый "объект"
  CTrade *trade; 
//--- имя эксперта системы
  string eaname; 
    
public:
  __extern() { };
  ~__extern() { };

//--- вызывается при передаче параметров в ex5-файл
  void Get() { trade=_trade; eaname=_eaname; };  // получили переменные

 //--- вызывается в ex5-файле
  void Set() { _trade=trade; _eaname=eaname; };  // установили переменные
                                                       
};
//--- получение переменных и указателя для передачи объекта в ex5 файл
__extern *_GetExt() { _ext.Get(); return(GetPointer(_ext)); } 

//--- единственный экземпляр для работы
extern __extern _ext; 
Файл ISuperClass.mq5 получит такую реализацию:
Пример 9.

//--- ISuperClass.mq5 –файл скрытой реализации

#property library
CSuperClass *iSuperClass(__extern *aext) export
{
//--- забрали все параметры себе
  aext.Set();
//--- вернули объект
  return(new ISuperClass); 
}
//--- ... остальной код

Вызов функции теперь получит такой упрощенный и главное расширяемый вариант.

Пример 10. Использование экспортных объектов при наличии общих глобальных переменных

//--- подключаем глобальные переменные (обычно находится в SuperClass.mqh)
#include "globals.mqh"    

//--- подключаем открытый заголовочный класс
#include "SuperClass.mqh" 
//--- получаем объект скрытой реализации
#import "ISuperClass.ex5"
  CSuperClass *iSuperClass();
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
//--- создали объект скрытой реализации с передачей всех параметров
  CSuperClass *sc=iSuperClass(_GetExt()); 
  //--- ... остальной код
}


4. Наследование экспортных классов

Как вы уже поняли, при таком виде экспорта объектов, ни о каком прямом и простом наследовании речи быть не может. Экспорт объекта скрытой реализации подразумевает, что этот самый объект является самым последним в цепочке наследования и он тот, которым можно пользоваться в конечном итоге.

В общем случае можно создать "эмуляцию" наследования, написав дополнительный промежуточный класс. И конечно же, здесь нам поможет полиморфизм и виртуальность.

Пример 11. Эмуляция наследования скрытых классов

//--- подключаем открытый заголовочный класс
#include "SuperClass.mqh" 

//--- получаем объект скрытой реализации
#import "ISuperClass.ex5"
  CSuperClass *iSuperClass();
#import

class _CSuperClass
{
public:
//--- экземпляр объекта скрытой реализции
  CSuperClass *_base;
public:
//--- конструктор
  _CSuperClass() {  _base=iSuperClass(_GetExt()); };
//--- деструктор
  ~_CSuperClass() { delete _base; };
//--- далее идут все функции базового CSuperClass
//--- рабочая функция, вызванная из объекта скрытой реализации
  virtual int func(int a, int b) { _base.func(a,b); }; 
};

Единственный момент в работе такой связки – это доступ к переменным CSuperClass. Как вы видите, они отсутствуют в объявлении наследника и находятся в переменной _base. Обычно это не влияет на удобство использования, если вы имеете заголовочный класс SuperClass.mqh.

Конечно, если ваша основная работа будет заключена именно в ноу-хау функциях, то делать заранее для них обертку ISuperClass необязательно. Достаточно сделать экспорт тех самых ноу-хау функций и предоставить стороннему программисту самому создавать свои оберточные классы, которые потом можно будет легко наследовать.

Таким образом, подготавливая свои разработки для других программистов, вы должны позаботиться и создать весь необходимый набор экспортных функций, mqh, ex5-файлов и классов:
  1. Экспорт функций, которые независимы от классов
  2. Заголовочные файлы mqh и ex5-реализации
  3. Инициализацию переменных ex5 файлов


5. Публикация ex5-библиотек

С ноября 2011 года компания MetaQuotes предоставляет доступ к хранилищу файлов. Подробнее можно прочитать в анонсе.

В данном репозитории вы можете хранить свои разработки и главное – давать доступ другим программистам к этим разработкам. С помощью данного инструмента вы легко и быстро можете публиковать новые версии своих файлов, чтобы программисты, которые их используют, получали их как можно быстрее.

Также на сайте компании есть Маркет, где вы сможете предоставлять свои библиотеки функций на коммерческой основе или бесплатно.


Заключение

Итак, теперь вы знаете и умеете создавать ex5-библиотеки с экспортом своих функций или объектов классов. Все эти возможности позволят вам организовать более тесное сотрудничество с другими программистами: работать над общими проектами, продвигать их в Маркете или предоставлять доступ к библиотечным ex5-функциям.


Прикрепленные файлы |
spiro.zip (4.42 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (18)
Alain Verleyen
Alain Verleyen | 26 авг. 2014 в 10:04
Renat:
...
Is it planned to implement export for Class or something similar ?
MetaQuotes
Renat Fatkhullin | 26 авг. 2014 в 10:14
angevoyageur:
Is it planned to implement export for Class or something similar ?
Yes, but not now.
Alain Verleyen
Alain Verleyen | 26 авг. 2014 в 10:16
Renat:
Yes, but not now.
Thank you.
Alexandr Gavrilin
Alexandr Gavrilin | 19 нояб. 2021 в 07:04

Ну хоть какая то возможность экспорта классов. 

В МТ4 это сработает?

Alain Verleyen
Alain Verleyen | 19 нояб. 2021 в 13:56
Alexandr Gavrilin # :

Ну хоть какая то возможность экспорта классов. 

В МТ4 это сработает?

Спустя 7 лет это все еще «не сейчас».

Забыл про МТ4, это в прошлом.

Создай свои графические панели на MQL5 Создай свои графические панели на MQL5
Удобство пользования MQL5-программой определяется не только её богатой функциональностью, но и продуманным графическим интерфейсом. Визуальное восприятие иногда гораздо важнее, чем быстрая и стабильная работа. Перед вами пошаговое руководство по самостоятельному созданию индикаторных панелей на основе классов Стандартной библиотеки.
Объектно-ориентированный подход к построению мультитаймфреймовых и мультивалютных панелей Объектно-ориентированный подход к построению мультитаймфреймовых и мультивалютных панелей
В статье рассказывается об использовании объектно-ориентированного подхода для разработки для создания мультитаймфреймовых и мультивалютных панелей в MetaTrader 5. Основной целью является построение универсальной панели, которая может быть использована для отображения различных типов данных (цены, их изменения, значения индикаторов или текущее состояние условий на покупку и продажу) без изменения кода самой панели.
Простейшие торговые системы с использованием семафорных индикаторов Простейшие торговые системы с использованием семафорных индикаторов
Если разобраться досконально в любой сложной торговой системе, то мы увидим, что в основе её лежит набор простых торговых сигналов. Поэтому начинающему разработчику торговых роботов не стоит сразу же приниматься за написание сложных алгоритмов. В статье приводится пример торговой системы, использующей для осуществления сделок семафорные индикаторы.
Интервью с Андреа Дзани (ATC 2011) Интервью с Андреа Дзани (ATC 2011)
На предпоследней неделе участник чемпионата Андреа Дзани (sbraer) вплотную подобрался к пятерке лидеров чемпионата. Он занимает 6-е место с результатом около 47 000 USD. За все время чемпионата советник Андреа "AZXY" совершил только одну убыточную сделку в самом начале. С тех пор его кривая эквити неуклонно растет.