English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
ООП в MQL5 на примерах: обработка кодов ошибок и предупреждений

ООП в MQL5 на примерах: обработка кодов ошибок и предупреждений

MetaTrader 5Примеры | 4 мая 2010, 11:16
6 113 5
KlimMalgin
KlimMalgin

Краткое введение в ООП

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

Безусловно, будут использоваться структуры и классы. Это базовые элементы в объектно-ориентированных языках. Что такое структура, что такое класс и чем они отличаются?

Структура - это конструкция, которая позволяет содержать в себе набор переменных и функций различных типов (кроме void).

Класс также как и структура представляет собой набор полей данных. Но класс - это более сложная и "гибкая" конструкция. Именно классы являются ключевым понятием в ООП. В документации указаны различия классов и структур, я повторюсь:

  • в объявлении используется ключевое слово class;
  • по умолчанию все члены класса имеют спецификатор доступа private, если не указано иное. Члены-данные структуры по умолчанию имеют тип доступа public, если не указано иное;
  • объекты классов всегда имеют таблицу виртуальных функций, даже если в классе не объявлено ни одной виртуальной функции. Структуры не могут иметь виртуальных функций;
  • к объектам класса можно применять оператор new, к структурам этот оператор применять нельзя;
  • классы могут наследоваться только от классов, структуры могут наследоваться только от структур.

 

Рассмотрим подробнее классы. Все поля класса делятся на два вида. Это члены-данные (переменные, массивы и др.) и функции, определенные внутри класса.

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

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

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

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

Если ближе к делу, то для любого из свойств и методов класса мы можем задать уровень доступа при помощи трех модификаторов private, protected и public.

Если для поля класса используется модификатор private, то доступ к этому полю делается возможным только с помощью методов этого же класса, таким образом не допускается возможность его модификации извне. Модификатор protected также накладывает ограничения на доступ к полю извне, но при этом он делает возможным доступ к полям класса для методов этого же класса и для методов классов-наследников. Ну а public, наоборот, убирает все ограничения доступа и предоставляет свободный доступ к полям класса.

Создание включаемого mqh-файла

Класс, который мы с вами напишем, должен находиться в отдельном mqh-файле, для того чтобы его можно было включать в свои программы (эксперты, скрипты, индикаторы). 

Для создания этого файла воспользуемся Мастером MQL5. В меню Файл -> Создать выберем пункт Включаемый файл (*.mqh) и перейдем Далее. В появившемся окне введем имя файла, я назвал его ControlErrors, и нажмем Готово. Перед вами откроется шаблон mqh-файла, в котором мы продолжим работать.

Приступаем к работе

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

Рассмотрим код класса с объявлением всех его свойств и методов:

class ControlErrors
{
private:

   // Флаги, определяющие, какие виды отчетов нужно вести
   bool _PlaySound;    // Подавать или не подавать звуковой сигнал при появлении ошибки.
   bool _PrintInfo;    // Печатать информацию об ошибках в журнал экспертов
   bool _AlertInfo;    // Выдавать Alert c информацией об ошибках
   bool _WriteFile;    // Писать отчеты об ошибках в файл или нет
   
   // Структура для хранения информации об ошибке и элементы, использующие эту структуру
   struct Code
   {
      int code;      // Код ошибки
      string desc;   // Описание кода ошибки
   };
   Code Errors[];    // Массив, содержащий коды ошибок и их описания
   Code _UserError;  // Хранит информацию о пользовательской ошибке
   Code _Error;      // Хранит информацию о последней ошибке любого типа   
   
   // Различные служебные свойства
   short  _CountErrors;     // Количество ошибок, содержащихся в массиве Errors[]
   string _PlaySoundFile;   // Файл, который будет проигрываться при подаче сигнала
   string _DataPath;        // Путь к директории для сохранения логов

   
public:
   // Конструктор
   ControlErrors(void);
   
