English Русский Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
例解 MQL5 中的面向对象编程:处理警告和错误代码

例解 MQL5 中的面向对象编程:处理警告和错误代码

MetaTrader 5示例 | 17 十二月 2013, 07:14
3 007 0
KlimMalgin
KlimMalgin

面向对象编程简介

在我们开始开发之前,让我们先熟悉将在本文中使用的面向对象编程 (OOP) 的一些特点。 

当然,我们将使用结构和类。这些是面向对象的语言的基础。结构是什么?类是什么?它们有何区别?

结构是一种允许包含一组不同类型(void 除外)的变量和函数的构造。

与结构一样也是一组数据字段。但类是一种更加复杂和“灵活”的构造。类是 OOP 的基本概念。说明文档说明了类和结构的差异。我将重复如下:

  • 在声明中使用关键字 class;
  • 默认情况下,所有类成员的访问说明符是私有的,除非另有指定。默认情况下,结构的数据成员是公共访问类型,除非另有指定;
  • 类对象始终有一个虚拟函数表,即使在类中没有声明虚拟函数。结构不能有虚拟函数;
  • 运算符 new 可应用到类对象,但是此运算符不能应用到结构;
  • 类只能从类继承,结构只能从结构继承。

 

现在,让我们考虑类。所有类字段都可以分为两种类型。它们是在类中定义的数据成员(变量、数组等)和函数。

数据成员通常被称为类的属性,因为在从类创建对象时,数据成员精确反应了该对象的属性。举例而言,如果这是一个几何形状 - 圆,属性将包含其半径、线宽、阴影颜色等对象属性。

在类内定义的函数被称为方法。它们都被用于处理类的属性及实施在它们的内部编程的任何其他算法。

面向对象编程具有封装的概念。这样能够避免对象的数据和实施受到用户(程序员)的直接影响。程序员仅获得说明使用什么方法能够更改对象属性的文档,而直接更改对象的属性是不可能的。

在更改属性值之前需要进行若干检查的情形中,这种保护措施是必须的。所有需要的检查都通过方法来实施,并且如果正确执行了方法,则它们允许更改属性值。 在用户能够直接存取属性的情形中,并不执行这些检查,因此可能会不正确地设置属性值,从而使 MQL 程序不能正常工作

对于任何类的属性和方法,我们可以使用三个修饰符来设定存取级别:private (私有)protected (受保护)public (公用)

如果对某类的字段使用 private 修饰符,则只能使用该类的方法才能存取此字段,因此不能从外部修改。修饰符 protected对从外部存取字段施加某些限制,但是它使为该类的方法和该类的子类的方法存取该类的字段成为可能。作为对比,public 取消所有存取限制,并且为类的字段打开自由存取权限。

创建 mqh 包含文件

我们即将编写的类应位于单独的 mqh 文件中,从而能够包含在我们的程序(EA 交易程序、脚本、指标)中。 

要使用该文件,让我们使用 MQL5 向导。在菜单 File(文件) -> Create(创建)中,选择 Include file (*.mqh)(包含文件 (*.mqh)),然后单击 Next(下一步)。在出现的窗口中,输入文件名(我已经称之为 ControlErrors),然后按 Done(完成)。一个 mqh 文件模板打开。我们将在该文件中继续工作。

开始

现在,您知道在学习本文的过程中能够有所帮助的 OOP 的所有理论基础。那么,让我们继续吧。

让我们考虑含有其所有属性和方法声明的类代码:

class ControlErrors{
private:

   // 确定何种类型的报告需要被启用的标识
   bool _PlaySound;    // 当错误发生时播放或不播放一段音乐
   bool _PrintInfo;    // 将报错详情添加到EA日志中
   bool _AlertInfo;    // 创建具有报错详情的报警
   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);          // 将报错详情是否添加到EA日志中
   void SetAlert(bool value);          // 是否生成报警消息
   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 = "");       // 检查当前系统错误状态的方法
   
   // 警报方式(警报,打印,声音)
   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 - 负责将错误详情添加到 EA 交易程序的日志。
    • _AlertInfo - 启用或禁用含有错误信息的警告输出。
    • _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 = "Requote";
   Errors[1].code = 10006;Errors[1].desc = "Request rejected";
   Errors[2].code = 10007;Errors[2].desc = "Request canceled by trader";
   Errors[3].code = 10008;Errors[3].desc = "Order placed";
   Errors[4].code = 10009;Errors[4].desc = "Request is completed";
   Errors[5].code = 10010;Errors[5].desc = "Request is partially completed";
   Errors[6].code = 10011;Errors[6].desc = "Request processing error";
   Errors[7].code = 10012;Errors[7].desc = "Request canceled by timeout";
   Errors[8].code = 10013;Errors[8].desc = "Invalid request";
   Errors[9].code = 10014;Errors[9].desc = "Invalid volume in the request";
   Errors[10].code = 10015;Errors[10].desc = "Invalid price in the request";
   Errors[11].code = 10016;Errors[11].desc = "Invalid stops in the request";
   Errors[12].code = 10017;Errors[12].desc = "Trade is disabled";
   Errors[13].code = 10018;Errors[13].desc = "Market is closed";
   Errors[14].code = 10019;Errors[14].desc = "There is not enough money to fulfill the request";
   Errors[15].code = 10020;Errors[15].desc = "Prices changed";
   Errors[16].code = 10021;Errors[16].desc = "There are no quotes to process the request";
   Errors[17].code = 10022;Errors[17].desc = "Invalid order expiration date in the request";
   Errors[18].code = 10023;Errors[18].desc = "Order state changed";
   Errors[19].code = 10024;Errors[19].desc = "Too frequent requests";
   Errors[20].code = 10025;Errors[20].desc = "No changes in request";
   Errors[21].code = 10026;Errors[21].desc = "Autotrading disabled by server";
   Errors[22].code = 10027;Errors[22].desc = "Autotrading disabled by client terminal";
   Errors[23].code = 10028;Errors[23].desc = "Request locked for processing";
   Errors[24].code = 10029;Errors[24].desc = "Order or position frozen";
   Errors[25].code = 10030;Errors[25].desc = "Invalid order filling type";

   // 常见错误
   Errors[26].code = 4001;Errors[26].desc = "Unexpected internal error";
   Errors[27].code = 4002;Errors[27].desc = "Wrong parameter in the inner call of the client terminal function";
   Errors[28].code = 4003;Errors[28].desc = "Wrong parameter when calling the system function";
   Errors[29].code = 4004;Errors[29].desc = "Not enough memory to perform the system function";
   Errors[30].code = 4005;Errors[30].desc = "The structure contains objects of strings and/or dynamic arrays and/or structure of such objects and/or classes";
   Errors[31].code = 4006;Errors[31].desc = "Array of a wrong type, wrong size, or a damaged object of a dynamic array";
   Errors[32].code = 4007;Errors[32].desc = "Not enough memory for the relocation of an array, or an attempt to change the size of a static array";
   Errors[33].code = 4008;Errors[33].desc = "Not enough memory for the relocation of string";
   Errors[34].code = 4009;Errors[34].desc = "Not initialized string";
   Errors[35].code = 4010;Errors[35].desc = "Invalid date and/or time";
   Errors[36].code = 4011;Errors[36].desc = "Requested array size exceeds 2 GB";
   Errors[37].code = 4012;Errors[37].desc = "Wrong pointer";
   Errors[38].code = 4013;Errors[38].desc = "Wrong type of pointer";
   Errors[39].code = 4014;Errors[39].desc = "System function is not allowed to call";


}

请注意,实施说明在类的外部进行!仅在类的内部声明方法!尽管这不是必须的。如果您愿意 - 您可以在类的内部说明每种方法的主体,但是我认为这样不方便并且难以理解。

如我所述,仅在类的主体内声明方法函数的头部,而说明则在类的外部进行。在说明方法时,您必须指定它属于哪个类。使用上下文允许操作符号 ::实现此目的。如以上代码所示,首先指定了方法的返回类型(对构造函数而言,为 void),接着是类名(方法所属的上下文的名称),类名之后是上下文允许操作符号,然后是方法名称及其输入参数。之后,方法的算法说明开始。

首先,在构造函数中指定了所有标记和声音与日志文件: 

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

这些方法中的每一个都处理类的某个属性。这是故意针对必须筛选用户设置的属性值的情形。例如,您可以设置某个自定义文件名和路径应与之对应的某个掩码。如果现在有与该掩码的对应出现,则通知用户。 

可能您已经提及,所有标记的值都为 false,即在默认情况下,创建该示例类时不保存报告。用户应选择应该保存哪些报告,并使用函数 OnInit() 中的相同 "Set" 方法激活它们。同样地,您也可以更改日志文件和声音方法的名称和路径,日志文件的路径相对于目录 '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;

}

如代码所示,它是一个简单的将指定的参数传递到类属性的方法。标记不需要任何检查,因为只能是两个值。然后,在赋值之前需要检查文件名和路径。

对这些方法的调用,以及其他代码,看起来如下所示: 

type Class_name::Function_Name(parameters_description)
{
   // 函数体

}

接下来是错误处理方法的说明,其中前两个是 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() 复位变量 variable _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。自定义错误从这些代码开始。因此,在方法的主体中检查最新收到的错误代码。如果方法返回 0,则这是一个预定义错误。如果返回 1,则为一个自定义错误。

处理自定义错误的方法

在 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。接着在变量 _UserError 中保存 value 及赋予的相应说明。

第二个方法 mResetUserError() 复位变量 _UserError 的字段:

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

}

