使用命名管道与 MetaTrader 5 进行无 DLL 通信·进阶篇
「管道连上之后怎么互发数据」
客户端在管道连通后,第一件事是把自身标识推过去:文件名拼接 MQL5 编译版本号,以 ANSI 文本形式写出。由于底层文件是按 ANSI 模式开的,Unicode 串会被自动转码,不需要你手动处理宽字符。 服务端回传的内容很固定——字符串 "Hello from pipe server" 加一个整数 1234567890。客户端读完这两项,再把自己的 "Test string" 和同一个整数发回去,一次往返就算结束。 下面这段是客户端实际交换过程的代码,注意 WriteString / ReadString / ReadInteger / WriteInteger 的成对调用顺序,漏掉任一步都会让管道卡在等待态。 别把回传整数当校验依据 服务端给的 1234567890 只是演示值,真实场景里你应当用随机或递增令牌,否则多实例并发时容易串管道。
class=class="str">"cmt">//--- send welcome message if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__))) { Print("Client: sending welcome message failed"); class="kw">return; } class=class="str">"cmt">//--- read data from server class="type">class="kw">string str; class="type">int value=class="num">0; if(!ExtPipe.ReadString(str)) { Print("Client: reading class="type">class="kw">string failed"); class="kw">return; } Print("Server: ",str," received"); if(!ExtPipe.ReadInteger(value)) { Print("Client: reading integer failed"); class="kw">return; } Print("Server: ",value," received"); class=class="str">"cmt">//--- send data to server if(!ExtPipe.WriteString("Test class="type">class="kw">string")) { Print("Client: sending class="type">class="kw">string failed"); class="kw">return; } if(!ExtPipe.WriteInteger(value)) { Print("Client: sending integer failed"); class="kw">return; }
命名管道实测吞吐:本地与局域网差着两个数量级
把 1 GB 双精度数组(每次发 8 MB 块,循环 128 次)从 C++ 服务端推到 MT5 客户端,校验头尾哨兵元素并回发确认信号,是验证管道零丢包的最小闭环。本机跑 PipeServer.exe 配 PipeClient.mq5,实测速率逼近 3 GB/s,意味着本地进程间给 MQL5 喂任意体量数据基本无瓶颈。 换到普通千兆局域网,同样 1 GB 传输掉到 63 MB/s,刚好卡在千兆带宽理论上限的 63% 左右。外汇与贵金属交易走这种通道做跨进程信号分发时,需留意局域网抖动可能拉低实时性,属高风险链路。 下面这段是服务端基准核心:先塞满 8 MB 缓冲,每块写首尾签名防串帧,发完等客户端回 12345 确认才统计。 客户端侧对称:ArrayResize 出 1M double 数组,循环 128 次 ReadDoubleArray 收满即校验,缺失立即断连。没最终确认这一步,任一方早退都会静默丢尾包。
class=class="str">"cmt">//--- benchmark class="type">class="kw">double volume=class="num">0.0; class="type">class="kw">double *buffer=new class="type">class="kw">double[class="num">1024*class="num">1024]; class=class="str">"cmt">// class="num">8 Mb wprintf(L"Server: start benchmark\n"); if(buffer) { class=class="str">"cmt">//--- fill the buffer for(class="type">size_t j=class="num">0;j<class="num">1024*class="num">1024;j++) buffer[j]=j; class=class="str">"cmt">//--- send class="num">8 Mb * class="num">128 = class="num">1024 Mb to client DWORD ticks=GetTickCount(); for(class="type">size_t i=class="num">0;i<class="num">128;i++) { class=class="str">"cmt">//--- setup guard signatures buffer[class="num">0]=i; buffer[class="num">1024*class="num">1024-class="num">1]=i+class="num">1024*class="num">1024-class="num">1; class=class="str">"cmt">//--- if(!manager.Send(buffer,class="kw">sizeof(class="type">class="kw">double)*class="num">1024*class="num">1024)) { wprintf(L"Server: benchmark failed, %d\n",GetLastError()); class="kw">break; } volume+=class="kw">sizeof(class="type">class="kw">double)*class="num">1024*class="num">1024; wprintf(L"."); } wprintf(L"\n"); class=class="str">"cmt">//--- read confirmation if(!manager.Read(&value,class="kw">sizeof(value)) || value!=class="num">12345) wprintf(L"Server: benchmark confirmation failed\n"); class=class="str">"cmt">//--- show statistics ticks=GetTickCount()-ticks; if(ticks>class="num">0) wprintf(L"Server: %.0lf Mb sent at %.0lf Mb per second\n",volume/class="num">1024/class="num">1024,volume/class="num">1024/ticks); class=class="str">"cmt">//--- class="kw">delete[] buffer; } class=class="str">"cmt">//--- benchmark class="type">class="kw">double buffer[]; class="type">class="kw">double volume=class="num">0.0; if(ArrayResize(buffer,class="num">1024*class="num">1024,class="num">0)==class="num">1024*class="num">1024) { class="type">uint ticks=GetTickCount(); class=class="str">"cmt">//--- read class="num">8 Mb * class="num">128 = class="num">1024 Mb from server for(class="type">int i=class="num">0;i<class="num">128;i++) { class="type">uint items=ExtPipe.ReadDoubleArray(buffer); if(items!=class="num">1024*class="num">1024) {
◍ 命名管道压测里的两端对账
这段客户端逻辑跑在 MQL5 命名管道 benchmark 的收尾段,核心不是发数据,而是校验服务端扔过来的 1 GB 双精度流有没有错位。它先比对 buffer 首尾两个索引:buffer[0] 必须等于本轮序号 i,buffer[1024*1024-1] 必须等于 i+1048575,任一不符就 Print 报错并 break,避免脏数据继续累计。 校验通过后 volume 按 sizeof(double)*1024*1024 累加,也就是每轮吃满 1 MB 双精度数组。循环结束前客户端回写 WriteInteger(12345) 做确认,服务端收到才认账。 真实日志里同一套代码两次跑分差异极大:第一次 Server 与 Client 都报 1024 Mb received at 2921 Mb per second,第二次掉到 63 Mb per second。这种波动大概率来自本机其他进程占用管道带宽或磁盘缓冲,外汇贵金属 EA 用管道做跨终端行情转发时,得把峰值和谷值都记下来,别只信第一次亮眼数字。 收尾用 GetTickCount 算耗时,ticks>0 才 printf 出 Mb/s,最后 ArrayFree(buffer) 释放。开 MT5 把这段塞进你自己的 PipeClient 里,连本地服务端跑两遍,就能直接看到你机器上的管道吞吐区间。
Print("Client: benchmark failed after ",volume/class="num">1024," Kb, ",items," items received"); class="kw">break; } class=class="str">"cmt">//--- check the data if(buffer[class="num">0]!=i || buffer[class="num">1024*class="num">1024-class="num">1]!=i+class="num">1024*class="num">1024-class="num">1) { Print("Client: benchmark invalid content"); class="kw">break; } class=class="str">"cmt">//--- volume+=class="kw">sizeof(class="type">class="kw">double)*class="num">1024*class="num">1024; } class=class="str">"cmt">//--- send confirmation value=class="num">12345; if(!ExtPipe.WriteInteger(value)) Print("Client: benchmark confirmation failed "); class=class="str">"cmt">//--- show statistics ticks=GetTickCount()-ticks; if(ticks>class="num">0) printf("Client: %.0lf Mb received at %.0lf Mb per second\n",volume/class="num">1024/class="num">1024,volume/class="num">1024/ticks); class=class="str">"cmt">//--- ArrayFree(buffer); }
「一点提醒」
MT5 的沙箱机制会拦住 MQL5 程序在外部直接跑,这层保护让不受信 EA 难以越权,但命名管道能绕开限制做第三方联通与外部管控,且过程不破坏平台隔离。 附件里 pipeclient.mq5 仅 3.15 KB、pipeserver.zip 为 43.59 KB,拿去在 MT5 建两个终端实测最直观:一方起服务端管道,另一方用客户端连,能互通就说明外部调度已通。 外汇与贵金属波动剧烈、杠杆风险高,外挂管道虽方便,权限配错(如服务端管理员、MT 普通权限)就会报 INVALID_HANDLE,实测前先统一 UAC 层级再跑。