   // Методы для установки флагов
   void SetSound(bool value);          // Подавать звуковой сигнал при возникновении ошибки или нет
   void SetPrint(bool value);          // Писать информацию об ошибке в журнал экспертов или нет
   void SetAlert(bool value);          // Подавать сообщение Alert или нет
   void SetWriteFlag(bool flag);       // Установить флаг записи. true - вести логи, false - не вести
   
   // Методы для работы с ошибками
   int  mGetLastError();            // Возвращает содержимое системной переменной _LastError
   int  mGetError();                // Возвращает код последней полученной ошибки
   int  mGetTypeError();            // Возвращает тип ошибки (Пользовательская = 1 или предопределенная = 0)
   void mResetLastError();          // Обнуляет содержимое системной переменной _LastError
   void mSetUserError(ushort value, string desc = "");   // Устанавливает пользовательскую ошибку
   void mResetUserError();          // Обнуляет поля класса, содержащие информацию о пользовательской ошибке
   void mResetError();              // Обнуляет структуру, содержащую информацию о последней ошибке
   string mGetDesc(int nErr = 0);   // Возвращает описание ошибки по номеру, либо текущей, если номер не указан
   int Check(string st = "");       // Метод для проверки текущего состояния системы на наличие ошибок
   
   // Сигнальные методы(Alert, Print, Sound)
   void mAlert(string message = "");
   void mPrint(string message = "");
   void mSound();
      
   // Различные служебные методы
   void SetPlaySoundFile(string file); // Метод устанавливает имя файла для подачи звукового сигнала
   void SetWritePath(string path);     // Установить путь для хранения логов   
   int mFileWrite();                   // Запись в файл доступной информации о последней ошибке
};

Свойства класса

В начале следует объявление свойств класса, при чем ко всем свойствам применен модификатор private и тем самым становится невозможной работа со свойствами напрямую вне класса. Условно свойства разбиты на три группы:

  1. Флаги, определяющие, какие виды отчетов нужно вести. Все эти флаги принимают только два значения: true - означает, что данный вид отчетов (уведомлений) включен и false - обозначает, что данный вид отчетов не ведется.
    • _PlaySound - переменная, которая запрещает или разрешает проигрывать указанную мелодию или звук, когда возникает ошибка.
    • _PrintInfo - отвечает за добавление информации об ошибке в журнал экспертов.
    • _AlertInfo - разрешает или запрещает выдавать Alert с информацией об ошибке.
    • _WriteFile - разрешает или запрещает писать информацию об ошибке в файл.
  2. Структура для хранения информации об ошибке и элементы, которые используют эту структуру.
    • Code - сама по себе структура. Она была создана для удобства хранения информации об ошибках в массиве.
    • Errors - массив типа Code, т.е. каждый элемент массива представляет собой структуру Code.
    • _UserError - переменная типа Code. Используется для работы с пользовательскими ошибками.
    • _Error - переменная типа Code. Последняя появившаяся ошибка, помещается в эту переменную и дальнейшая работа с ошибкой идет именно через эту переменную.
  3. Остальные служебные свойства.
    • _CountErrors - переменная содержит количество ошибок, информация о которых должна храниться в массиве Errors. Используется для указания размера массива.
    • _PlaySoundFile - содержит имя файла, который будет проигрываться при подаче сигнала.
    • _DataPath - содержит путь и название лог-файла, в который записывается информация об ошибках.

С первой группой свойств, я думаю, все понятно: они разрешают или запрещают ведение тех или иных отчетов. А вот во второй группе представляет интерес структура Code. Что она собой представляет и почему в качестве элементов массива используется именно структура? Все довольно просто! Ведь гораздо удобнее хранить все необходимые данные в одном элементе массива, чем заводить отдельные массивы для кода ошибки и для ее описания. Для реализации такой возможности используется структура. Внутри структуры объявляются необходимые поля, в нашем случае это:

  • code - поле типа int, которое содержит код ошибки.
  • desc - поле типа string. Содержит описание ошибки.

