Препроцессор — это программа, которая берёт один тип данных и преобразует его в другой тип данных. По сути это подсистема компилятора MQL5, которая занимается предварительной компоновкой исходного текста программы непосредственно перед ее компиляцией. Препроцессор не производит синтаксического анализа текста исходного кода, но разбивает его на токены для обнаружения вызовов макросов. Хотя компилятор обычно вызывает препроцессор при первом проходе, препроцессор можно также вызвать отдельно для обработки текста без его компиляции.
Препроцессор позволяет улучшить читаемость исходного кода. Структурирование кода может быть достигнуто путем включения отдельных файлов с исходными кодами mql5-программ. Улучшению читаемости кода способствует и возможность присвоения мнемонических имен отдельным константам.
Препроцессор позволяет также определять специфические параметры mql5-программ:
- Объявлять константы
- Устанавливать свойства программы
- Включать в текст программы файлы
- Импортировать функции
- Использовать условную компиляцию
Если в качестве первого символа в строке программы используется символ #, то эта строка является директивой препроцессора. Директива препроцессора заканчивается символом перевода на новую строку.
Макроподстановка (#define, #undef)
Директива #define создает макрос, представляющий собой ассоциацию обычного или параметризованного идентификатора со строкой токена. После определения макроса компилятор может подставить строку токена для каждого обнаруженного идентификатора в исходном файле.
Существует две формы:
#define identifier expression // беспараметрическая форма #define identifier(par1,... par8) expression // параметрическая форма
Директива #define подставляет expression вместо всех последующих найденных вхождений identifier в исходном тексте. identifier заменяется только в том случае, если он представляет собой отдельный токен. identifier не заменяется, если он является частью комментария, частью строки, или частью другого более длинного идентификатора.
Идентификатор константы подчиняется тем же правилам, что и для имен переменных. Значение может быть любого типа:
#define ABC 100 #define PI 3.14 #define COMPANY_NAME "MetaQuotes Software Corp." ... void ShowCopyright() { Print("Copyright 2001-2017, ",COMPANY_NAME); Print("https://www.metaquotes.net"); }
expression может состоять из нескольких токенов, таких как ключевые слова, константы, константные и неконстантные выражения. expression заканчивается с концом строки и не может быть перенесено на следующую строку.
Пример:
#define TWO 2 #define THREE 3 #define INCOMPLETE TWO+THREE #define COMPLETE (TWO+THREE) void OnStart() { Print("2 + 3*2 = ",INCOMPLETE*2); Print("(2 + 3)*2 = ",COMPLETE*2); } /* Результат 2 + 3*2 = 8 (2 + 3)*2 = 10 */
Параметрическая форма #define
При параметрической форме все последующие найденные вхождения identifier будут заменены на expression с учетом фактических параметров. Например,
// пример с двумя параметрами a и b #define A 2+3 #define B 5-1 #define MUL(a, b) ((a)*(b)) double c=MUL(A,B); Print("c=",c); /* выражение double c=MUL(A,B); равносильно double c=((2+3)*(5-1)); */ // Результат // c=20
Обязательно заключайте параметры в круглые скобки при использовании параметров в expression, так как это позволит избежать не очевидных ошибок, которые трудно найти. Если переписать пример без использования скобок, то результат окажется совсем другой:
// пример с двумя параметрами a и b #define A 2+3 #define B 5-1 #define MUL(a, b) a*b double c=MUL(A,B); Print("c=",c); /* выражение double c=MUL(A,B); равносильно double c=2+3*5-1; */ // Результат // c=16
При использовании параметрической формы допускается не более 8 параметров.
// правильная параметрическая форма #define LOG(text) Print(__FILE__,"(",__LINE__,") :",text) // один параметр - 'text' // неправильная параметрическая форма #define WRONG_DEF(p1, p2, p3, p4, p5, p6, p7, p8, p9) p1+p2+p3+p4 // более 8 параметров от p1 до p9
Директива #undef
Директива #undef удаляет текущее определение идентификатора. Следовательно, последующие вхождения идентификатора игнорируются препроцессором. Чтобы удалить определение макроса с помощью #undef, присвойте только идентификатор макроса; не предоставляйте список параметров.
Также можно применить директиву #undef к идентификатору, который не имеет предыдущего определения. Это гарантирует, что идентификатор не определен. Замена макроса не выполняется внутри инструкций #undef.
Директива #undef обычно связана с директивой #define для создания области в программе-источнике, в которой идентификатор имеет специальное значение. Например, определенная функция программы-источника может использовать константы манифестов для определения значений среды, которые не влияют на остальные части программы.
Пример:
#define MACRO void func1() { #ifdef MACRO Print("MACRO is defined in ",__FUNCTION__); #else Print("MACRO is not defined in ",__FUNCTION__); #endif } #undef MACRO void func2() { #ifdef MACRO Print("MACRO is defined in ",__FUNCTION__); #else Print("MACRO is not defined in ",__FUNCTION__); #endif } void OnStart() { func1(); func2(); } /* Результат: MACRO is defined in func1 MACRO is not defined in func2 */
Свойства программ (#property)
У каждой программы, написанной на языке MQL5 можно указать дополнительные определённые параметры #property, которые помогают клиентскому терминалу правильно обслуживать программы без необходимости их явного запуска. В первую очередь это касается внешних настроек индикаторов. Свойства, описанные во включаемых файлах, полностью игнорируются. Свойства необходимо задавать в главном mq5-файле.
#property идентификатор значение
Компилятор запишет в настройках выполняемого модуля объявленные значения.
Константа |
Тип |
Описание |
icon |
string |
Путь к файлу с картинкой, которая будет показываться для программы EX5. Правила указания пути такие же, как и для ресурсов. Свойство должно указываться в главном модуле с исходным кодом MQL5. Файл иконки должен быть в формате ICO. |
link |
string |
Ссылка на сайт компании-производителя |
copyright |
string |
Название компании-производителя |
version |
string |
Версия программы, не более 31 символа |
description |
string |
Краткое текстовое описание mql5-программы. Может присутствовать несколько description, каждый из которых описывает одну строку текста. Общая длина всех description не может превышать 511 символов с учетом переводов строк |
stacksize |
int |
Указывает размер стека для MQL5 программы, стек достаточного объема требуется в случае выполнения рекурсивных вызовов функций. При запуске скрипта или эксперта на графике выделяется стек не менее 8Мб, для индикаторов свойство не работает — стек всегда фиксированного объема в 1Мб. При запуске в тестере программе всегда выделяется стек в размере 16 Мб. |
library |
|
Библиотека; не назначается никакой стартовой функции, функции с модификатором export можно импортировать в других mql5-программах |
indicator_applied_price |
int |
Задает значение по умолчанию для поля «Apply to». Можно задавать одно из значений перечисления ENUM_APPLIED_PRICE. Если свойство не задано, то по умолчанию применяется значение PRICE_CLOSE |
indicator_chart_window |
|
Выводить индикатор в окно графика |
indicator_separate_window |
|
Выводить индикатор в отдельное окно |
indicator_height |
int |
Фиксированная высота подокна индикатора в пикселях (свойство INDICATOR_HEIGHT) |
indicator_buffers |
int |
Количество буферов для расчета индикатора |
indicator_plots |
int |
Количество графических серий в индикаторе |
indicator_minimum |
double |
Нижнее ограничение шкалы отдельного окна индикатора |
indicator_maximum |
double |
Верхнее ограничение шкалы отдельного окна индикатора |
indicator_labelN |
string |
Задает метку для N-ой графической серии, отображаемую в окне DataWindow. Для графических серий, требующих несколько индикаторных буферов (DRAW_CANDLES, DRAW_FILLING и другие), имена меток задаются через разделитель ‘;’. |
indicator_colorN |
color |
Цвет для вывода линии N, где N — номер графической серии; нумерация с 1 |
indicator_widthN |
int |
Толщина линии в графической серии, где N — номер графической серии; нумерация с 1 |
indicator_styleN |
int |
Стиль линии в графической серии, указываемый с помощью значения из ENUM_LINE_STYLE. N — номер графической серии, нумерация с 1 |
indicator_typeN |
int |
Вид графического построения, указываемый с помощью значения из ENUM_DRAW_TYPE. N — номер графической серии, нумерация с 1 |
indicator_levelN |
double |
Горизонтальный уровень N в отдельном окне индикатора |
indicator_levelcolor |
color |
Цвет горизонтальных уровней индикатора |
indicator_levelwidth |
int |
Толщина горизонтальных уровней индикатора |
indicator_levelstyle |
int |
Стиль горизонтальных уровней индикатора |
script_show_confirm |
|
Выводить окно подтверждения перед запуском скрипта |
script_show_inputs |
|
Выводить окно со свойствами перед запуском скрипта и запретить вывод окна подтверждения |
tester_indicator |
string |
Имя пользовательского индикатора в формате «имя_индикатора.ex5″. Необходимые для тестирования индикаторы определяются автоматически из вызова функций iCustom(), если соответствующий параметр задан константной строкой. Для остальных случаев (использование функции IndicatorCreate() или использование неконстантной строки в параметре, задающем имя индикатора) необходимо данное свойство |
tester_file |
string |
Имя файла для тестера с указанием расширения, заключенное в двойные кавычки (как константная строка). Указанный файл будет передан тестеру в работу. Входные файлы для тестирования, если необходимы, должны указываться всегда |
tester_library |
string |
Имя библиотеки с расширением, заключенное в двойные кавычки. Библиотека может быть как с расширением dll, так и с расширением ex5. Необходимые для тестирования библиотеки определяются автоматически. Однако если какая-либо библиотека используется пользовательским индикатором, то необходимо использовать данное свойство |
Пример задания описания и номера версии
#property version "3.70" // текущая версия эксперта #property description "ZigZag универсальный с паттернами Песавенто" #property description "В настоящий момент в индикатор встроены несколько ZigZag с различными алгоритмами" #property description "Имеется возможность встраивать большое количество других индикаторов, показывающих максимумы и " #property description "минимумы и автоматически строить от этих минимумов и максимумов различные графические инструменты"
Пример указания отдельной метки для каждого индикаторного буфера («C open;C high;C low;C close»)
#property indicator_chart_window #property indicator_buffers 4 #property indicator_plots 1 #property indicator_type1 DRAW_CANDLES #property indicator_width1 3 #property indicator_label1 "С open;С high;С low;С close"
Включение файлов (#include)
Указывает препроцессору, что содержимое заданного файла необходимо обработать так, как если бы оно находилось в исходной программе в той точке, в которой располагается эта директива. Командная строка #include может встречаться в любом месте программы, но обычно все включения размещаются в начале файла исходного текста. Формат вызова:
#include <имя_файла>
#include «имя_файла»
Примеры:
#include <WinUser32.mqh> #include "mylib.mqh"
Препроцессор заменяет строку #include <имя_файла> содержимым файла WinUser32.mqh. Угловые скобки обозначают, что файл WinUser32.mqh будет взят из стандартного каталога (обычно это каталог_терминала\MQL5\Include). Текущий каталог не просматривается.
Если имя файла заключено в кавычки, то поиск производится в текущем каталоге (в котором содержится основной файл исходного текста). Cтандартный каталог не просматривается.
Импорт функций (#import)
Импорт функций осуществляется из откомпилированных модулей MQL5 (файлы *.ex5) и из модулей операционной системы (файлы *.dll). Имя модуля указывается в директиве #import. Для того чтобы компилятор мог правильно оформить вызов импортируемой функции и организовать правильную передачу параметров, необходимо полное описание функций. Описания функций следуют непосредственно за директивой #import «имя модуля». Новая команда #import (можно без параметров) завершает блок описания импортируемых функций.
#import «имя_файла»
func1 define;
func2 define;
…
funcN define;
#import
Импортируемые функции могут иметь любые имена. Можно одновременно импортировать из разных модулей функции с одинаковыми именами. Импортируемые функции могут иметь имена, совпадающие с именами встроенных функций. Операция разрешения контекста определяет, какая из функций должна вызываться.
Порядок поиска файла, указанного после ключевого слова #import, описан в разделе Вызов импортируемых функций.
Так как импортируемые функции находятся вне компилируемого модуля, компилятор не может проверить правильность передаваемых параметров. Поэтому, во избежание ошибок выполнения, необходимо точно описывать состав и порядок параметров, передаваемых в импортируемые функции. Параметры, передаваемые в импортируемые функции (как из EX5, так и из DLL-модулей), могут иметь значения по умолчанию.
В импортируемых функциях в качестве параметров нельзя использовать:
указатели (*);
ссылки на объекты, содержащие динамические массивы и/или указатели.
В импортируемые из DLL функции нельзя передавать в качестве параметра классы, массив строк или сложные объекты, содержащие строки и/или динамические массивы любых типов.
Примеры:
#import "user32.dll" int MessageBoxW(uint hWnd,string lpText,string lpCaption,uint uType); #import "stdlib.ex5" string ErrorDescription(int error_code); int RGB(int red_value,int green_value,int blue_value); bool CompareDoubles(double number1,double number2); string DoubleToStrMorePrecision(double number,int precision); string IntegerToHexString(int integer_number); #import "ExpertSample.dll" int GetIntValue(int); double GetDoubleValue(double); string GetStringValue(string); double GetArrayItemValue(double &arr[],int,int); bool SetArrayItemValue(double &arr[],int,int,double); double GetRatesItemValue(double &rates[][6],int,int,int); #import
Для импорта функций во время выполнения mql5-программы используется раннее связывание. Это значит, что библиотека загружается в процессе загрузки использующей ее ex5-программы.
Не рекомендуется использовать полностью квалифицированное имя загружаемого модуля вида Drive:\Directory\FileName.Ext. Библиотеки MQL5 загружаются из папки terminal_dir\MQL5\Libraries.
Условная компиляция (#ifdef, #ifndef, #else, #endif)
Директивы условной компиляции препроцессора позволяют компилировать или пропускать часть программы в зависимости от выполнения некоторого условия.
Условие может принимать одну из описываемых ниже форм.
#ifdef identifier // код, находящийся здесь, компилируется, если identifier уже был определен для препроцессора в команде #define. #endif
#ifndef identifier // код, находящийся здесь, компилируется, если identifier в данный момент не определен командой препроцессора #define. #endif
За любой из команд условной компиляции может следовать произвольное число строк, содержащих, возможно, команду вида #else и заканчивающихся #endif. Если проверяемое условие справедливо, то строки между #else и #endif игнорируются. Если же проверяемое условие не выполняется, то игнорируются все строки между проверкой и командой #else, а если ее нет, то командой #endif.
Пример:
#ifndef TestMode #define TestMode #endif //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { #ifdef TestMode Print("Test mode"); #else Print("Normal mode"); #endif }
В зависимости от типа программы и режима компиляции стандартные макросы определяются следующим образом:
Макрос __MQL5__ доступен при компиляции файла *.mq5, при компиляции *.mq4 доступен макрос __MQL4__.
Макрос _DEBUG доступен при компиляции под отладку.
Макрос _RELEASE доступен при компиляции не под отладку.
Пример:
//+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { #ifdef __MQL5__ #ifdef _DEBUG Print("Hello from MQL5 compiler [DEBUG]"); #else #ifdef _RELEASE Print("Hello from MQL5 compiler [RELEASE]"); #endif #endif #else #ifdef __MQL4__ #ifdef _DEBUG Print("Hello from MQL4 compiler [DEBUG]"); #else #ifdef _RELEASE Print("Hello from MQL4 compiler [RELEASE]"); #endif #endif #endif #endif }