包括库和函数的 #import

函数从已编译的 MQL5 模块(*.ex5 文件)和 Windows 动态库模块(*.dll 文件)中导入。模块名称在 #import 指令中指定,后面是导入函数原型说明。该代码块必须以另一个 #import 指令结束,此外,它可以没有名称,只需关闭代码块本身,或者可以在指令中指定另一个库的名称,同时下一个导入块启动。一串导入块应该始终以不带库名的指令结尾。

该指令最简单的形式如下:

#import "[path] module_name [.extension]"
  function_type function_name([parameter_list]);
  [function_type function_name([parameter_list]);]
   ... 
#import

可以指定不带扩展名的库文件的名称:默认情况下采用 DLL。扩展名 ex5 是必须的。

该名称前面可以是库地址路径。默认情况下,如果没有路径,将在 MQL5/Libraries 文件夹或库所连接的 MQL 程序旁的文件夹中搜索库。否则,根据类型是 DLL 还是 EX5,可以应用不同的规则来搜索库。这些规则在 单独章节中介绍。

以下是来自两个库的顺序导入块的例子:

#import "user32.dll"
   int     MessageBoxW(int hWndstring szTextstring szCaptionint nType); 
   int     SendMessageW(int hWndint Msgint wParamint lParam); 
#import "lib.ex5" 
   double  round(double value); 
#import

使用这样的指令,可以从源代码中调用导入的函数,就像在 MQL 程序中直接定义函数一样。所有关于加载库和重定向调用到第三方模块的技术事项都由 MQL 程序执行环境处理。

为了让编译器正确地发出对导入函数的调用并组织参数的传递,需要一个完整的说明:包括结果类型、所有参数、修饰符和默认值(如果它们出现在源代码中的话)。

由于导入的函数在已编译模块的外部,因此编译器无法检查传递的参数和返回值是否正确。预期数据和接收数据的格式之间的任何差异都将导致程序执行过程出错,这可能表现为程序的紧急停止或非预期行为。

如果无法加载库或者无法找到调用的导入函数,MQL 程序将终止,并在日志中显示相应的消息。在问题解决之前,程序将无法运行,例如通过以下方式来解决问题:修改和重新编译,将所需的库放在搜索路径上的某个地址,或者允许使用 DLL(仅适用于 DLL)。

当共享多个库时(不管是 DLL 还是 EX5),记住它们必须有不同的名称,无论它们的地址目录是什么。所有导入的函数都有一个与库文件名称匹配的作用域,即它是一种 命名空间,会为每个包含的库隐式分配该命名空间。

导入的函数可以有任何名称,包括与内置函数名称匹配的名称(尽管不建议这样做)。此外,可以从不同的模块中同时导入同名的函数。在这种情况下,应使用 上下文权限 来确定应调用哪个函数。

例如:

#import "kernel32.dll"
   int GetLastError();
#import "lib.ex5" 
   int GetLastError();
#import
  
class Foo
{
public
   int GetLastError() { return(12345); }
   void func() 
   { 
      Print(GetLastError());           // call a class method 
      Print(::GetLastError());         // calling the built-in (global) MQL5 function 
      Print(kernel32::GetLastError()); // function call from kernel32.d 
      Print(lib::GetLastError());      // function call from lib.ex5 
   }
};
   
void OnStart()
{
   Foo foo
   foo.func(); 
}

让我们来分析一个简单的 LibRandTest.mq5 脚本的例子,它使用了上一节中创建的 EX5 库中的函数。

#include <MQL5Book/LibRand.mqh>

在输入参数中,您可以选择数字数组中的元素数量、分布参数以及直方图的步长,我们将计算直方图以确保分布近似符合正态分布。

input int N = 10000;
input double Mean = 0.0;
input double Sigma = 1.0;
input double HistogramStep = 0.5;
input int RandomSeed = 0;

MQL 5 中内置的随机数生成器的初始化由 RandomSeed 的值执行,或者,此处为 0,GetTickCount 被选取(每次启动时都是新的)。

为了构建直方图,我们使用 MapArrayQuickSortStructT(我们已经在 多货币指标 以及有关 数组排序的章节中使用过它们)。该映射将累加直方图单元中命中随机数的计数器,步长为 HistogramStep

#include <MQL5Book/MapArray.mqh>
#include <MQL5Book/QuickSortStructT.mqh>

要显示基于地图的直方图,你需要能够以键值顺序对映射进行排序。为此,我们必须定义一个派生类。

#define COMMA ,
   
