如何交换数据:10 分钟为 MQL5 创建 DLL(基础篇)
「十分钟搭一个能被 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 明确化。
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 属于高风险操作,参数错传可能导致终端异常,先在策略测试器里用小周期验证再上实盘。
_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"