Получение общего списка свойств терминала и программы

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

Как мы увидим далее, данный подход часто применяется в MQL5 API и в других, в том числе прикладных областях. В частности, похожие наборы функций используются для получения свойств торгового счета и финансового инструмента.

Для описания среды достаточно свойств трех простых типов: int, double, string. При этом с помощью значений int представлены не только целочисленные свойства, но и логические флаги (в частности, разрешения/запреты, наличие сетевого соединения и т.д.), а также прочие встроенные перечисления (например, типы MQL-программ и типы лицензий).

С учетом условного деления на свойства терминала и свойства конкретной MQL-программы, существуют следующие функции, описывающие среду.

int MQLInfoInteger(ENUM_MQL_INFO_INTEGER p)

int TerminalInfoInteger(ENUM_TERMINAL_INFO_INTEGER p)

double TerminalInfoDouble(ENUM_TERMINAL_INFO_DOUBLE p)

string MQLInfoString(ENUM_MQL_INFO_STRING p)

string TerminalInfoString(ENUM_TERMINAL_INFO_STRING p)

Данные прототипы задают соответствие типов значений и перечислений. Например, "терминальные" свойства типа int сведены в ENUM_TERMINAL_INFO_INTEGER, а его же свойства типа double перечислены в ENUM_TERMINAL_INFO_DOUBLE, и т.д. Список доступных перечислений и их элементов можно найти в документации, в разделах, посвященных свойствам терминала и MQL-программы.

В следующих разделах мы рассмотрим все свойства, группируя их по назначению. Здесь же обратимся к задаче получения общего списка всех существующих свойств и их значений. Это часто бывает необходимо для выявления "узких мест" или особенностей работы MQL-программ на конкретных экземплярах терминала. Довольно распространена ситуация, когда на одном компьютере MQL-программа работает, а на другом — не работает совсем или работает с отклонениями.

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

В разделе Перечисления мы создали шаблонную функцию EnumToArray для получения полного списка элементов перечисления (файл EnumToArray.mqh). Также в том разделе был представлен скрипт ConversionEnum.mq5, использующий указанный заголовочный файл. В скрипте была реализована вспомогательная функция process, которая получала массив с кодами элементов перечисления и выводила их в журнал. Мы возьмем эти наработки в качестве отправной точки для дальнейшего усовершенствования.

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

Дадим новой версии скрипта имя Environment.mq5.

Поскольку свойства среды рассредоточены по нескольким разным функциям (в данном случае, пяти), необходимо научиться передавать в новую версию функции process указатель на требуемую встроенную функцию (см. раздел Указатели на функции (typedef)). Однако MQL5 не позволяет присвоить указателю на функцию адрес встроенной функции. Это можно сделать только с прикладной функцией, реализованной на MQL5. Поэтому мы создадим функции-обертки. Например:

int _MQLInfoInteger(const ENUM_MQL_INFO_INTEGER p)
{
   return MQLInfoInteger(p);
}
// пример описания типа указателя   
typedef int (*IntFuncPtr)(const ENUM_MQL_INFO_INTEGER property);
// инициализация переменных-указателей
IntFuncPtr ptr1 = _MQLInfoInteger;  // ok
IntFuncPtr ptr2 = MQLInfoInteger;   // ошибка компиляции

Выше показан "двойник" для MQLInfoInteger (очевидно, он должен иметь отличное, но — желательно, похожее имя). Остальные функции "упаковываются" аналогичным образом. Всего их получится пять.

Если в старой версии process был только один параметр шаблона, задающий перечисление, то в новой нам необходимо также передать тип возвращаемого значения (поскольку MQL5 не "понимает" слов в названии перечислений): хоть в имени ENUM_MQL_INFO_INTEGER и присутствует окончание "INTEGER", компилятор не способен увязать его с типом int).

Однако помимо увязки типов возвращаемого значения и перечисления нам требуется каким-то образом передать в функцию process указатель на соответствующую функцию-обертку (одну из пяти, определенных нами ранее). Ведь компилятор не умеет сам определять по аргументу, например, типа ENUM_MQL_INFO_INTEGER, что нужно вызвать MQLInfoInteger.

Для решения данной проблемы была создана специальная шаблонная структура, соединяющая в себе все три фактора воедино.

template<typename E,typename R>
struct Binding
{
public:
   typedef R (*FuncPtr)(const E property);
   const FuncPtr f;
   Binding(FuncPtr p): f(p) { }
};

Два параметра шаблона позволяют определить тип указателя функции (FuncPtr) с требуемым сочетанием результата и входного параметра. В экземпляре структуры имеется поле f для указателя этого, только что определенного типа.

Теперь новая версия функции process может быть описана следующим образом.

template<typename E,typename R>
void process(Binding<E,R> &b)
{
   E e = (E)0// отключаем предупреждение об отсутствии инициализации
   int array[];
   // получаем перечень элементов перечисления в массив
   int n = EnumToArray(earray0USHORT_MAX);
   Print(typename(E), " Count="n);
   ResetLastError();
   // для каждого элемента выводим название и значение,
   // получаемое через вызов указателя в структуре Binding
   for(int i = 0i < n; ++i)
   {
      e = (E)array[i];
      R r = b.f(e); // вызываем функцию, потом анализируем _LastError
      const int snapshot = _LastError;
      PrintFormat("% 3d %s=%s"iEnumToString(e), (string)r +
         (snapshot != 0 ? E2S(snapshot) + " (" + (string)snapshot + ")" : ""));
      ResetLastError();
   }
}

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