По сути, структура является составным типом данных, т.е. ее можно использовать для объявления переменных и массивов, что и было сделано. В результате получается, что любая переменная типа Code будет содержать в себе поля этой структуры. Точно так же и каждый элемент массива типа Code содержит в себе два поля для хранения кода и его описания. Таким образом, в MQL5 реализуется довольно удобный способ для хранения данных различных типов об объекте в одном месте.

Далее по коду следуют переменные _UserError и _Error. Они обе содержат в себе информацию о последней возникшей ошибке, с той разницей, что _UserError хранит информацию о пользовательских ошибках, а _Error обо всех.

И последняя - третья группа свойств. К ней я отнес оставшиеся свойства, которые не вписываются по своему назначению ни в первую, ни во вторую группы. Всего их три. Первое - _CountErrors, в нем содержится количество ошибок, информация о которых хранится в массиве _Errors. Это свойство используется для задания размера массива _Errors в конструкторе и в некоторых методах для обращения к элементам массива. Второе свойство - _PlaySoundFile. Хранит имя звукового файла, который будет проигрываться при возникновении ошибки. И третье свойство - _DataPath. Хранит путь и название файла для ведения логов.

Методы класса. Конструктор

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

  • Имя конструктора совпадает с  именем класса.
  • Конструктор не имеет возвращаемого значения (указывается тип void).
  • Конструктор не имеет входных параметров.

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

void ControlErrors::ControlErrors(void)
{
   SetAlert(false);
   SetPrint(false);
   SetSound(false);
   SetWriteFlag(false);
   SetPlaySoundFile("alert.wav");
   SetWritePath("LogErrors.txt");
   
   _CountErrors = 150;
   ArrayResize(Errors, _CountErrors);

   // Коды возврата торгового сервера
   Errors[0].code = 10004;Errors[0].desc = "Реквота";
   Errors[1].code = 10006;Errors[1].desc = "Запрос отвергнут";
   Errors[2].code = 10007;Errors[2].desc = "Запрос отменен трейдером";
   Errors[3].code = 10008;Errors[3].desc = "Ордер размещен";
   Errors[4].code = 10009;Errors[4].desc = "Заявка выполнена";
   Errors[5].code = 10010;Errors[5].desc = "Заявка выполнена частично";
   Errors[6].code = 10011;Errors[6].desc = "Ошибка обработки запроса";
   Errors[7].code = 10012;Errors[7].desc = "Запрос отменен по истечению времени";
   Errors[8].code = 10013;Errors[8].desc = "Неправильный запрос";
   Errors[9].code = 10014;Errors[9].desc = "Неправильный объем в запросе";
   Errors[10].code = 10015;Errors[10].desc = "Неправильная цена в запросе";
   Errors[11].code = 10016;Errors[11].desc = "Неправильные стопы в запросе";
   Errors[12].code = 10017;Errors[12].desc = "Торговля запрещена";
   Errors[13].code = 10018;Errors[13].desc = "Рынок закрыт";
   Errors[14].code = 10019;Errors[14].desc = "Нет достаточных денежных средств для выполнения запроса";
   Errors[15].code = 10020;Errors[15].desc = "Цены изменились";
   Errors[16].code = 10021;Errors[16].desc = "Отсутствуют котировки для обработки запроса";
   Errors[17].code = 10022;Errors[17].desc = "Неверная дата истечения ордера в запросе";
   Errors[18].code = 10023;Errors[18].desc = "Состояние ордера изменилось";
   Errors[19].code = 10024;Errors[19].desc = "Слишком частые запросы";
   Errors[20].code = 10025;Errors[20].desc = "В запросе нет изменений";
   Errors[21].code = 10026;Errors[21].desc = "Автотрейдинг запрещен сервером";
   Errors[22].code = 10027;Errors[22].desc = "Автотрейдинг запрещен клиентским терминалом";
   Errors[23].code = 10028;Errors[23].desc = "Запрос заблокирован для обработки";
   Errors[24].code = 10029;Errors[24].desc = "Ордер или позиция заморожены";
   Errors[25].code = 10030;Errors[25].desc = "Указан неподдерживаемый тип исполнения ордера по остатку";

   // Общие ошибки
   Errors[26].code = 4001;Errors[26].desc = "Неожиданная внутренняя ошибка";
   Errors[27].code = 4002;Errors[27].desc = "Ошибочный параметр при внутреннем вызове функции клиентского терминала";
   Errors[28].code = 4003;Errors[28].desc = "Ошибочный параметр при вызове системной функции";
   Errors[29].code = 4004;Errors[29].desc = "Недостаточно памяти для выполнения системной функции";
   Errors[30].code = 4005;Errors[30].desc = "Структура содержит объекты строк и/или динамических массивов и/или структуры с такими объектами и/или классы";
   Errors[31].code = 4006;Errors[31].desc = "Массив неподходящего типа, неподходящего размера или испорченный объект динамического массива";
   Errors[32].code = 4007;Errors[32].desc = "Недостаточно памяти для перераспределения массива либо попытка изменения размера статического массива";
   Errors[33].code = 4008;Errors[33].desc = "Недостаточно памяти для перераспределения строки";
   Errors[34].code = 4009;Errors[34].desc = "Неинициализированная строка";
   Errors[35].code = 4010;Errors[35].desc = "Неправильное значение даты и/или времени";
   Errors[36].code = 4011;Errors[36].desc = "Запрашиваемый размер массива превышает 2 гигабайта";
   Errors[37].code = 4012;Errors[37].desc = "Ошибочный указатель";
   Errors[38].code = 4013;Errors[38].desc = "Ошибочный тип указателя";
   Errors[39].code = 4014;Errors[39].desc = "Системная функция не разрешена для вызова";

}

