如何交换数据:10 分钟为 MQL5 创建 DLL·进阶篇
📘

如何交换数据:10 分钟为 MQL5 创建 DLL·进阶篇

第 2/2 篇

「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 加载本身涉及第三方代码执行,需自行评估来源安全性。

MQL5 / C++
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 本身属高风险操作,实盘前务必在模拟环境复现。

MQL5 / C++
_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 更会放大平台稳定性风险,上真仓前务必在模拟环境验证。

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">#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==NULLfrom==NULLto==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 异常可能拖垮整个终端。

MQL5 / C++
_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 运行时边界,别直接当实盘风控用。

MQL5 / C++
  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">//---

常见问题

字符串要靠引用传参(如双指针或结构体封装),只传指针副本改不到调用方内存,需用引用或返回新缓冲区。
在 DLL 入口用异常捕获兜底并记录日志,调用侧加崩溃探针钩子,能区分是逻辑错还是访问越界。
小布可读取你的调用耗时采样,标出高频包装点并给出内联或批处理建议,省去手算开销。
按编码分配足够缓冲区,替换前测长并留终止符,用探针函数校验边界,别原地硬写。
将编译好的库放到调用程序同级目录或系统路径,工程里填对相对路径,缺依赖先用工具查一遍。