Данная версия алгоритма выводит в журнал порядковый номер, идентификатор свойства и его значение. Еще раз обратим внимание на то, что первое число в каждой записи будет содержать порядковый номер элемента в перечислении, а не значение (значения могут быть назначены элементам с пропусками). При необходимости вы можете добавить вывод переменной e "в чистом виде" внутри инструкции PrintFormat.

Кроме того вы можете модифицировать process, чтобы она собирала в массив (или другой контейнер, такой как карта (map)) получаемые значения свойств и возвращала их "наружу".

Было бы потенциальной ошибкой обращаться к указателю функции непосредственно в инструкции PrintFormat вместе с анализом кода ошибки _LastError. Дело в том, что последовательность вычисления аргументов функции (см. раздел Параметры и аргументы) и операндов в выражении (см. раздел Базовые понятия) в данном случае не определена. Поэтому при вызове указателя в той же строке, где считывается _LastError, компилятор может решить выполнить второе раньше первого. В результате мы увидим нерелевантный код ошибки (например, с предыдущего вызова функции).

Но и это еще не всё. Встроенная переменная _LastError может изменить свое значение практически в любом месте вычисления выражения, если какая-либо операция выполнится с ошибкой. В частности, функция EnumToString может потенциально взвести код ошибки, если в качестве аргумента передано значение, отсутствующее в перечислении. В данном фрагменте мы защищены от этой проблемы, потому что наша функция EnumToArray возвращает массив только с проверенными (правильными) элементами перечисления. Однако в общем случае в любой "сложносочиненной" инструкции может оказаться много мест, где _LastError будет изменена. В связи с этим желательно зафиксировать код ошибки сразу после интересующего нас действия (здесь это вызов функции по указателю), сохранив его в промежуточную переменную snapshot.

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

void OnStart()
{
   process(Binding<ENUM_MQL_INFO_INTEGER,int>(_MQLInfoInteger));
   process(Binding<ENUM_TERMINAL_INFO_INTEGER,int>(_TerminalInfoInteger));
   process(Binding<ENUM_TERMINAL_INFO_DOUBLE,double>(_TerminalInfoDouble));
   process(Binding<ENUM_MQL_INFO_STRING,string>(_MQLInfoString));
   process(Binding<ENUM_TERMINAL_INFO_STRING,string>(_TerminalInfoString));
}

Ниже приведен фрагмент сгенерированных записей в журнале.

ENUM_MQL_INFO_INTEGER Count=15
  0 MQL_PROGRAM_TYPE=1
  1 MQL_DLLS_ALLOWED=0
  2 MQL_TRADE_ALLOWED=0
  3 MQL_DEBUG=1
...
  7 MQL_LICENSE_TYPE=0
...
ENUM_TERMINAL_INFO_INTEGER Count=50
  0 TERMINAL_BUILD=2988
  1 TERMINAL_CONNECTED=1
  2 TERMINAL_DLLS_ALLOWED=0
  3 TERMINAL_TRADE_ALLOWED=0
...
  6 TERMINAL_MAXBARS=100000
  7 TERMINAL_CODEPAGE=1251
  8 TERMINAL_MEMORY_PHYSICAL=4095
  9 TERMINAL_MEMORY_TOTAL=8190
 10 TERMINAL_MEMORY_AVAILABLE=7813
 11 TERMINAL_MEMORY_USED=377
 12 TERMINAL_X64=1
...
ENUM_TERMINAL_INFO_DOUBLE Count=2
  0 TERMINAL_COMMUNITY_BALANCE=0.0 (MQL5_WRONG_PROPERTY,4512)
  1 TERMINAL_RETRANSMISSION=0.0
ENUM_MQL_INFO_STRING Count=2
  0 MQL_PROGRAM_NAME=Environment
  1 MQL_PROGRAM_PATH=C:\Program Files\MT5East\MQL5\Scripts\MQL5Book\p4\Environment.ex5
ENUM_TERMINAL_INFO_STRING Count=6
  0 TERMINAL_COMPANY=MetaQuotes Software Corp.
  1 TERMINAL_NAME=MetaTrader 5
  2 TERMINAL_PATH=C:\Program Files\MT5East
  3 TERMINAL_DATA_PATH=C:\Program Files\MT5East
  4 TERMINAL_COMMONDATA_PATH=C:\Users\User\AppData\Roaming\MetaQuotes\Terminal\Common
  5 TERMINAL_LANGUAGE=Russian
 

Эти и другие свойства будут описаны в следующих разделах.

Стоит отметить, что некоторые свойства унаследованы с прошлых этапов развития платформы и оставлены только для совместимости. В частности, свойство TERMINAL_X64 в TerminalInfoInteger возвращает признак того, является ли терминал 64-разрядным. Сегодня разработка 32-битных версий прекращена, и потому это свойство всегда равно 1 (true).