Обратите внимание, что описание реализации происходит за пределами класса! В самом классе только объявлены методы! Хотя делать так не обязательно. Если хотите - вы можете описывать тело каждого метода в самом классе, но, на мой взгляд, это неудобно и сложно для восприятия.

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

Сначала в конструкторе класса устанавливаются все флаги и указываются звуковой и лог-файл: 

SetAlert(false);
SetPrint(false);
SetSound(false);
SetWriteFlag(false);
SetPlaySoundFile("alert.wav");
SetWritePath("LogErrors.txt"); 

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

Как вы могли заметить: все флаги принимают значение false. Т.е. по умолчанию при создании экземпляра класса ни один вид отчетов вестись не будет. Пользователь должен сам выбрать, какие отчеты необходимо вести и активировать их с помощью этих же методов "Set" в функции OnInit(). Точно также можно изменить имя и путь до файла ведения логов (путь задается относительно директории 'MetaTrader 5\MQL5\Files\') и звукового файла(путь задается относительно директории 'MetaTrader 5\Sounds\').

После установки флагов, инициализируем переменную _CountErrors, присваивая ей значение 150 (в массиве будет храниться информация о 149 ошибках) и затем с помощью функции ArrayResize() устанавливаем необходимый нам размер массива. После чего начинаем заполнять массив.

Методы установки флагов

После описания конструктора идет описание методов установки флагов и задания имен звукового и лог-файла:

void ControlErrors::SetAlert(bool value)
{
   _AlertInfo = value;
}

void ControlErrors::SetPrint(bool value)
{
   _PrintInfo = value;
}

void ControlErrors::SetSound(bool value)
{
   _PlaySound = value;
}

void ControlErrors::SetWriteFlag(bool flag)
{
   _WriteFile = flag;
}

void ControlErrors::SetWritePath(string path)
{
   _DataPath = path;
}

void ControlErrors::SetPlaySoundFile(string file)
{
   _PlaySoundFile = file;
}

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

Обращение к этим методам, так же как и ко всем остальным, имеет вид: 

тип Имя_класса::Имя_функции(описание_параметров)
{
   // тело функции
}

Далее следует описание методов по работе с ошибками и первые из них mGetLastError() и mResetLastError().

Методы mGetLastError() и mResetLastError() 

Название метода mGetLastError() говорит само за себя, он дублирует функцию GetLastError(). Но помимо вызова GetLastError(), для полученного кода ошибки в массиве _Errors ищется описание и вся информация об ошибке (код и его описание) сохраняется в переменной _Error, чтобы в последующем использовать сохраненное значение, а не вызывать каждый раз GetLastError().

Код метода:

int ControlErrors::mGetLastError(void)
{
   _Error.code = GetLastError();
   _Error.desc = mGetDesc(_Error.code);
   return _Error.code;
}

Метод mResetLastError() дублирует функцию ResetLastError():

void ControlErrors::mResetLastError(void)
{
   ResetLastError();
}

Методы для работы с последним сообщением об ошибке

Это два метода: mGetError() и mResetError().

Метод mGetError() возвращает код, содержащийся в _Error.code:

int ControlErrors::mGetError(void)
{
   return _Error.code;
}

Метод mResetError() обнуляет содержимое переменной _Error:

void ControlErrors::mResetError(void)
{
   _Error.code = 0;
   _Error.desc = "";
}

Метод определения типа ошибки mGetTypeError()

Следующий метод mGetTypeError(). Он проверяет, является последняя возникшая ошибка пользовательской, либо она предопределенная (содержится в массиве _Errors).

Код метода:

int ControlErrors::mGetTypeError(void)
{
   if (mGetError() < ERR_USER_ERROR_FIRST)
   {
      return 0;
   }
   else if (mGetError() >= ERR_USER_ERROR_FIRST)
   {
      return 1;
   }
   return -1;
}

Константа ERR_USER_ERROR_FIRST имеет значение 65536. С этого кода начинаются ошибки, задаваемые пользователем. Поэтому в теле метода осуществляется проверка последнего поступившего кода ошибки. Если метод возвращает значение ноль, значит ошибка предопределенная, если единицу - пользовательская.

Методы для работы с пользовательскими ошибками

В MQL5 у пользователей появилась возможность задавать собственные ошибки в ходе работы программы. Для того чтобы пользовательским кодам ошибок можно было поставить в соответствие описание, в классе есть свойство _UserError и для работы с этим свойством используются два метода.

Метод mSetUserError() используется для установки кода и описания пользовательской ошибки:

void ControlErrors::mSetUserError(ushort value, string desc = "")
{
   SetUserError(value);
   _UserError.code = value;
   _UserError.desc = desc;
}

В начале функция SetUserError() устанавливает предопределенную переменную _LastError в значение, равное ERR_USER_ERROR_FIRST + value. А затем значение value и заданное ему в соответствие описание сохраняются в переменной _UserError.

Второй метод mResetUserError() обнуляет поля переменной _UserError:

void ControlErrors::mResetUserError(void)
{
   _UserError.code = 0;
   _UserError.desc = "";
}

Этот метод работает только с переменной _UserError. Для обнуления значения системной переменной _LastError, предназначен другой метод: mResetLastError(), он описан выше.

Метод для получения описания кода ошибки

В классе также заведен специальный метод mGetDesc(), который при вызове вернет описание кода ошибки из массива Errors, либо из поля desc переменной _UserError, если ошибка задана пользователем:

string ControlErrors::mGetDesc(int nErr=0)
{
   int ErrorNumber = 0;
   string ReturnDesc = "";
   
   ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
   ErrorNumber = (nErr>0)?nErr:ErrorNumber;
   
   if ((ErrorNumber > 0) && (ErrorNumber < ERR_USER_ERROR_FIRST))
   {
      for (int i = 0;i<_CountErrors;i++)
      {
         if (Errors[i].code == ErrorNumber)
         {
            ReturnDesc = Errors[i].desc;
            break;
         }
      }
   }
   else if (ErrorNumber > ERR_USER_ERROR_FIRST)
   {
      ReturnDesc = (_UserError.desc=="")?"Пользовательская ошибка":_UserError.desc;
   }
      
   if (ReturnDesc == ""){return "Неизвестный код ошибки: "+(string)ErrorNumber;}
   return ReturnDesc;
}

У этого метода есть один параметр nErr и по умолчанию он равен нулю. Если при вызове метода параметру будет задано какое-либо значение, то поиск описания будет производиться для заданного значения, если же параметр задан не будет, то поиск описания будет производиться для последнего поступившего кода ошибки.

В начале, в методе объявляются две переменные: ErrorNumber - с помощью нее будет проводиться работа с кодом ошибки и ReturnDesc - в ней будет храниться полученное для ErrorNumber описание. В следующих двух строках, при присвоении значения ErrorNumber используется условный оператор ?:. Это упрощенный аналог конструкции if-else. 

ErrorNumber = (mGetError()>0)?mGetError():ErrorNumber;
ErrorNumber = (nErr>0)?nErr:ErrorNumber;

В первой строке мы определяем: если была зафиксирована ошибка, т.е. mGetError() вернул не нулевой результат, то переменной ErrorNumber будет присвоен полученный код ошибки (значение, которое вернул метод mGetError()), в ином случае значение самой переменной ErrorNumber. Во второй строке производится такая же проверка, но для параметра метода mGetError(). В том случае, если значение nErr не нулевое, то оно присваивается переменной ErrorNumber.

После того как мы получили код ошибки - приступаем к поиску описания для этого кода. Если полученный код больше нуля и меньше значения ERR_USER_ERROR_FIRST, т.е. не является пользовательской ошибкой, то в цикле производим поиск описания. А если полученный код больше ERR_USER_ERROR_FIRST, то берем описание из поля desc переменной _UserError.

В самом конце проверяем: найдено ли описание и если не найдено, то возвращаем сообщение о неизвестном коде ошибки.

Сигнальные методы

К сигнальным методам относятся mAlert(), mPrint() и mSound(). По своему устройству эти методы очень похожи:

void ControlErrors::mAlert(string message="")
{
   if (_AlertInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Alert("Ошибка №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Alert(message);
      }   
   }
}

void ControlErrors::mPrint(string message="")
{
   if (_PrintInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Print("Ошибка №",mGetError()," - ",mGetDesc());
         }
      }
      else
      {
         Print(message);
      }
   }
}

