Используйте EX5-библиотеки для продвижения своих разработок
Введение
Для искушенного читателя не нужно объяснять назначение возможности сокрытия реализации функций и классов в библиотеках. Для тех, кто находится в активном поиске, скажем, что с помощью сокрытия реализации функций/классов в ex5-файл вы сможете делиться своими ноу-хау алгоритмами с другими программистами, создавать общие проекты и продвигать их в сети.
Пока команда MetaQuotes всеми силами приближает возможность прямого наследования классов из ex5‑библиотек, мы реализуем эту возможность уже сейчас.
Содержание
1. Экспорт и импорт функций
2. Экспорт скрытой реализации классов
3. Инициализация переменных в ex5-файле
4. Наследование экспортных классов
5. Публикация ex5-библиотек
1. Экспорт и импорт функций
Это базовая возможность, на которой основан и экспорт классов. Для того чтобы ваши функции были доступны другим программам, необходимо сделать три главные вещи:
- Создаваемый файл должен иметь расширение mq5 (а не mqh), чтобы он мог скомпилироваться в ex5-файл.
- В файле должен стоять команда препроцессора #property library
- После заголовков требуемых экспортируемых функции поставить ключевое слово 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-реализации
- Инициализацию переменных ex5 файлов
5. Публикация ex5-библиотек
С ноября 2011 года компания MetaQuotes предоставляет доступ к хранилищу файлов. Подробнее можно прочитать в анонсе.
В данном репозитории вы можете хранить свои разработки и главное – давать доступ другим программистам к этим разработкам. С помощью данного инструмента вы легко и быстро можете публиковать новые версии своих файлов, чтобы программисты, которые их используют, получали их как можно быстрее.
Также на сайте компании есть Маркет, где вы сможете предоставлять свои библиотеки функций на коммерческой основе или бесплатно.
Заключение
Итак, теперь вы знаете и умеете создавать ex5-библиотеки с экспортом своих функций или объектов классов. Все эти возможности позволят вам организовать более тесное сотрудничество с другими программистами: работать над общими проектами, продвигать их в Маркете или предоставлять доступ к библиотечным ex5-функциям.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
...
Is it planned to implement export for Class or something similar ?
Yes, but not now.
Ну хоть какая то возможность экспорта классов.
В МТ4 это сработает?
Ну хоть какая то возможность экспорта классов.
В МТ4 это сработает?
Спустя 7 лет это все еще «не сейчас».
Забыл про МТ4, это в прошлом.