创建 ex5 库;函数 export

要说明一个库,请将 #property library 指令添加到主模块(已编译)的源代码中(通常在文件的开头)。

#property library

在编译过程中,通过 #include 在任何其他文件中指定该指令将无效。

library 特性通知编译器给定的 ex5 文件是一个库:在 ex5 文件的头文件中存储了一个标记。

在 MetaTrader 5 中为库保留了一个单独的文件夹 MQL5/Libraries。可以在其中组织嵌套文件夹的层级,就像 MQL5 中对其他类型程序所做的操作一样。

库不直接参与事件处理,因此编译器不要求代码中存在任何标准处理程序。但是,您可以从该库所连接的 MQL 程序的事件处理程序中调用该库的导出函数。

要从库中导出一个函数,只需用一个特殊的 export 关键字。该修饰符必须放在函数头文件的最末端。

result_type function_id ( [ parameter_type parameter_id
                          [ = default_value] ...] ) export
{
   ...
}

参数必须是简单类型或字符串、具有这种类型的字段的结构或它们的数组。MQL5 对象类型允许指针和引用(有关导入 DLL 的限制,参见 相关章节)。

让我们来分析一些例子。参数是一个质数:

double Algebraic2(const double xexport
{
   return x / sqrt(1 + x * x); 
}

这些参数是指向一个对象的指针和指向一个指针的引用(允许您在函数内部分配一个指针)。

class X
{
public:
   X() { Print(__FUNCSIG__); }
};
void setObject(const X *objexport { ... }
void getObject(X *&objexport { obj = new X(); }

参数是一个结构体:

struct Data
{
   int value;
   double data[];
   Data(): value(0) { }
   Data(const int i): value(i) { ArrayResize(datai); }
};
   
void getRefStruct(const int iData &dataexport { ... }

只能导出函数,而不能导出整个类或结构。在指针和引用的帮助下,可以避免这些限制,我们将在后面详细讨论有关内容。

不能使用 export 关键字和 #import 指令声明函数模板。

export 修饰符指示编译器将该函数包含在给定的 ex5 可执行文件的导出函数表中。借助这个功能,这类函数可以从其他 MQL 程序中获得(“可见”),使用特殊指令 #import 导入之后,它们就可以使用了

所有要导出的函数都必须用 export 修饰符标记。主程序不需要导入所有这些函数,因为它只能导入必要的函数。

如果您忘记导出一个函数,但将它包含在主 MQL 程序的导入指令中,那么当后者启动时,将会出现一个错误:

cannot find 'function' in 'library.ex5'
unresolved import function call

如果导出函数和它的导入原型的描述存在差异,也会出现类似问题。例如,如果您在更改编程接口(通常在单独的头文件中说明)后忘记重新编译库或主程序,就会发生这种情况。

无法对库进行调试,所以如果有必要,应该有一个帮助脚本或另一个 MQL 程序,它是在调试器模式下从库的源代码构建的,并且可以通过断点或分步执行。当然,这将需要使用一些真实或人工数据来模拟对导出函数的调用。

对于 DLL,导出函数以不同方式描述,具体取决于创建它们的编程语言。详细情况,请查询您选择的开发环境的文档。

我们以一个简单库 MQL5/Libraries/MQL5Book/LibRand.mq5 为例进行分析,从这个库中导出了几个函数,它们具有不同类型的参数和结果。该库设计用于生成随机数据:

  • 具有伪正态分布的数值数据
  • 具有来自给定集的随机字符的字符串(可能适用于密码)

特别是,可以使用 PseudoNormalValue 函数获得一个随机数,其中期望值和方差被设置为参数。

double PseudoNormalValue(const double mean = 0.0const double sigma = 1.0,
   const bool rooted = falseexport
{
   // use ready-made sqrt for mass generation in a cycle in PseudoNormalArray
   const double s = !rooted ? sqrt(sigma) : sigma
   const double r = (rand() - 16383.5) / 16384.0// [-1,+1] excluding borders
   const double x = -(log(1 / ((r + 1) / 2) - 1) * s) / M_PI * M_E + mean;
   return x;
}

PseudoNormalArray 函数用给定数量 (n) 的随机值填充数组,并具有所需的分布。

bool PseudoNormalArray(double &array[], const int n,
   const double mean = 0.0const double sigma = 1.0export
{
   bool success = true;
   const double s = sqrt(fabs(sigma)); // passing ready sqrt when calling PseudoNormalValue
   ArrayResize(arrayn);
   for(int i = 0i < n; ++i)
   {
      array[i] = PseudoNormalValue(meanstrue);
      success = success && MathIsValidNumber(array[i]);
   }
   return success;
}

为了生成一个随机字符串,我们编写了 RandomString 函数,它从提供的字符集 (pattern) 中“选择”给定数量 (length) 的任意字符。当 pattern 参数为空(默认)时,假定为一组完整的字母和数字。辅助函数 StringPatternAlphaStringPatternDigit 用于获取这些字母和数字;这些函数也是可导出函数(本书未列出,请参见源代码)。

string RandomString(const int lengthstring pattern = NULLexport
{
   if(StringLen(pattern) == 0)
   {
      pattern = StringPatternAlpha() + StringPatternDigit();
   }
   const int size = StringLen(pattern);
   string result = "";
   for(int i = 0i < length; ++i)
   {
      result += ShortToString(pattern[rand() % size]);
   }
   return result;
}

一般来说,要使用一个库,必须发布一个头文件,说明可以从外部获得的所有内容(内部实现的细节可以而且应该被隐藏)。在我们的例子中,该文件被称为 MQL5Book/LibRand.mqh。特别是,它说明了用户定义的类型(在我们的例子中是 STRING_PATTERN 枚举)和函数原型。

虽然我们还不知道 #import 块的确切语法,但这不应该影响它内部声明的清晰性:这里重复了导出函数的头文件,但没有使用 export 关键字。

enum STRING_PATTERN
{
   STRING_PATTERN_LOWERCASE = 1// lowercase letters only
   STRING_PATTERN_UPPERCASE = 2// capital letters only
   STRING_PATTERN_MIXEDCASE = 3  // both registers
};
   
#import "MQL5Book/LibRand.ex5"
string StringPatternAlpha(const STRING_PATTERN _case = STRING_PATTERN_MIXEDCASE);
string StringPatternDigit();
string RandomString(const int lengthstring pattern = NULL);
void RandomStrings(string &array[], const int nconst int minlength,
   const int maxlengthstring pattern = NULL);
void PseudoNormalDefaultMean(const double mean = 0.0);
void PseudoNormalDefaultSigma(const double sigma = 1.0);
double PseudoNormalDefaultValue();
double PseudoNormalValue(const double mean = 0.0const double sigma = 1.0,
   const bool rooted = false);
bool PseudoNormalArray(double &array[], const int n,
   const double mean = 0.0const double sigma = 1.0);
#import

探讨了 #import 指令之后,我们将在下一节编写一个使用该库的测试脚本。