void ControlErrors::mSound(void)
{
   if (_PlaySound == true)
   {
      PlaySound(_PlaySoundFile);
   }
}

Во всех трех методах в начале проверяется флаг на разрешение ведения отчета или выдачу сигнала. Затем в методах mAlert() и mPrint() проверяется входной параметр message на наличие сообщения, которое нужно показать в окне Alert'а или добавить в журнал. Если сообщение в message задано и код последней ошибки больше нуля, то выводится оно, если нет - то выводится стандартное сообщение. Метод mSound() никаких параметров не имеет, поэтому после проверки флага, в нем сразу вызывается функция PlaySound() для подачи звукового сигнала.

Метод Check()

Этот метод просто вызывает все функции данного класса в нужной очередности, тем самым проверяется появление новой ошибки, выдаются все разрешенные отчеты и сразу же после этого код ошибки с ее описанием удаляются. Таким образом, метод Check() выполняет комплексную проверку:

int ControlErrors::Check(string st="")
{
   int errNum = 0;
   errNum = mGetLastError();
   mFileWrite();
   mAlert(st);
   mPrint(st);
   mSound();
   mResetError();
   mResetLastError();
   mResetUserError();
   return errNum;
}

У метода Check() есть один параметр типа string. Это пользовательское сообщение, которое передается в методы mAlert() и mPrint() для записи в отчеты.

