如何交换数据:10 分钟为 MQL5 创建 DLL(基础篇)
📘

如何交换数据:10 分钟为 MQL5 创建 DLL(基础篇)

第 1/2 篇

「十分钟搭一个能被 MQL5 调用的 DLL」

不少写 MQL5 的人卡在第一步:怎么让 EA 跟外部系统说话。最常见也最轻量的办法是写一个 DLL,再在 MT5 里用 #import 挂进来。难点不在语法,而在 Windows 调用约定和导出符号的绑定细节,弄错一个 __stdcall 就直接崩客户端。 官方文档很少把“从零到能跑”的过程讲透。这里用 Visual Studio 2005/2008 走一遍:Express 版可免费从微软站拉,不需要企业授权就能编译出 32 位 DLL,MT5 终端默认加载的就是这类模块。 实操节奏大概是:建 Win32 DLL 工程 → 只导出纯 C 函数(别用 C++ 修饰名)→ 编译出 .dll → 丢进 MT5 的 MQL5/Libraries 目录。整套流程熟练后十分钟能跑通一个加法或字符串回显的样例。外汇与贵金属交易本身高风险,外接 DLL 若含缺陷会引发终端异常,上实盘前务必在策略测试器隔离验证。

把 DLL 直接丢进 MQL5\Libraries 省掉拷贝

在 Visual Studio 2005/2008 里建 DLL,走 File -> New 调出 Win32 应用程序向导,项目类型选 Visual C++,模板用 Win32 Console Application,名字可自取如 MQL5DLLSamples。Location 手选根目录,顺手取消 Create directory for solution,别用默认路径,点 OK 进下一步。 设置页点 Next,最后一页应用程序类型勾 DLL,其余留空,Finish 结束。若不想让向导塞演示代码,Export symbols 保持不勾,出来的就是空项目骨架。 真正省事的做法在输出目录:把 Output Directory 直接指到 MT5 客户端的 ...\MQL5\Libraries。这样一来每次编译完 DLL 不需手动拷文件,终端能立刻调用,验证 dll 接口时少绕一步弯。外汇与贵金属 EA 调外部库本身不影响行情风险,但 DLL 出错会让策略中断,需先在模拟盘跑通。

◍ 导出函数怎么写才不会被运行时拒掉

在 stdafx.h 末尾加一个 _DLLAPI 宏,能把导出函数的声明一次性打包好,后面写 C++ 端函数时直接前缀即可,少敲很多重复修饰符。 MQL5 调 DLL 只认 stdcall 和 cdecl 两种调用约定。两者清栈方式不同,但 MQL5 运行时靠一层包装程序把两种都接住了,所以安全上不用你操心。 编译器默认走 __cdecl,但导出函数建议显式写 __stdcall。编译后 stdcall 在导出表里会变成 _fnCalculateSpeed@8——下划线加函数名,@后面跟通过栈传的字节数(本例两个参数 int+double 共 8 字节)。这个修饰让调用方明确知道栈上该放多少字节,类型虽不管,数量错不了。 如果导入说明里的参数块大小写错,函数根本不会被调用,日志只丢一句:Cannot find 'fnCrashTestParametersStdCall' in 'MQL5DLLSamples.dll。遇到这句就去比对 DLL 源码和 MQL5 原型里的参数个数和宽度。 用 __cdecl 定义的函数不走修饰,导出名就是裸的 fnCalculateSpeed,无 @8 后缀。老项目为了兼容未含全名的导出表会这么干,但新写建议 stdcall 明确化。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------+
class=class="str">"cmt">//|                                                 MQL5 DLL Samples |
class=class="str">"cmt">//|           Copyright class="num">2001-class="num">2010, MetaQuotes Software Corp.          |
class=class="str">"cmt">//|                 https://www.metaquotes.net                      |
class=class="str">"cmt">//+------------------------------------------------------------------+
class="macro">#pragma once
class="macro">#include "targetver.h"
class="macro">#define WIN32_LEAN_AND_MEAN     class=class="str">"cmt">// 从Windows头中排除极少使用的
class="macro">#include <windows.h>
class=class="str">"cmt">//---
class="macro">#define _DLLAPI class="kw">extern "C" __declspec(dllexport)
class=class="str">"cmt">//+------------------------------------------------------------------+
_DLLAPI class="type">int __stdcall fnCalculateSpeed(class="type">int &res1,class="type">class="kw">double &res2)
  {
   class="kw">return(class="num">0);
 
}
class="macro">#class="kw">import "MQL5DLLSamples.dll"
class="type">int  fnCalculateSpeed(class="type">int &res1,class="type">class="kw">double &res2);
class="macro">#class="kw">import
class=class="str">"cmt">//--- 调用
  speed=fnCalculateSpeed(res_int,res_double);
_DLLAPI class="type">int fnCalculateSpeed(class="type">int &res1,class="type">class="kw">double &res2)
  {
   class="kw">return(class="num">0);
 
}

