获取终端和程序特性的总列表

可用于获取环境特性的内置函数采用普通方法:每种特定类型的特性被组合到一个单独函数中,该单独函数具有一个指定所请求特性的自变量。定义了一些用于识别特性的枚举:每个元素描述一个特性。

我们将在下面看到,该方法通常用于 MQL5 API 以及其它领域中,包括应用程序领域。尤其是类似的函数集用于获取 交易账户金融工具的特性。

三个简单类型的特性(intdoublestring)即足以描述环境。然而,不仅使用 int 类型的值呈现了整数特性,还有逻辑标志(尤其是权限/禁止、网络连接存在性等)以及其它内置枚举(如 MQL 程序类型以及许可证类型)。

根据终端特性和特定 MQL 程序特性的条件性划分,有以下描述环境的函数。

int MQLInfoInteger(ENUM_MQL_INFO_INTEGER p)

int TerminalInfoInteger(ENUM_TERMINAL_INFO_INTEGER p)

double TerminalInfoDouble(ENUM_TERMINAL_INFO_DOUBLE p)

string MQLInfoString(ENUM_MQL_INFO_STRING p)

string TerminalInfoString(ENUM_TERMINAL_INFO_STRING p)

这些原型将值类型映射到枚举类型。例如,int 类型的终端特性在 ENUM_TERMINAL_INFO_INTEGER 中概括,而其 double 类型的特性列在 ENUM_TERMINAL_INFO_DOUBLE 中。可用枚举及其元素的列表参见关于 终端特性MQL 程序章节。

在以下章节中,我们将了解基于其用途而分组的所有特性。但在这里我们先探讨获取所有现有特性以及它们的值的总列表的问题。通常来说这是有必要的,可识别 MQL 程序在特定终端实例上的运行“瓶颈”或特性。一种常见情况是一个 MQL 程序在一台计算机上可正常运行,但在另一台计算机上完全无法运行,或者能运行但表现出某些问题。

特性列表随着平台开发而持续更新,因此最好不要基于源代码中的固定列表作出请求,而是自动请求。

枚举 一节中,我们创建了模板函数 EnumToArray 用于获取枚举元素的完整列表(EnumToArray.mqh 文件)。同样在该章节中,我们介绍了脚本 ConversionEnum.mq5,该脚本使用指定的头文件。在该脚本中,实施了辅助函数 process,该函数接受具有枚举元素的数组,并将它们输出到日志。我们将把取得的这些进展作为进一步改进的起点。

我们需要修改 process 函数,使得我们不仅能够获取特定枚举的元素的列表,而且能够使用内置特性函数之一查询对应特性。

我们赋予新版本脚本一个名称:Environment.mq5

由于环境的特性分散在若干个不同的函数中(在本例中,五个函数),你需要学会如何向新版本的函数 process 传递需要的内置函数的指针(参见 函数指针 (typedef)章节)。然而,MQL5 不允许将一个内置函数的地址指派给一个函数指针。只能通过在 MQL5 中实施的一个应用函数完成。因此,我们将创建包装器函数。例如:

int _MQLInfoInteger(const ENUM_MQL_INFO_INTEGER p)
{
   return MQLInfoInteger(p);
}
// example of pointer type description  
typedef int (*IntFuncPtr)(const ENUM_MQL_INFO_INTEGER property);
// initialization of pointer variables
IntFuncPtr ptr1 = _MQLInfoInteger;  // ok
IntFuncPtr ptr2 = MQLInfoInteger;   // compilation error

上面显示了 MQLInfoInteger 的一个“分身”(很明显,它应具有一个不同但最好类似的名称)。其它函数以类似方式“包装”。总共将有五个。

如果在旧版本的 process 中仅有一个指定一个枚举的模板参数,则在新版本中我们还需要传递返回值的类型(因为 MQL5 不“理解”枚举名称中的字词):即使结尾的 "INTEGER" 出现在名称 ENUM_MQL_INFO_INTEGER 中,编译器也不能够将它与类型 int 关联)。

然而,除了链接返回值的类型和枚举,我们还需要以某种方式将恰当的包装器函数(我们早前定义的五个函数中的一个)的指针传递给 process 函数。毕竟,编译器本身不能够通过例如一个 ENUM_MQL_INFO_INTEGER 类型的自变量确定 MQLInfoInteger 需要被调用。

为解决这个问题,创建了一个将所有三个因素组合起来的特殊模板结构。

template<typename Etypename R>
struct Binding
{
public:
   typedef R (*FuncPtr)(const E property);
   const FuncPtr f;
   Binding(FuncPtr p): f(p) { }
};

两个模板参数允许你以所需的结果和输入参数的组合来指定函数指针 (FuncPtr) 的类型。该结构体实例具有用于该新定义类型的指针的 f 字段。

现在,一个新版本的 process 函数可描述如下。