Метод для записи сообщений в лог файл

Это метод под названием mFileWrite(). Если ведение лог-файла разрешено и путь до файла указан корректно - этот метод делает записи в указанном файле.

int ControlErrors::mFileWrite(string message = "")
{
   int      handle  = 0,
            _return = 0;
   datetime time    = TimeCurrent();
   string   text    = (message != "")?message:time+" - Ошибка №"+mGetError()+" "+mGetDesc();
   
   if (_WriteFile == true)
   {
      handle = FileOpen(_DataPath,FILE_READ|FILE_WRITE|FILE_TXT|FILE_ANSI);
      if (handle != INVALID_HANDLE)
      {
         ulong size = FileSize(handle);
         FileSeek(handle,size,SEEK_SET);
         _return = FileWrite(handle,text);
         FileClose(handle);
      }
   }
   return _return;
}

В начале объявляются четыре переменные: handle - для хранения хендла открытого файла, _return - для хранения возвращаемого значения, time, в которой сохраняется текущее время (время в которое будет сделана запись в файл) и text - текст сообщения, которое будет записано в файл. У метода mFileWrite() есть один входной параметр - message, в котором пользователь может передать любую строку, которую ему необходимо будет записать в файл.

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

После объявления переменных проверяется флаг _WriteFile и если ведение лог-файла разрешено, то с помощью функции FileOpen() файл открывается для перезаписи. Первым параметром FileOpen() является свойство DataPath, содержащее путь до файла и его имя. В качестве второго параметра подается набор флагов, которые определяют режим работы с файлом. В нашем случае используется четыре флага:

  • FILE_READ и FILE_WRITE вместе предписывают открывать не пустой файл с возможностью дописывать в него информацию.
  • FILE_TXT дает понять, что работа будет идти с простым текстовым файлом.
  • FILE_ANSI говорит о том, что записи в файл будут производиться как строки типа ANSI (однобайтовые символы).

