使用包含文件 (.mqh
) 来组织函数
随着我们创建的辅助函数越来越多(如 PipPoint
, GetSlippage
, CalcLotSize
, VerifyLotSize
, OpenBuyOrder
, AddStopProfit
等),将它们全部放在主 EA 文件中会使代码显得冗长、结构混乱且难以维护。一个更好的组织方式是将这些可重用的辅助函数统一放到一个或多个包含文件中。
- 什么是包含文件 (
.mqh
)?- 它是一个纯粹的MQL源代码文件,通常以
.mqh
作为扩展名。 - 文件内容可以包括:函数定义、全局变量声明、
#define
宏定义、#import
导入声明等。 - 它不能包含 MQL 程序的入口点函数,如
OnInit()
,OnTick()
,OnDeinit()
等。
- 它是一个纯粹的MQL源代码文件,通常以
- 存放位置: 为了让 MQL 编译器能找到它们,
.mqh
文件通常需要放在 MetaTrader 数据文件夹下的特定子目录中:- MQL4:
MQL4\Includes\
- MQL4:
- 如何使用 (
#include
): 在您的主EA、指标或脚本文件的开头部分(通常在#property
之后,全局变量声明之前),使用#include
预处理器指令来引入.mqh
文件。#property copyright "My EA" #property link "mywebsite.com" #include <stdlib.mqh> // 使用尖括号 <> 包含 MQL 标准库路径下的文件 #include "MyTradeFunctions.mqh" // 使用双引号 "" 包含与当前 EA 文件在同一目录或其子目录下的自定义文件 // --- 全局变量 --- // ... // --- init(), start(), deinit() --- // ... // --- 函数定义 (如果有些函数不放在 .mqh 里) --- // ...
编译器在处理主文件时,遇到
#include
指令,会找到对应的.mqh
文件,并将其全部内容如同复制粘贴一样插入到#include
指令所在的位置,然后将合并后的完整代码进行编译。
使用包含文件是实现代码模块化和重用的标准且推荐的方式。
使用库文件 (.ex4
)
除了包含文件 (.mqh
),MQL 还支持另一种代码复用机制:库文件。
- 什么是库文件?
- 库文件是一个已编译的MQL程序(MQL4 中是
.ex4
文件),它专门用于提供函数供其他程序(如 EA、指标、脚本)调用。 - 它与普通 EA 或指标不同,库文件本身不能直接运行在图表上,因为它没有
OnTick
,OnInit
,OnDeinit
等事件处理函数。 - 要创建一个库文件,其源代码文件(
.mq4
或.mq5
)必须在文件开头使用#property library
预处理器指令进行声明。
- 库文件是一个已编译的MQL程序(MQL4 中是
- 存放位置: 编译后的库文件 (
.ex4
) 需要放在 MetaTrader 数据文件夹下的特定子目录中:- MQL4:
MQL4\Libraries\
- MQL4:
- 与包含文件的核心区别:
- 包含文件 (
.mqh
): 提供源代码。在编译调用程序(如 EA)时,其内容被#include
指令直接合并到 EA 的代码中,最终编译成一个单独的 EA 可执行文件。 - 库文件 (
.ex4
): 提供已编译的代码(二进制形式)。调用程序(EA)在编译时只链接库函数的接口声明。在运行时,EA 需要能够找到并加载对应的.ex4
库文件,才能实际调用其中的函数。这意味着,如果您部署一个依赖库的 EA,必须同时提供 EA 的.ex4
文件和所有它依赖的库.ex4
文件。
- 包含文件 (
- 优点:
- 源代码保护: 由于您分发的是编译后的文件,可以有效保护函数实现的知识产权,避免源代码泄露,尤其适用于商业分发。
- 独立更新与维护: 如果库的开发者修复了内部错误,但没有改变任何导出函数的接口(即函数名、参数类型和数量、返回值类型保持不变),那么只需要更新
.ex4
/.ex5
库文件,所有依赖该库的 EA 无需重新编译即可自动使用更新后的功能。
- 缺点:
- 编译时参数检查缺失: 由于库函数是预编译的,当 EA 调用库函数时,编译器无法检查传递的参数类型是否与库函数定义完全匹配,这增加了运行时出错的风险。
- 不支持默认参数值: 在库文件中定义的函数不能为其参数指定默认值。因此,EA 在调用库函数时,必须为该函数的所有参数都显式提供值。
- 变量作用域限制: 库文件不能包含
extern
外部变量(输入参数)。同时,库内部定义的全局变量的作用域也仅限于库本身,不能被调用它的 EA 直接访问(数据交换必须通过函数参数或返回值进行)。 - 部署复杂度增加: 需要管理和分发额外的库文件。
- 如何使用 (
#import
): 要在 EA 中调用库函数,必须使用#import "库文件名.ex4"
指令块来导入所需函数的接口声明。#import "MyCompiledLib.ex4" // 指定要导入的库文件名 (需带扩展名) // 在这里声明需要从该库导入的函数原型 (函数签名必须与库导出时完全一致!) int CalculateSomething(double price, int period); string GetStatus(); // ... 可以声明多个函数 ... #import // 使用一个单独的 #import 来结束导入声明块 // --- 在 EA 代码中就可以像调用普通函数一样调用它们了 --- // int result = CalculateSomething(1.2345, 14); // string status = GetStatus();
如果一个库包含很多函数,通常会将这些
#import
声明放到一个单独的.mqh
包含文件中,然后在 EA 中#include
这个.mqh
文件,以保持主代码的整洁。但这无疑增加了项目文件的数量。 - 建议: 除非您有非常明确的理由需要使用库(例如,商业分发需要保护核心算法源代码),对于大多数情况,使用包含文件 (
.mqh
) 来组织和复用函数通常是更简单、更直接、也更容易调试的选择。
从Windows DLLs导入函数
除了 MQL 库,MQL 还支持通过 #import
指令调用标准的Windows 动态链接库 (.dll) 中的函数。这使得 MQL 程序能够与用其他语言(如 C++, Delphi)编写的模块交互,或者调用 Windows API 实现更底层的功能。
- 示例: MetaTrader 自带的
WinUser32.mqh
(位于 MQL4/5 的Includes
目录下) 就是一个典型例子,它导入了 Windows 核心库user32.dll
中的许多函数,以供 MQL 内置的MessageBox()
弹窗函数使用。(本书将在第 8 章讨论MessageBox()
)。 - 高级应用: 调用 DLL 函数通常涉及更复杂的数据类型处理(例如 MQL字符串与 C 字符串的转换)、内存管理和潜在的稳定性问题,属于 MQL 的高级应用范畴,本系列教学不会深入探讨。对这方面感兴趣的开发者可以查阅 MQL4/MQL5 官方网站上的相关文档和文章。