来源与维护说明(2026 年 9 月): 文章
1972的 2019 年导出只包含前置元数据和空白正文,没有可保留的历史教程。本文明确披露为依据标题重建,并只使用当前 MetaQuotes 与 Microsoft 一手文档。示例是在 MetaTrader 5/MQL5 中调用离线算术函数;它不下单、不绕过 DLL 权限、不连接经纪商,也不承诺任何财务结果。
DLL 会在终端进程内执行原生代码。错误的签名、过期依赖、越界写入、阻塞调用或不可信二进制文件,都可能让终端停止运行或遭到破坏。应把 DLL 当作小型、带版本的系统接口,而不是绕过 MQL 或平台安全措施的捷径。
Table of Contents
什么时候不该使用 DLL
如果工作能用 MQL5 或 EX5 库完成,就优先使用它们。以下情况应避免原生 DLL:
- 目的只是普通计算、文件处理或 MQL5 已支持的数据结构;
- 必须使用远程或 MQL5 Cloud 策略测试代理,因为这些代理不允许 DLL 调用;
- 无法核实库的源码、发布者、依赖或构建记录;
- 接口需要 C++ 对象、STL 容器、裸所有权转移、回调或长期保存 MQL 内存指针;
- 必须把凭据、账户秘密、API 令牌或私钥嵌入二进制文件;
- 失败或延迟可能阻塞调用它的 MQL 程序线程。
只有面对狭窄的原生依赖或经过测量的瓶颈时才使用 DLL。本示例不把交易决策、权限、品种状态或订单操作放进 DLL。
加载之前先处理信任与可见授权
MetaTrader 5 把 DLL 导入标为潜在危险。平台级 Allow DLL imports 选项提供默认值;应用的 Dependencies 选项卡会显示外部模块,并控制该应用的权限。不需要时保持平台默认关闭,检查显示的依赖,只对已经核实发布者和精确文件哈希的 DLL 授权。
MQL5 使用早期绑定:声明的 DLL 可能在 OnStart() 或 OnInit() 运行前加载。因此,程序内权限检查适合记录日志,却不能替代平台的可见同意对话框。不要隐藏该对话框、自动修改终端配置,也不要建议用户为未知程序全局启用 DLL。
示例脚本使用 #property script_show_inputs 来显示属性窗口。保持 AutoTrading 关闭:这个演示不包含任何交易调用,也不需要交易权限。
定义一个小型、带版本的 ABI
第一个接口应只使用固定宽度整数、double、调用方拥有的数组、显式长度、调用方拥有的输出空间以及整数状态码。不要返回指针或 C++ 对象。
| 边界问题 | 本文采用的契约 |
|---|---|
| 调用约定 | 使用 MetaQuotes 对原生导入要求的 __stdcall。 |
| 链接/导出 | extern "C" 加 __declspec(dllexport);用 DUMPBIN /EXPORTS 验证最终名称。 |
| 架构 | 本文为 x64 MetaTrader 5 终端构建 x64 DLL。32 位目标是独立制品,必须单独检查和测试。 |
| 版本 | MtApiVersion() 返回 1;不兼容变更必须使用新的 DLL 文件名和 API 版本。 |
| 内存 | 所有输入与输出缓冲区都由 MQL 拥有;DLL 不保留也不释放它们。 |
| 错误 | 0 表示成功;其他稳定整数表示参数或数值错误。 |
原生实现
保存为 mt_safe_math.cpp:
#include <cmath>
#include <cstdint>
#if defined(_WIN32)
#define MT_API extern "C" __declspec(dllexport)
#define MT_CALL __stdcall
#else
#define MT_API extern "C"
#define MT_CALL
#endif
namespace {
constexpr std::int32_t kApiVersion = 1;
constexpr std::int32_t kMaxValues = 1'000'000;
enum Status : std::int32_t {
kOk = 0,
kInvalidArgument = 1,
kInvalidOutput = 2,
kNonFiniteValue = 3,
};
}
static_assert(sizeof(std::int32_t) == 4);
static_assert(sizeof(double) == 8);
MT_API std::int32_t MT_CALL MtApiVersion() noexcept {
return kApiVersion;
}
MT_API std::int32_t MT_CALL MtMean(
const double* values,
std::int32_t count,
double* out_mean) noexcept {
if (out_mean == nullptr) {
return kInvalidOutput;
}
*out_mean = 0.0;
if (values == nullptr || count <= 0 || count > kMaxValues) {
return kInvalidArgument;
}
double sum = 0.0;
for (std::int32_t index = 0; index < count; ++index) {
if (!std::isfinite(values[index])) {
return kNonFiniteValue;
}
sum += values[index];
if (!std::isfinite(sum)) {
return kNonFiniteValue;
}
}
*out_mean = sum / static_cast<double>(count);
return kOk;
}
非 Windows 宏分支只用于在其他开发主机上单元测试算术与校验逻辑;MetaTrader 仍然需要 Windows DLL 构建。代码没有自定义 DllMain;Microsoft 建议让它保持最小,而 MSVC /LD 能提供默认入口点。
可移植核心测试
保存为 mt_safe_math_test.cpp:
#include <cassert>
#include <cstdint>
#include <limits>
#if defined(_WIN32)
#define MT_CALL __stdcall
#else
#define MT_CALL
#endif
extern "C" std::int32_t MT_CALL MtApiVersion() noexcept;
extern "C" std::int32_t MT_CALL MtMean(
const double* values,
std::int32_t count,
double* out_mean) noexcept;
int main() {
assert(MtApiVersion() == 1);
double values[] = {1.0, 2.0, 3.0, 4.0};
double mean = -1.0;
assert(MtMean(values, 4, &mean) == 0);
assert(mean == 2.5);
mean = -1.0;
assert(MtMean(nullptr, 4, &mean) == 1);
assert(mean == 0.0);
double invalid[] = {1.0, std::numeric_limits<double>::infinity()};
assert(MtMean(invalid, 2, &mean) == 3);
assert(MtMean(values, 4, nullptr) == 2);
}
在安装了 GCC 的主机上,可移植核心测试为:
g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror
mt_safe_math.cpp mt_safe_math_test.cpp -o mt_safe_math_test
./mt_safe_math_test
测试通过并不代表 Windows ABI、导出表、MetaTrader 权限流程或 MQL 声明已经验证;它们是下面的独立关卡。
MQL5 导入与冒烟脚本
保存为 MQL5/Scripts/MtSafeMathSmoke.mq5:
#property script_show_inputs
#import "mt_safe_math_v1.dll"
int MtApiVersion();
int MtMean(double &values[], int count, double &out_mean);
#import
enum NativeStatus
{
MT_OK = 0,
MT_INVALID_ARGUMENT = 1,
MT_INVALID_OUTPUT = 2,
MT_NON_FINITE_VALUE = 3
};
void OnStart()
{
bool terminal_dlls=(bool)TerminalInfoInteger(TERMINAL_DLLS_ALLOWED);
bool program_dlls=(bool)MQLInfoInteger(MQL_DLLS_ALLOWED);
bool terminal_x64=(bool)TerminalInfoInteger(TERMINAL_X64);
PrintFormat("dll-default=%s dll-program=%s x64=%s",
terminal_dlls ? "true" : "false",
program_dlls ? "true" : "false",
terminal_x64 ? "true" : "false");
if(!program_dlls || !terminal_x64)
{
Print("Stop: this smoke test requires explicit DLL consent and x64.");
return;
}
int api_version=MtApiVersion();
if(api_version!=1)
{
PrintFormat("Stop: incompatible native API version %d",api_version);
return;
}
double values[]={1.0,2.0,3.0,4.0};
double mean=0.0;
ResetLastError();
int status=MtMean(values,ArraySize(values),mean);
int mql_error=GetLastError();
PrintFormat("native-status=%d mean=%.8f mql-error=%d",
status,mean,mql_error);
if(status!=MT_OK)
Print("Native calculation rejected the input; no other action was taken.");
}
MQL 原型必须与原生参数顺序和大小完全一致。数组按引用传递,长度单独传入。尽管 MQL 导入语法不能表达原生的 const 限定,DLL 仍把数组视为只读。
构建、检查并标识 Windows 制品
使用 x64 Native Tools Command Prompt for Visual Studio 和干净的构建目录。记录源码提交、cl /Bv 输出、Windows SDK 版本、完整命令和最终哈希。可重复的发布流程比没有记录的 IDE 点击操作更重要。
cl /Bv
cl /nologo /std:c++17 /O2 /W4 /WX /EHsc /MT /LD mt_safe_math.cpp ^
/link /OUT:mt_safe_math_v1.dll /INCREMENTAL:NO
dumpbin /headers mt_safe_math_v1.dll | findstr /i machine
dumpbin /exports mt_safe_math_v1.dll
除非头部报告预期 x64 machine,且导出表恰好包含可调用名称 MtApiVersion 和 MtMean,否则立即停止。extern "C" 控制 C++ 名称改编,但修饰规则与调用约定会因架构而异。不要为了让 MQL 调用“看起来能工作”而猜测或重命名修饰后的导出;应有意修复并验证 ABI。
使用同一套已记录工具链和输入,在两个全新目录中各构建一次,然后比较 SHA-256。若不同,先调查构建输入,再把流程称为确定性构建。不要声称编译器或 SDK 升级后仍保证同一哈希。
Get-FileHash -LiteralPath .mt_safe_math_v1.dll -Algorithm SHA256
Get-AuthenticodeSignature -LiteralPath .mt_safe_math_v1.dll |
Format-List Status,StatusMessage,SignerCertificate
哈希能标识精确字节,却不能证明由谁产生。通过经过认证的发布渠道发布哈希;向他人分发二进制文件时,对 DLL 签名并验证预期发布者。把发布清单、签名结果、导出项、架构、API 版本和测试结果一起保存。
字符串、数组、结构与所有权
MetaQuotes 记录的限制应直接决定接口形状:
| MQL 值 | 原生边界规则 |
|---|---|
| 简单标量 | 除非显式声明为引用,否则按值传递;必须匹配精确大小。 |
double &array[] | DLL 收到数据缓冲区起点。它不知道 ArraySetAsSeries;另行传入并验证元素数。 |
按值 string | DLL 收到复制后字符串缓冲区的指针;不得保留。 |
string & | 指向原始字符串缓冲区。修改与容量规则容易出错,不应出现在第一个 ABI 中。 |
| 文本协议 | 优先使用以明确代码页(例如 CP_UTF8)生成、由调用方拥有的 uchar[],并传入字节长度与输出容量;定义长度是否包含终止符。 |
| 简单结构 | 只考虑不含字符串、类、指针或动态数组的 POD 类结构;显式镜像打包与字段宽度。MQL5 结构默认紧凑排列。 |
| 复杂结构或字符串数组 | 不要传给导入 DLL;MetaQuotes 明确限制这些类型。 |
导入调用返回后,绝不能保留 MQL 数组或字符串指针。绝不能在 DLL 中用 new/malloc 分配,再要求 MQL 或另一个运行时释放。Microsoft 记录了不同运行时之间跨 DLL 边界传递内存或 CRT 对象造成堆损坏的风险。由调用方分配缓冲区、显式提供容量、只在边界内写入,更容易审计。
错误、日志与秘密
同时使用两个错误通道,但不要混为一谈:
- 原生函数返回有文档的状态码,并在失败时把调用方拥有的输出初始化为安全值;
- MQL 单独记录
GetLastError(),用于终端/运行时诊断。
不要让 C++ 异常跨越 C ABI。导出函数保持 noexcept,把内部失败转换为稳定状态码。不要记录完整市场数据集、账户标识、包含用户名的路径或凭据。有效诊断记录应包含终端 build 与架构、DLL API 版本、预期发布 ID/哈希、函数名、元素数、原生状态、MQL 错误和耗时。
DLL 不能包含经纪商凭据、API 密钥、签名密钥、账户密码或“隐藏”端点;原生二进制文件可以被检查。如果以后设计确实需要特权外部访问,应单独定义威胁模型与秘密存储,不能把秘密偷塞进这个计算边界。
Strategy Tester 与演示测试计划
MetaQuotes 说明远程测试代理和 MQL5 Cloud 代理不能执行 DLL 调用;本地代理只有在启用 Allow import DLL 时才能调用。应正面设计这一限制,不要规避,也不要静默回退到未经审核的代码。
按以下顺序执行:
- 对无效、边界、非有限值与最大规模输入运行可移植核心测试。
- 在干净环境构建 x64 DLL;检查头部、依赖和导出;验证哈希/签名。
- 终端关闭时,把经过验证且带版本的 DLL 复制到终端数据目录的
MQL5/Libraries文件夹。 - 使用一次性或模拟终端配置,保持 AutoTrading 关闭,并授予最小权限。
- 无警告编译冒烟脚本,检查 Dependencies,只明确允许这个已知 DLL。
- 连续运行两次并比较 Journal。预期结果:API
1、状态0、均值2.50000000,且没有交易动作。 - 如需 Strategy Tester 覆盖,只使用明确允许 DLL 的本地代理,并把远程/云优化标为不支持。
- 更广泛部署前先演练移除与回滚。
应在独立测试进程中对原生函数做模糊或压力测试,而不是反复冒险让终端崩溃。MetaQuotes 说明 DLL 代码在调用模块线程中执行,因此导出调用必须短小。
允许清单、部署、回滚与移除
允许清单条目应包含:
- 唯一 DLL 文件名与原生 API 版本;
- x64 架构与预期导出名称;
- SHA-256 与预期签名者身份/状态;
- 源码版本、MSVC/SDK 版本与构建命令;
- 直接依赖及许可证;
- 已通过测试与批准日期。
把 DLL 与匹配的 MQL 源码/EX5 作为一对审核并部署。使用带版本文件名,避免回滚时覆盖已加载模块。替换或删除 DLL 前:关闭该应用的 DLL 权限,从图表移除脚本/EA,关闭终端并确认已经退出,然后恢复上一对获批版本或删除带版本文件。之后在模拟配置中重新打开并运行冒烟测试。
终端可能仍加载 DLL 时绝不能覆盖它。不要把终端目录加入宽泛搜索路径,不要把依赖复制进 Windows 系统目录,也不要通过禁用安全控制来“修复”加载。
故障排查矩阵
| 症状 | 只读检查 | 安全响应 |
|---|---|---|
程序在 OnStart() 前停止 | Journal、Dependencies、DLL 权限、精确文件名 | 只有完成信任验证后才恢复权限;确认文件位于 MQL5/Libraries。 |
| “Function not found” | dumpbin /exports、拼写、API 版本 | 重新构建匹配的一对;不要猜修饰名称。 |
| DLL 无法加载 | dumpbin /headers、dumpbin /dependents、终端 x64 状态 | 提供正确架构和经过审核的依赖;不要从网上随机复制 DLL。 |
| 终端崩溃或输出损坏 | 原型顺序/类型、数组长度、输出指针、原生单元测试 | 禁用导入,关闭终端,移除候选 DLL,恢复上一获批版本。 |
| 本地测试成功,远程优化失败 | 测试代理类型与官方 DLL 限制 | 标为不支持远程/云,或移除 DLL 依赖;不要绕过限制。 |
| 哈希或签名不同 | 发布清单、签名者、源码/工具链记录 | 隔离文件,来源未解决前停止部署。 |
接受与停止标准
只有满足以下条件才接受发布:
- 源码与构建输入已版本化,干净构建流程有记录;
- 精确架构、依赖、两个导出名、API 版本、SHA-256 和签名状态都匹配允许清单;
- 原生正向/反向测试通过,且没有无法解释的 sanitizer/静态分析发现;
- MQL 冒烟脚本无警告编译,并在模拟配置中连续两次产生相同预期日志;
- AutoTrading 保持关闭,不发生网络、文件、凭据或订单操作;
- 已记录本地 Strategy Tester 行为与远程/云不支持;
- 已成功演练回滚和完整移除。
出现意外依赖/导出、权限提示不一致、崩溃、挂起、越界报告、无法解释的非确定性构建、哈希/签名不符、源码/二进制/日志中出现秘密,或试图启用真实交易、绕过终端防护时,应立即停止。
一手文档
- MetaQuotes:调用导入函数
- MetaQuotes:`#import` 与支持的参数形式
- MetaQuotes:MQL5 结构与打包
- MetaQuotes:MQL5 Unicode 字符串类型
- MetaQuotes:`StringToCharArray()` 与显式代码页
- MetaTrader 5 帮助:平台 DLL 设置
- MetaTrader 5 帮助:应用 Dependencies 与 DLL 授权
- MetaQuotes:Strategy Tester 的 DLL 限制
- Microsoft:从 DLL 导出
- Microsoft:`dllexport` 与名称修饰
- Microsoft:`/LD` 与运行库选择
- Microsoft:DUMPBIN 选项
- Microsoft:跨 DLL 边界的 CRT 对象与内存
- Microsoft:DLL 入口点最佳实践
- Microsoft:`Get-FileHash`
- Microsoft:`Get-AuthenticodeSignature`
- Microsoft:使用 SignTool 验证文件签名