template<typename Etypename R>
void process(Binding<ER> &b)
{
   E e = (E)0// turn off the warning about the lack of initialization
   int array[];
   // get a list of enum elements into an array
   int n = EnumToArray(earray0USHORT_MAX);
   Print(typename(E), " Count="n);
   ResetLastError();
   // display the name and value for each element,
   // obtained by calling a pointer in the Binding structure
   for(int i = 0i < n; ++i)
   {
      e = (E)array[i];
      R r = b.f(e); // call the function, then parse _LastError
      const int snapshot = _LastError;
      PrintFormat("% 3d %s=%s"iEnumToString(e), (string)r +
         (snapshot != 0 ? E2S(snapshot) + " (" + (string)snapshot + ")" : ""));
      ResetLastError();
   }
}

输入自变量为 Binding 结构体。它包含用于获取特性的特定函数的指针(该字段将由调用代码填充)。

该版本的算法将系列号、特性标识符及其值记入日志。同样需注意的是,每个条目中的第一个数字将包含枚举中元素的序数,而不是值(值可以间隔赋予给元素)。或者你可以在 print format 指令中添加一个“纯形式”的变量 e 的输出。

此外,还可以修改该过程,使之将生成的特性值收集进一个数组(或者其它容器,如映射),并“向外”返回这些特性值。

若在 print format 指令中直接引用函数指针并结合 _LastError 错误代码分析,可能会造成潜在错误。问题在于,这样一来,表达式中的函数自变量(参见 参数和自变量章节)和操作数(参见 基本概念章节)的求值顺序未定义。因此,当在调用 _LastError 的同一行中调用指针时,编译器的执行顺序可能会出错。结果,我们将看到一个不相关的错误代码(例如,来自一个先前函数调用的错误代码)。

但不仅于此。在表达式的求值中,如果任何运算失败,内置变量 _LastError 可能在任何地方更改其值。尤其是如果一个值作为不在枚举范围内自变量被传递,则函数 EnumToString 可能产生错误代码。在这个代码片段中,我们不存在此问题,因为我们的函数 EnumToArray 返回一个只包含校验过的(有效)枚举元素的数组。然而,在一般情况下,在任何“复合”指令中,可能很多情况下 _LastError 会被更改。对此,最好在我们感兴趣的目标操作(在这里是由指针进行的函数调用)完成后立即修复错误代码,将其保存到一个中间变量 snapshot

我们回到主要问题。我们终于可以组织对新函数 process 的调用,从而获取软件环境的各种特性。

void OnStart()
{
   process(Binding<ENUM_MQL_INFO_INTEGERint>(_MQLInfoInteger));
   process(Binding<ENUM_TERMINAL_INFO_INTEGERint>(_TerminalInfoInteger));
   process(Binding<ENUM_TERMINAL_INFO_DOUBLEdouble>(_TerminalInfoDouble));
   process(Binding<ENUM_MQL_INFO_STRINGstring>(_MQLInfoString));
   process(Binding<ENUM_TERMINAL_INFO_STRINGstring>(_TerminalInfoString));
}

下面是生成的日志条目的一个片断。

ENUM_MQL_INFO_INTEGER Count=15
  0 MQL_PROGRAM_TYPE=1
  1 MQL_DLLS_ALLOWED=0
  2 MQL_TRADE_ALLOWED=0
  3 MQL_DEBUG=1
...
  7 MQL_LICENSE_TYPE=0
...
ENUM_TERMINAL_INFO_INTEGER Count=50
  0 TERMINAL_BUILD=2988
  1 TERMINAL_CONNECTED=1
  2 TERMINAL_DLLS_ALLOWED=0
  3 TERMINAL_TRADE_ALLOWED=0
...
  6 TERMINAL_MAXBARS=100000
  7 TERMINAL_CODEPAGE=1251
  8 TERMINAL_MEMORY_PHYSICAL=4095
  9 TERMINAL_MEMORY_TOTAL=8190
 10 TERMINAL_MEMORY_AVAILABLE=7813
 11 TERMINAL_MEMORY_USED=377
 12 TERMINAL_X64=1
...
ENUM_TERMINAL_INFO_DOUBLE Count=2
  0 TERMINAL_COMMUNITY_BALANCE=0.0 (MQL5_WRONG_PROPERTY,4512)
  1 TERMINAL_RETRANSMISSION=0.0
ENUM_MQL_INFO_STRING Count=2
  0 MQL_PROGRAM_NAME=Environment
  1 MQL_PROGRAM_PATH=C:\Program Files\MT5East\MQL5\Scripts\MQL5Book\p4\Environment.ex5
ENUM_TERMINAL_INFO_STRING Count=6
  0 TERMINAL_COMPANY=MetaQuotes Software Corp.
  1 TERMINAL_NAME=MetaTrader 5
  2 TERMINAL_PATH=C:\Program Files\MT5East
  3 TERMINAL_DATA_PATH=C:\Program Files\MT5East
  4 TERMINAL_COMMONDATA_PATH=C:\Users\User\AppData\Roaming\MetaQuotes\Terminal\Common
  5 TERMINAL_LANGUAGE=Russian
 

这些特性以及其它特性将在以下章节中介绍。

值得说明的是,一些特性是继承自平台开发的前期阶段,仅为了兼容性而保留。尤其是 TerminalInfoInteger 中的 TERMINAL_X64 特性返回关于终端是否是 64 位的指示。如今,32 位版本的开发已经停止,因此该特性始终等于 1 (true)。