ООП в MQL5 на примерах: обработка кодов ошибок и предупреждений
Краткое введение в ООП
Перед тем как начать разработку, предлагаю ознакомиться с теми возможностями ООП, которые будут использоваться в этой статье.
Безусловно, будут использоваться структуры и классы. Это базовые элементы в объектно-ориентированных языках. Что такое структура, что такое класс и чем они отличаются?
Структура - это конструкция, которая позволяет содержать в себе набор переменных и функций различных типов (кроме 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 и тем самым становится невозможной работа со свойствами напрямую вне класса. Условно свойства разбиты на три группы:
- Флаги, определяющие, какие виды отчетов нужно вести. Все эти флаги принимают только два значения: true - означает, что данный вид отчетов (уведомлений) включен и false - обозначает, что данный вид отчетов не ведется.
- _PlaySound - переменная, которая запрещает или разрешает проигрывать указанную мелодию или звук, когда возникает ошибка.
- _PrintInfo - отвечает за добавление информации об ошибке в журнал экспертов.
- _AlertInfo - разрешает или запрещает выдавать Alert с информацией об ошибке.
- _WriteFile - разрешает или запрещает писать информацию об ошибке в файл.
- Структура для хранения информации об ошибке и элементы, которые используют эту структуру.
- Code - сама по себе структура. Она была создана для удобства хранения информации об ошибках в массиве.
- Errors - массив типа Code, т.е. каждый элемент массива представляет собой структуру Code.
- _UserError - переменная типа Code. Используется для работы с пользовательскими ошибками.
- _Error - переменная типа Code. Последняя появившаяся ошибка, помещается в эту переменную и дальнейшая работа с ошибкой идет именно через эту переменную.
- Остальные служебные свойства.
- _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(), по каким-то причинам не подходит, то можно составлять отчеты по своему из всех доступных методов класса.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
классс =)
давно ждал такой статейки =)
уже с первых слов ясно что настоящая статья для чайников =)
Замечательная статья. Спасибо!