На следующем шаге проверяем - удалось ли открыть файл. Если не удалось, то handle будет иметь значение INVALID_HANDLE и на этом работа метода закончится. Но если все прошло благополучно, тогда мы получаем размер файла с помощью FileSize(), затем при помощи функции FileSeek() перемещаем положение файлового указателя в конец файла и функцией FileWrite() дописываем сообщение в конец файла. После всех этих операций функцией FileClose() закрываем ранее открытый файл.

В функцию FileSize() в качестве входного параметра необходимо подать хендл файла, размер которого мы хотим получить. Это единственный параметр у этой функции.

Для работы функции FileSeek() нужно указать три параметра:

  • Хендл файла, с которым ведется работа.
  • Смещение файлового указателя.
  • Точка отсчета для смещения. Принимает одно из значений ENUM_FILE_POSITION.

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

Функции FileClose() так же требуется хендл файла, чтобы его закрыть.

Примеры использования

В завершении статьи, я хочу привести несколько типовых примеров использования, написанного нами класса. Начнем с создания объекта и разрешения вести нужные нам типов отчетов.

Итак, создадим объект класса:

#include <ControlErrors.mqh>

ControlErrors mControl;

Перед тем как создавать объект, в советник необходимо включить файл, содержащий описание класса. Это делается в самом начале программы при помощи директивы #include. И только потом создается объект, при чем выглядит это так же, как и создание новой переменной, только вместо типа данных подставляется название класса. 

Теперь выберем те отчеты об ошибках, которые хотим получать, делается это в функции OnInit(): 

int OnInit()
{
//---
mControl.SetAlert(true);
mControl.SetPrint(true);
mControl.SetSound(false);
mControl.SetWriteFlag(true);
mControl.SetPlaySoundFile("news.wav");
//---
return(0);
}

Напомню, что по умолчанию, при создании объекта все флаги, разрешающие ведение отчетов установлены в false, т.е. все отчеты запрещены. Поэтому в OnInit() не обязательно вызывать методы со значением false, как это сделано в примере выше (метод SetSound()). Эти методы также можно вызывать и в других фрагментах программы. К примеру, если вам понадобится отключить ведение отчетов при определенных условиях, то вы программируете эти условия и при их выполнении устанавливаете флагам нужные значения.

