如何交换数据:10 分钟为 MQL5 创建 DLL·进阶篇
「DLL 里改字符串要靠引用传参」
在 MQL5 里调用外部 DLL 做字符串替换,最常见的坑是函数签名没加引用符,导致原字符串纹丝不动。下面这段声明少了 &,运行时 Print 出来的还是 fox:
MQL5DLL Test (GBPUSD,M1) 19:56:42 的日志显示,Replace: A quick brown fox jumps over the lazy dog——说明 text 没有被 DLL 内部改写。GBPUSD 的 M1 回测环境只是个载体,重点在参数传递机制。
把声明改成 void fnReplaceString(string &text,string from,string to); 之后,同一段调用逻辑在 19:58:31 的输出变成了 cat。差异仅在一个 &,却决定了字符串能否被就地修改。
开 MT5 跑这段时,先确认 DLL 导出函数本身接受的是指针或引用类型;若签名不匹配,日志不会报错但结果 silently 失效。外汇与贵金属品种上做这类字符串预处理属低风险操作,但 DLL 加载本身涉及第三方代码执行,需自行评估来源安全性。
class="macro">#class="kw">import "MQL5DLLSamples.dll" class="type">void fnReplaceString(class="type">class="kw">string text,class="type">class="kw">string from,class="type">class="kw">string to); class="macro">#class="kw">import class=class="str">"cmt">//--- 改变字符串 class="type">class="kw">string text="A quick brown fox jumps over the lazy dog"; fnReplaceString(text,"fox","cat"); Print("Replace: ",text); MQL5DLL Test(GBPUSD,M1) class="num">19:class="num">56:class="num">42 Replace: A quick brown fox jumps over the lazy dog class="macro">#class="kw">import "MQL5DLLSamples.dll" class="type">void fnReplaceString(class="type">class="kw">string &text,class="type">class="kw">string from,class="type">class="kw">string to); class="macro">#class="kw">import MQL5DLL Test(GBPUSD,M1) class="num">19:class="num">58:class="num">31 Replace: A quick brown cat jumps over the lazy dog
◍ DLL 调用里的异常兜底
MT5 在调用外部 DLL 函数时,默认套了一层未处理异常保护。内存越界、除零这类标准错误不会直接把终端干崩,而是被捕获后写进日志,调用方继续跑后面的逻辑。
想验证这套机制,可以在 DLL 侧故意往零地址写数据。下面这段就是典型的 crash 测试:DLL 里收到 NULL 指针后执行 *arr=0,客户端用 fnCrashTest(NULL) 去调。
实际跑起来,终端日志会留下 Access violation write to 0x00000000,但 Print("You won't see this text!") 之后的流程不会被打断——前提是异常发生在被包装的 DLL 调用边界内。外汇与贵金属交易外接 DLL 本身属高风险操作,实盘前务必在模拟环境复现。
_DLLAPI class="type">void __stdcall fnCrashTest(class="type">int *arr) { class=class="str">"cmt">//--- 等待接收一个零值引用来调用异常处理 *arr=class="num">0; } class="macro">#class="kw">import "MQL5DLLSamples.dll" class="type">void fnCrashTest(class="type">int arr); class="macro">#class="kw">import class=class="str">"cmt">//--- 调用crash(执行环境将抓住异常并防止客户端崩溃) fnCrashTest(NULL); Print("You won&class="macro">#x27;t see this text!"); class=class="str">"cmt">//--- MQL5DLL Test(GBPUSD,M1) class="num">20:class="num">31:class="num">12 Access violation write to 0x00000000
DLL 调用的包装开销到底多大
MQL5 里每发起一次 DLL 函数调用,运行环境都会先塞进一层特殊包装程序。它干四件事:把底层代码藏起来、换掉调用栈、兼容 stdcall 和 cdecl 两种协议、在调用内部抓异常。 这套机制看着繁琐,但实际测下来,单次调用的额外耗时在微秒级,对 EA 的 tick 级逻辑几乎无感。也就是说,包装带来的延迟不会成为策略瓶颈,真正拖慢速度的往往是 DLL 内部算法本身而非这道门槛。 做高频或每 tick 多次跨 DLL 取价的贵金属策略时,仍建议本地打点:用 GetTickCount 绕一下看真实往返。外汇和贵金属杠杆高、滑点凶,任何跨边界调用都要先确认极端行情下的稳定性。
「把示例跑起来的工程落点」
前面拆过的 DLL 导出函数都收在 MQL5DLLSamples.cpp 里,配套的 MQL5 调用脚本是 MQL5DLL Test.mq5。官方附带的压缩包 mql5dllsamples.zip 约 5.27 KB,mq5 脚本 3.35 KB,直接用 VS2008 工程编译 DLL、MT5 加载脚本就能复现调用链路。
有读者在 2018 年反馈过相同报错:MT5 加载 DLL 后崩溃,把编译目标改成 64 位就解决了。这说明 MT5 终端位数和 DLL 位数必须对齐,否则 fnCalculateSpeed 这类 __stdcall 导出函数调用时内存模型会错配。
评论区还有人用 Visual Studio 2017 Community 编译运行完美,可见这套样例对新版 MSVC 也兼容。外汇与贵金属交易本身高风险,外接 DLL 更会放大平台稳定性风险,上真仓前务必在模拟环境验证。
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">#include "stdafx.h" class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 传输并接收简单的变量 | class=class="str">"cmt">//+------------------------------------------------------------------+ _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=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| 用值填充数组 | class=class="str">"cmt">//+------------------------------------------------------------------+ _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">//--- 用值填充数组
◍ DLL 侧字符串替换与崩溃探针的实现细节
在 MQL5 调用外部 DLL 时,字符串替换函数要求被替换子串与目标子串长度严格一致,否则直接 return 不做任何操作。下面这段 C 侧代码通过 wcsstr 定位宽字符子串,再用 memcpy 按 wchar_t 尺寸覆盖,属于原位改写,不会重新分配内存。 _DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to) { wchar_t *cp;
| if(text==NULL | from==NULL | to==NULL) return; |
|---|
if(wcslen(from)!=wcslen(to)) return; if((cp=wcsstr(text,from))==NULL) return; memcpy(cp,to,wcslen(to)*sizeof(wchar_t)); } 崩溃检测函数 fnCrashTest 接收一个 int 引用,直接对传入地址写 0。若 MQL5 侧传的是空引用或已释放句柄,这一行会触发访问异常,可用于验证 DLL 调用边界保护是否生效。 _DLLAPI void __stdcall fnCrashTest(int *arr) { *arr=0; } MQL5 脚本里用 #import "MQL5DLLSamples.dll" 声明上述函数原型,注意 fnReplaceString 在 MQL5 侧签名是 (string,string,string),而 DLL 内是宽字符指针,长度不等时静默失败。开 MT5 建个脚本引这个 dll,故意传 from="AB" to="C",Print 输出原文本不变,就能确认这条限制。外汇与贵金属品种上跑这类外部调用属高风险,DLL 异常可能拖垮整个终端。
_DLLAPI class="type">void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to) { wchar_t *cp; if(text==NULL || from==NULL || to==NULL) class="kw">return; if(wcslen(from)!=wcslen(to)) class="kw">return; if((cp=wcsstr(text,from))==NULL) class="kw">return; memcpy(cp,to,wcslen(to)*class="kw">sizeof(wchar_t)); } _DLLAPI class="type">void __stdcall fnCrashTest(class="type">int *arr) { *arr=class="num">0; }
一点提醒
上面这段调用链把数组重设、字符串替换和崩溃检测串在一起:ArrayResize 把 arr 拉到 10 个元素,fnFillArray 按 ArraySize 填值,循环里用 IntegerToString 拼出结果并打印;text 里的 fox 被 fnReplaceString 换成 cat 后输出 Replace: A quick brown cat jumps over the lazy dog。 fnCrashTest(NULL) 故意往空指针上撞,执行环境会捕获异常阻断客户端崩掉,所以后面那句 You won't see this text! 永远不会进日志——这点在 MT5 里跑一遍就能确认。 外汇和贵金属波动剧烈、杠杆高风险大,这类异常处理示例仅用于理解 MQL5 运行时边界,别直接当实盘风控用。
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); class=class="str">"cmt">//--- 修改字符串 class="type">class="kw">string text="A quick brown fox jumps over the lazy dog"; fnReplaceString(text,"fox","cat"); Print("Replace: ",text); class=class="str">"cmt">//--- 最后调用崩溃检测函数 class=class="str">"cmt">//--- (执行环境将抓住异常并防止客户端崩溃) fnCrashTest(NULL); Print("You won&class="macro">#x27;t see this text!"); class=class="str">"cmt">//---