「DLL 参数传递里那些暗坑」

MQL5 调 DLL 时,简单变量可以用值传,也可以加 & 走引用。上面那段 fnCalculateSpeed 在 GBPUSD M1 上跑完返回 16 毫秒,int 结果是 -752584127、double 是 17247836076609——纯循环累加,没做溢出保护,int 已经负了,这提醒你引用回写前得自己管好边界。 数组不能像普通 MQL5 内部函数那样连维度带数据一起塞过去,它直接引用数据缓冲区,维度和大小得单独传。fnFillArray 那次调用,ArrayResize(arr,10) 后填出来是「0 1 2 … 9」,证明 arr_size 参数少传一个就会写越界崩 DLL。 字符串是对象,初学者最容易栽在副本上。fnReplaceString 第一次传 "text" 进去,线没变——系统自动建了副本在 DLL 里改完就扔,原串不动。要真改原串,导入声明里给参数加 & 变成引用,才能拿到正确结果。 外汇和贵金属 EA 接 DLL 属于高风险操作,参数错传可能导致终端异常,先在策略测试器里用小周期验证再上实盘。

MQL5 / C++
_DLLAPI class="type">int __stdcall fnCalculateSpeed(class="type">int &res1,class="type">class="kw">double &res2)
  {
   class="type">int    res_int=class="num">0;
   class="type">class="kw">double res_double=class="num">0.0;
   class="type">int    start=GetTickCount();
class=class="str">"cmt">//--- 简单的算数运算
   for(class="type">int i=class="num">0;i<=class="num">10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;

}
class=class="str">"cmt">//--- 设置计算结果
   res1=res_int;
   res2=res_double;
class=class="str">"cmt">//--- 返回计算时间 
   class="kw">return(GetTickCount()-start);
  }
class="macro">#class="kw">import "MQL5DLLSamples.dll"
class="type">int  fnCalculateSpeed(class="type">int &res1,class="type">class="kw">double &res2);
class="macro">#class="kw">import
class=class="str">"cmt">//--- 调用函数进行计算
   class="type">int    speed=class="num">0;
   class="type">int    res_int=class="num">0;
   class="type">class="kw">double res_double=class="num">0.0;
   speed=fnCalculateSpeed(res_int,res_double);
   Print("Time ",speed," msec, class="type">int: ",res_int," class="type">class="kw">double: ",res_double);
MQL5DLL Test(GBPUSD,M1) class="num">19:class="num">56:class="num">42 Time  class="num">16  msec, class="type">int:  -class="num">752584127  class="type">class="kw">double:  class="num">17247836076609
_DLLAPI class="type">void __stdcall fnFillArray(class="type">int *arr,const class="type">int arr_size)
  {
class=class="str">"cmt">//--- 检查输入参数
   if(arr==NULL || arr_size<class="num">1) class="kw">return;
class=class="str">"cmt">//--- 用值填充数组
   for(class="type">int i=class="num">0;i<arr_size;i++) arr[i]=i;
  }
class="macro">#class="kw">import "MQL5DLLSamples.dll"
class="type">void fnFillArray(class="type">int &arr[],class="type">int arr_size);
class="macro">#class="kw">import
class=class="str">"cmt">//--- 填充数组
   class="type">int    arr[];
   class="type">class="kw">string result="Array: ";
   ArrayResize(arr,class="num">10);

   fnFillArray(arr,ArraySize(arr));
   for(class="type">int i=class="num">0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" ";
   Print(result);
MQL5DLL Test(GBPUSD,M1) class="num">20:class="num">31:class="num">12 Array: class="num">0 class="num">1 class="num">2 class="num">3 class="num">4 class="num">5 class="num">6 class="num">7 class="num">8 class="num">9
_DLLAPI class="type">void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
  {
   wchar_t *cp;
class=class="str">"cmt">//--- 参数检查
   if(text==NULL || from==NULL || to==NULL) class="kw">return;
   if(wcslen(from)!=wcslen(to))            class="kw">return;
class=class="str">"cmt">//--- 搜索字串
   if((cp=wcsstr(text,from))==NULL)            class="kw">return;
class=class="str">"cmt">//--- 替换
   memcpy(cp,to,wcslen(to)*class="kw">sizeof(wchar_t));
  }
class="macro">#class="kw">import "MQL5DLLSamples.dll"

常见问题

把编译好的 DLL 放进 MQL5\Libraries 目录,EA 里用 #import 指向文件名即可,省去手动拷贝。
用 extern "C" 和 __declspec(dllexport) 导出,且调用约定统一用 __stdcall,否则 MT 端会拒掉。
小布可读取你的调用日志,提示字符串指针、数组引用等易错点,并给出改参建议。
多半是没声明数组长度或用了错误指针类型,应在导入侧显式传 size,并用 double& 而非 double*。
直接丢进 MQL5\Libraries,重启终端后 EA 就能按文件名 import,免重复拷贝。