И последнее о чем здесь нужно сказать, это вызов методов в ходе работы программы и "отлов" ошибок. Эта часть работы ничего сложного собою не представляет, т.к. здесь можно пользоваться одним только методом Check(), предварительно установив все флаги:

mControl.Check();

Этот метод, как было сказано выше, выявит код возникшей ошибки, вызовет все методы, которые ведут отчеты и затем обнулит значения всех переменных, содержащих информацию о последней ошибке. Если тот вид работы с ошибками, который предоставляет Check(), по каким-то причинам не подходит, то можно составлять отчеты по своему из всех доступных методов класса.

Прикрепленные файлы |
controlerrors.mqh (20.3 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (5)
Vasily
Vasily | 4 мая 2010 в 11:50

классс =)

давно ждал такой статейки =)

уже с первых слов ясно что настоящая статья для чайников =)

TheXpert
TheXpert | 4 мая 2010 в 14:27
Чайник для чайников.
Alphazavr
Alphazavr | 16 окт. 2011 в 16:10

Замечательная статья. Спасибо!

 

Timofey Rodikhin
Timofey Rodikhin | 24 июн. 2016 в 21:51
Я так понимаю метод Check отлавливает именно ошибки, коды возврата он не цепляет?
Alexey Volchanskiy
Alexey Volchanskiy | 25 июн. 2016 в 21:33
Надо исключения вводить в язык, тогда будет толк. Пока при любой критической рантайм ошибке программа валится, и никакие классы типа описанного ситуацию не спасут.
Использование WinInet.dll для обмена данными между терминалами через Интернет Использование WinInet.dll для обмена данными между терминалами через Интернет
В статье рассматриваются принципы работы с Интернет посредством HTTP запросов и обмен данными между терминалами с использованием промежуточного сервера. Представлен библиотечный класс MqlNet для работы с ресурсами Интернет в среде MQL5. Мониторинг цен от разных брокеров, обмен сообщениями с другими трейдерами не выходя из терминала, поиск информации в Интернете - вот только некоторые примеры, рассматриваемые в этой статье.
Связь ICQ и эксперта в MQL5 Связь ICQ и эксперта в MQL5
В статье рассматривается способ двустороннего обмена текстовыми сообщениями между клиентами ICQ, используя средства программирования языка MQL5. Материал заинтересует тех, кто хочет получать торговую информацию из работающего торгового терминала удаленно, например, через ICQ клиента в своем мобильном телефоне или КПК.
MetaTrader 5: Публикация торговых прогнозов и отчетов реальной торговли по e-mail в блогах, социальных сетях и специализированных сайтах MetaTrader 5: Публикация торговых прогнозов и отчетов реальной торговли по e-mail в блогах, социальных сетях и специализированных сайтах
Цель данной статьи - представить готовые решения для публикации прогнозов с использованием MetaTrader 5. Рассмотрен ряд идей: от использования специализированных сайтов для публикации торговых отчетов до создания своего собственного блога и финальной интеграции с социальными микроблогами, которые позволяют объединить множество трейдеров, использующих прогнозы. Все представленные здесь решения на 100% бесплатные и могут быть использованы всеми, кто знаком с базовыми понятиями почтовых и ftp-сервисов. Данные методы могут быть легко использованы и в коммерческих сервисах по предоставлению торговых прогнозов.
Пример разработки торговой системы, основанной на различиях  часовых поясов на разных континентах Пример разработки торговой системы, основанной на различиях часовых поясов на разных континентах
Листая страницы Интернета, можно найти множество стратегий, которые вам советуют делать то или иное. Давайте заглянем внутрь и посмотрим на сам процесс составления стратегии, основанной на различиях часовых поясов на разных континентах.