各类程序启动和停止的特征

在编程领域,“初始化”这一术语被用于多种不同上下文。在 MQL5 中,该术语同样具有多重性。在 初始化 章节中,我们曾用该术语表示设置变量的初始值。随后,我们讨论了指标和 EA 交易中的初始化事件OnInit。尽管这两种初始化的含义相近(均使程序进入可工作状态),但实际上它们代表了 MQL 程序启动准备的两个不同准备阶段:系统初始化和应用初始化。

一个完整的 MQL 程序的生命周期可以概括为以下主要阶段:

  1. 加载 - 将程序从文件读取到终端内存中:包括指令、预定义数据(字面量)、 资源。正是在这一阶段,#property指令发挥作用。
  2. 为全局变量分配内存并设置其初始值 - 这是由运行时执行的系统初始化。回顾 初始化章节内容,当我们通过调试器逐步分析程序启动过程时,我们看到 @global_initializations 条目出现在堆栈中。这是针对该项目的代码块,由编译器隐式创建。如果程序使用类/结构体的全局对象,则其 构造函数 将在该阶段进行调用。
  3. 调用OnInit事件处理程序(如果存在):它将执行更高级别的应用层面初始化操作,因此每个程序会根据需要独立执行此操作。例如,可能需要为对象数组动态分配内存,出于某种原因,需要使用参数化构造函数,而不是默认构造函数。如我们所知,数组的自动内存分配仅使用默认构造函数,因此无法在前面的步骤 (2) 中完成初始化。还可能包括打开文件、调用内置 API 函数以启用必要的图表模式等操作。
  4. 保持循环,直至用户关闭程序/终端,或执行需要重新初始化的任何其他操作(详见后文):
    • 根据发生的事件调用相应的处理程序。
  5. 当检测到用户或程序尝试关闭程序时(仅 EA 交易和脚本中可使用对应函数 ExpertRemove ),调用 OnDeinit事件处理程序(如果存在)。
  6. 最终清理:释放程序员未在OnDeinit中处理的已分配内存和其他资源。如果程序使用了 OOP,全局对象和静态对象的析构函数将在该阶段进行调用。
  7. 下载程序。

脚本和服务默认不具备 OnInitOnDeinit 处理程序,因此它们不存在步骤 3 和步骤 5,且步骤 4 简化为单次调用 OnStart

系统初始化(步骤 2)与加载阶段不可分割,即系统初始化总是紧随加载阶段之后执行。最终清理始终在卸载前执行。然而,指标和 EA 交易在不同场景下的加载和卸载机制存在差异。因此,OnInitOnDeinit 调用(步骤 3 和 5)是能够为 EA 交易和指标提供一致的应用初始化和反初始化的参考点。

在以下场景下执行指标和 EA 交易加载:

场景

指标

专家

用户在图表上启动程序

+

+

启动终端(如果程序在终端上次关闭前已在图表上运行)

+

+

加载模板(如果模板包含已附加到图表的程序)

+

+

配置文件变更(如果程序附加到其中一个配置文件图表)

+

+

成功重新编译后(如果程序之前已附加到图表)

+

+

更改活动账户

+

+

-

-

-

更改指标所附加的图表的交易品种或周期

+

-

更改指标的输入参数

+

-

-

-

-

连接账户(授权),无论账户号码是否发生变化

-

+

可以更简洁的形式制定以下规则:EA 交易不会经历完整的生命周期,即当图表的交易品种/时间范围发生变更时,以及当输入参数变更时,它们不会重新加载。

因此,在卸载程序时可以观察到类似不对称性。卸载指标和 EA 交易的原因如下:

场景

指标

专家

从图表中移除程序

+

+

关闭终端(当程序附加到图表时)

+

+

在正在运行程序的图表上加载模板

+

+

关闭正在运行程序的图表

+

+

更改配置文件(如果程序已附加到配置文件的其中一个图表)

+

+

更改终端所连接到的账户

+

+

-

-

-

更改指标所附加的图表的交易品种和/或周期

+

-

更改指标的输入参数

+

-

-

-

-

将另一个或同一个 EA 附加到当前 EA 已在运行的图表上

-

+

调用 ExpertRemove 函数

-

+

反初始化的原因可以在程序中通过函数 UninitializeReason或标志 _UninitReason 来查找(请参阅 检查停止 MQL 程序的状态和原因章节)。

请注意,更改图表的交易品种或时间范围时,以及更改输入参数时,EA 交易将保留在内存中,即不会执行步骤 6 至 7(最终清理和卸载)以及步骤 1 至 2(加载和主内存分配),因此全局变量和静态变量的值不会进行重置。在这种情况下,会分别针对旧的和新的交易品种/时间范围(或者旧的和新的设置)依次调用 OnDeinitOnInit 处理程序。

由于 EA 交易中的全局变量不会被清除,因此反初始化代码 _UninitReasonOnInit 处理程序中保持不变,可供分析使用。只有在下一个事件发生时,且在调用 OnDeinit之前,新代码才会被写入变量中。

OnInit函数结束之前接收到的针对 EA 交易的所有事件都会被跳过。

当 MQL 程序首次启动时,设置对话框会在步骤 1 和步骤 2 之间显示。当更改输入参数时,设置对话框会根据程序类型以不同方式插入到主循环中:对于指标,对话框仍会在步骤 2 之前显示,对于 EA 交易,则会在步骤 3 之前显示。
 
本书附带名为LifeCycle.mq5的指标和 EA 交易模板。它会在OnInit/OnDeinit处理程序中记录全局初始化/最终清理步骤。将程序放置在图表上,观察以下各种用户操作会触发哪些事件:加载/卸载、更改参数、切换交易品种/时间范围。

脚本仅在添加到图表时加载一次。如果脚本正在循环运行,重新编译不会导致其重启。

服务通过终端界面上下文菜单命令进行加载和卸载。若某个已在运行的服务被重新编译,该服务将重新启动。请记住,处于活动状态的服务实例将在终端启动时自动加载,并在终端关闭时自动卸载。

在接下来两个章节,我们将从事件处理程序的层面探讨启动不同 MQL 程序的特性。