该方法仅能用于变量 _UserError。要复位系统变量 _LastError 的值,使用另一方法:mResetLastError(),如上文所述。

获取错误代码说明的方法

在类中还有一个特殊的方法 mGetDesc(),该方法将从数组 Errors,或从变量 _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=="")?"Cusrom error":_UserError.desc;
   
}
      
   if (ReturnDesc == ""){return "Unknown error code: "+(string)ErrorNumber;}
   return ReturnDesc;

}

此方法有一个参数 nErr。默认情况下它等于 0。如果在调用方法期间一个值被设置到参数,则将为设定的值搜索说明。如果未设置参数,则将为最后收到的错误代码搜索说明。

在方法的开头声明了两个变量:ErrorNumber - 使用此变量处理错误代码;ReturnDesc - 为 ErrorNumber 获取的说明将存储在其中。在接下来的两行里,在向 ErrorNumber 赋值时,使用条件运算符 ?:。这是 if-else 结构的简化形式。

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

在第一行中,我们定义:如果错误得到修复,即 mGetError() 返回一个不等于 0 的结果,则获得的错误代码(mGetError() 方法返回的值)将被赋予变量 ErrorNumber;否则等于变量 ErrorNumber 的值。在第二行中执行相同的检查,但是针对方法 mGetError() 的参数。如果 nErr 的值不等于 0,则将被赋予变量 ErrorNumber。

一旦我们收到错误代码,即开始搜索此代码的说明。如果获得的代码大于 0 并且小于 ERR_USER_ERROR_FIRST,即不是一个自定义错误,则我们在一个循环中搜索说明。如果获得的代码大于 ERR_USER_ERROR_FIRST,则我们从变量 _UserError 的 desc 字段获取说明。

最后,我们检查是否找到说明。如果没有,则返回有关未知错误代码的消息

信号方法

信号方法包括 mAlert()、mPrint() 和 mSound()。在它们的排列中,这些方法非常类似:

void ControlErrors::mAlert(string message="")
{
   if (_AlertInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Alert("Error №",mGetError()," - ",mGetDesc());
         
}
      
}
      else
      {
         Alert(message);
      
}   
   
}

}

void ControlErrors::mPrint(string message="")
{
   if (_PrintInfo == true)
   {
      if (message == "")
      {
         if (mGetError() > 0)
         {
            Print("Error №",mGetError()," - ",mGetDesc());
         
}
      
}
      else
      {
         Print(message);
      
}
   
}

}

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

}

在所有三个方法中,在开头检查报告和信号的允许标记。接着,在方法 mAlert() 和 mPrint() 中,检查输入参数 message 以确定是否应在 Alert(警告)对话框中显示消息或添加到日志。如果在 message 中设定了消息,并且最后错误代码大于 0,则显示消息。否则显示标准消息。方法 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+" - Error №"+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;

在我们创建对象之前,我们需要向 EA 交易程序添加包含类的说明的文件。这在程序的最开头通过指令 #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() 提供的错误处理方式并不适合,则您可以使用可用的类方法生成您自己的报告。

本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/70

附加的文件 |
controlerrors.mqh (37.31 KB)
不使用额外的缓冲区,为中间计算进行系列价格的平均化 不使用额外的缓冲区,为中间计算进行系列价格的平均化
本文要讲述的是封装于最简单的单型类中的传统与非寻常平均线算法。它们旨在实现于几乎所有指标的开发过程中的普适用途。我希望建议的这些类,会成为那些自定义与技术指标“笨重”调用的一个很好的替代。
针对市场分析的数据库的具体应用 针对市场分析的数据库的具体应用
处理数据成为现代软件的主要任务 - 独立应用程序和网络应用程序都是如此。为解决此问题而创建了专业软件。这些软件被称为数据库管理系统 (DBMS),能够针对它们的计算机存储和处理对数据进行构建、系统化和组织。对于交易,大多数分析师并不在他们的工作中使用数据库。但是对于一些任务,必须使用此类解决方案。本文提供了一个在客户端-服务器和文件-服务器架构中都能将数据保存到数据库或从数据库加载数据的指标例子。
基于成交历史的交易播放器 基于成交历史的交易播放器
交易播放器。仅仅五个字,无需解释。一个带有按钮的小对话框出现在您的脑海中。按一个按钮 - 它开始播放,移动控制杆 - 播放速度改变。事实上,它非常类似。在本文中,我想展示我编写的以几乎与实时交易完全相同的方式播放交易历史的程序。本文使用指标和管理图表来介绍 OOP 的某些细节。
将指标从 MQL4 迁移到 MQL5 将指标从 MQL4 迁移到 MQL5
本文旨在说明将用 MQL4 编写的价格构建迁移到 MQL5。为了让将指标计算从 MQL4 迁移到 MQL5 的过程更加容易,建议使用 mql4_2_mql5.mqh 函数库。依据 MACD、随机动量指标和 RSI 指标说明其用途。