template<typename K,typename V>
class MyMapArraypublic MapArray<K,V>
{
public:
   void sort()
   {
      SORT_STRUCT(Pair<K COMMA V>, arraykey);
   }
};

请注意,COMMA 宏成为逗号字符 , 的替代表示形式,并在调用另一个 SORT_STRUCT 宏时使用。如果不进行这种替换,预处理器会将 <K,V> 对中的逗号解释为普通的宏参数分隔符,其结果是 SORT_STRUCT 会收到 4 个参数输入,而不是预期的 3 个,这将导致编译错误。预处理器对 MQL5 语法一无所知。

OnStart 开头,在初始化生成器之后,我们检查单个随机字符串和不同长度的字符串数组的接收。

void OnStart()
{
   const uint seed = RandomSeed ? RandomSeed : GetTickCount();
   Print("Random seed: "seed);
   MathSrand(seed);
   
   // call two library functions: StringPatternDigit and RandomString
   Print("Random HEX-string: "RandomString(30StringPatternDigit() + "ABCDEF"));
   Print("Random strings:");
   string text[];
   RandomStrings(text51020);         // 5 lines from 10 to 20 characters long
   ArrayPrint(text);
   ...

接下来,我们测试正态分布的随机数。

   // call another library function: PseudoNormalArray
   double x[];
   PseudoNormalArray(xNMeanSigma);   // filled array x
   
   Print("Random pseudo-gaussian histogram: ");
   
   // take 'long' as key type, because 'int' has already been used for index access
   MyMapArray<long,intmap;
   
   for(int i = 0i < N; ++i)
   {
 // value x[i] determines the cell of the histogram, where we increase the statistics
      map.inc((long)MathRound(x[i] / HistogramStep));
   }
   map.sort();                             // sort by key (i.e. by value)
   
   int max = 0;                            // searching for maximum for normalization
   for(int i = 0i < map.getSize(); ++i)
   {
      max = fmax(maxmap.getValue(i));
   }
   
   const double scale = fmax(max / 801); // the histogram has a maximum of 80 symbols
   
   for(int i = 0i < map.getSize(); ++i)  // print the histogram
   {
      const int p = (int)MathRound(map.getValue(i) / scale);
      string filler;
      StringInit(fillerp, '*');
      Print(StringFormat("%+.2f (%4d)",
         map.getKey(i) * HistogramStepmap.getValue(i)), " "filler);
   }

以下是使用默认设置运行的结果(计时器随机化 - 每次运行将选择一个 seed)。

随机种子:8859858

随机十六进制字符串:E58B125BCCDA67ABAB2F1C6D6EC677

随机字符串:

"K4ZOpdIy5yxq4ble2" "NxTrVRl6q5j3Hr2FY" "6qxRdDzjp3WNA8xV" "UlOPYinnGd36" "6OCmde6rvErGB3wG"

随机伪高斯直方图:

-9.50 ( 2)

-8.50 ( 1)

-8.00 ( 1)

-7.00 ( 1)

-6.50 ( 5)

-6.00 ( 10) *

-5.50 ( 10) *

-5.00 ( 24) *

-4.50 ( 28) **

-4.00 ( 50) ***

-3.50 ( 100) ******

-3.00 ( 195) ***********

-2.50 ( 272) ***************

-2.00 ( 510) ****************************

-1.50 ( 751) ******************************************

-1.00 (1029) *********************************************************

-0.50 (1288) ************************************************************************

+0.00 (1457) *********************************************************************************

+0.50 (1263) **********************************************************************

+1.00 (1060) ***********************************************************

+1.50 ( 772) *******************************************

+2.00 ( 480) ***************************

+2.50 ( 280) ****************

+3.00 ( 172) **********

+3.50 ( 112) ******

+4.00 ( 52) ***

+4.50 ( 43) **

+5.00 ( 10) *

+5.50 ( 8)

+6.00 ( 8)

+6.50 ( 2)

+7.00 ( 3)

+7.50 ( 1)

在此库中,只有内置类型的导入和导出的函数。然而,从实用的角度来看,具有结构、类和模板的对象接口更有趣,也更受欢迎。我们将在 单独的章节中讨论在库中使用它们的细微差别。

在测试程序中测试 EA 交易和指标时,应该记住与库相关的重要一点。主测试 MQL 程序所需的库是由 #import 指令自动确定的。但是,如果从主程序调用自定义指标,而某个库连接到该主程序,则有必要在程序特性中明确指出它间接依赖于某个特定的库。这是通过以下指令完成的:
 
#property tester_library"path_library_name.extension"