Препроцессор

Препроцессор — это программа, которая берёт один тип данных и преобразует его в другой тип данных. По сути это подсистема компилятора 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
}