处理运行时错误

任何程序即使在编写和编译时没有出现任何问题,仍然无法避免运行时错误。这些错误可能因为开发人员的疏忽或者因为在软件环境中出现的不可预见的情况(诸如互联网连接丢失,内存不足等等)。但同样并不少见的是,因程序应用不当而导致错误发生的情况。在所有这些情况下,程序必须能够分析问题的本质,并充分处理问题。

每个 MQL5 语句都是一个潜在的运行时错误来源。如果发生此类错误,终端会将一个描述代码保存到特殊 _LastError 变量。确保在每个语句后立即分析该代码,因为后续语句中的潜在错误会覆写该值。

请注意,有很多关键错误一旦发生,它们将立即终止程序执行:

  • 零除
  • 索引超出范围
  • 对象指针不正确

有关错误代码及其含义的完整列表,参见 文档

打开和关闭文件 一节中,在编写实用 PRTF 宏的过程中,我们已经讨论了诊断错误的问题。尤其我们已经了解了辅助头文件 MQL5/Include/MQL5Book/MqlError.mqh,其中 MQL_ERROR 枚举允许使用 EnumToString 将数字错误代码轻松转换为名称。

enum MQL_ERROR
{
   SUCCESS = 0
   INTERNAL_ERROR = 4001
   WRONG_INTERNAL_PARAMETER = 4002
   INVALID_PARAMETER = 4003
   NOT_ENOUGH_MEMORY = 4004
   ...
   // start of area for errors defined by the programmer (see next section)
   USER_ERROR_FIRST = 65536
};
#define E2S(XEnumToString((MQL_ERROR)(X))

在这里,作为 E2S 宏的 X 参数,我们应具有 _LastError 变量或等效的 GetLastError 函数。

int GetLastError() ≡ int _LastError

该函数返回 MQL 程序语句中上次发生错误的代码。一开始还没有错误时,值为 0。读取 _LastError 和调用 GetLastError 函数只是语法上不同而已(根据偏爱的风格选择适当选项)。

应记住,语句的正常无错误执行不会重置错误代码。调用 GetLastError 也不会重置错误代码。

因此,如果有一系列操作,其中仅一个操作将设置一个错误标志,则该标志将由函数或后续(成功)操作返回。例如,

// _LastError = 0 by default
action1// ok, _LastError doe not change
action2// error, _LastError = X
action3// ok, _LastError does not change, i.e. is still equal to X
action4// another error, _LastError = Y
action5// ok, _LastError does not change, that is, it is still equal to Y
action6// ok, _LastError does not change, that is, it is still equal to Y

此行为将导致难以定位问题区域。为避免这一情况,有一个单独的将 _LastError 变量重置为 0 的ResetLastError 函数。

void ResetLastError()

该函数将内置 _LastError 变量的值设置为零。

若你认为任何操作可能引发错误并且想在发生错误后使用 GetLastError 来分析错误,则建议在该操作前调用该函数。

同时使用这两个函数的一个典型例子就是前面提到的 PRTF 宏(PRTF.mqh 文件)。它的代码如下所示:

#include <MQL5Book/MqlError.mqh>
   
#define PRTF(AResultPrint(#A, (A))
   
template<typename T>
T ResultPrint(const string sconst T retval = NULL)
{
   const int snapshot = _LastError// recording _LastError at input
   const string err = E2S(snapshot) + "(" + (string)snapshot + ")";
   Print(s"="retval" / ", (snapshot == 0 ? "ok" : err));
   ResetLastError(); // clear the error flag for the next calls
   return retval;
}

该宏以及包装在宏中的 ResultPrint 函数的目的是将传递的值(当前错误代码)记录到日志并立即清除错误代码。这样,对多个语句连续应用 PRTF 便始终可以确保,打印到日志的错误(或成功指示)对应于获取到 retval 参数的值的最后一个语句。

我们需要将 _LastError 保存在中间本地变量 snapshot 中,因为在表达式求值过程中,如果任何运算失败,_LastError 的值几乎可能在任何位置被修改。在该特定示例中,E2S 宏使用 EnumToString 函数,如果枚举之外的值被传递为自变量,则该函数可能生成自己的错误代码。然后,在同一表达式的后续部分中,当形成一个字符串时,我们将看到的不是初始错误,而是生成的那个错误。

在任何语句中可能在若干个地方导致 _LastError 突然改变。对此,最好在所需操作后立即记录错误代码。