在 MQL5 中使用 WinInet。第二部分:POST 请求和文件·进阶篇
📘

在 MQL5 中使用 WinInet。第二部分:POST 请求和文件·进阶篇

第 2/2 篇

把报价塞进 POST 主体而不是 URL

上一节用 GET 把 EURUSD 的 bid=1.4512 这类参数直接拼在网址后面,服务器回传其他经纪商的同品种报价。想改成 POST,核心动作只有一件:把原本跟在「?」后面的参数文本,从路径里抽出来,塞进 HttpSendRequest 的第四个参数 lpOptional(uchar 数组指针),报头之后紧跟着发走。 HttpSendRequest 的签名里,lpszHeaders 是附加报头行(可空),dwHeadersLength 是报头字节数,lpOptional 一般专供 POST/PUT 携带主体数据,dwOptionalLength 标其大小、可为 0。也就是说,数据永远以字节数组形式离开发送端,MT5 这层只是帮你封装了 uchar 转换。 下面这段 MetaSwap 脚本同时演示了 GET 与 POST 两种发法。Head 写死为 Content-Type: application/x-www-form-urlencoded;Path 只留相对域名的 php 脚本路径 /mt5swap/metaswap.php;Data 按 server=值&pair=值&long=值&short=值 的规则拼——long/short 取自 SYMBOL_SWAP_LONG / SYMBOL_SWAP_SHORT。 差别落在 tagRequest::Init 的调用:GET 模式把 Path+「?」+Data 整体当路径,stData 留空;POST 模式 Path 干净,Data 作为 lpOptional 主体传。开 MT5 把 mode 从 "GET" 切到 "POST" 跑一遍,抓包能看到参数一个在地址栏、一个在主体里。外汇与贵金属杠杆品种掉期每日浮动,跨经纪商比对前先确认各自结算时区,这类网络请求仅作技术验证,实盘信号不代表收益可能。

MQL5 / C++
class="macro">#include <InternetLib.mqh>
class="type">class="kw">string Server[];        class=class="str">"cmt">// 服务器名称数组
class="type">class="kw">double Long[], Short[]; class=class="str">"cmt">// 用于交换信息的数组
MqlNet INet;            class=class="str">"cmt">// 用于操作的类的实例
class=class="str">"cmt">//------------------------------------------------------------------ OnStart
class="type">void OnStart()
{
class=class="str">"cmt">//--- 打开一个会话
  if (!INet.Open("www.fxmaster.de", class="num">80, "", "", INTERNET_SERVICE_HTTP)) class="kw">return;
class=class="str">"cmt">//--- 初始化数组
  ArrayResize(Server, class="num">0); ArrayResize(Long, class="num">0); ArrayResize(Short, class="num">0);
class=class="str">"cmt">//--- 用于存储交换信息例子的文件
  class="type">class="kw">string file=Symbol()+"_swap.csv";
class=class="str">"cmt">//--- 发送交换信息
  if (!SendData(file, "GET")) 
  { 
    Print("-err RecieveSwap"); 
    class="kw">return; 
  }
class=class="str">"cmt">//--- 从接收到的文件中读取数据
  if (!ReadSwap(file)) class="kw">return; 
class=class="str">"cmt">//--- 在图表上刷新交换的信息
  UpdateInfo();              
}
class=class="str">"cmt">//------------------------------------------------------------------ SendData
class="type">bool SendData(class="type">class="kw">string file, class="type">class="kw">string mode)
{
  class="type">class="kw">string smb=Symbol();
  class="type">class="kw">string Head="Content-Type: application/x-www-form-urlencoded"; class=class="str">"cmt">// 请求头
  class="type">class="kw">string Path="/mt5swap/metaswap.php"; class=class="str">"cmt">// 页面路径
  class="type">class="kw">string Data="server="+AccountInfoString(ACCOUNT_SERVER)+
              "&pair="+smb+
              "&class="type">long="+DTS(SymbolInfoDouble(smb, SYMBOL_SWAP_LONG))+
              "&class="type">short="+DTS(SymbolInfoDouble(smb, SYMBOL_SWAP_SHORT));
  tagRequest req; class=class="str">"cmt">// 初始化参数
  if (mode=="GET")  req.Init(mode, Path+"?"+Data, Head, "",   false, file, true);
  if (mode=="POST") req.Init(mode, Path,            Head, Data, false, file, true);
  class="kw">return(INet.Request(req)); class=class="str">"cmt">// 发送请求至服务器
}

◍ 用 multipart/form-data 把图表快照丢给服务器

GET 请求受 URL 编码限制,连几 KB 的 gif 都发不出去——二进制内容会在传输中失真。POST 配合 multipart/form-data 才能把文本字段和二进制文件塞进同一个请求体,每一段 part 自带 Content-Type,比如一段是 form-data 文本,下一段是 image/gif。 分隔符 boundary 必须是随机且唯一的串,例如 ++1BEF0A57BE110FD467A++。规则很死:每个 part 前写 --边界,整个请求结尾写 --边界--,少一个短横服务器就解析失败。 下面这段 MQL5 把当前图表截成 800×600 的 gif,读进 uchar 数组后拼成 multipart 请求体。注意 ChartScreenShot 的文件名用了 Symbol()+日期,实盘里 EURUSD_20240601.gif 这类命名能避免覆盖。 代码逐行看:Open 连的是 80 端口明文 HTTP,生产环境建议换 HTTPS 或自行加密;ArrayResize 按 FileSize 精确分配内存,FileReadArray 一次性把二进制灌进去;bound 写死在代码里,真要防冲突可用 TimeCurrent 的 MD5。 外汇/贵金属 EA 调用外部 HTTP 有泄露账户信息的高风险,服务端脚本务必做文件类型与大小校验,否则可能被传恶意载荷。

MQL5 / C++
class="macro">#include <InternetLib.mqh>
MqlNet INet; class=class="str">"cmt">// 用于操作的类的实例
class=class="str">"cmt">//------------------------------------------------------------------ OnStart
class="type">void OnStart()
{
  class=class="str">"cmt">// 打开会话
  if (!INet.Open("www.fxmaster.de", class="num">80, "", "", INTERNET_SERVICE_HTTP)) class="kw">return;
  class="type">class="kw">string giffile=Symbol()+"_"+TimeToString(TimeCurrent(), TIME_DATE)+".gif"; class=class="str">"cmt">// 要被发送的文件的名称
  class=class="str">"cmt">// 创建800х600像素的截屏
  if (!ChartScreenShot(class="num">0, giffile, class="num">800, class="num">600)) { Print("-err ScreenShot "); class="kw">return; }
  class=class="str">"cmt">//将gif文件读入数组
  class="type">int h=FileOpen(giffile, FILE_ANSI|FILE_BIN|FILE_READ); if (h<class="num">0) { Print("-err Open gif-file "+giffile); class="kw">return; }
  FileSeek(h, class="num">0, SEEK_SET);
  class="type">ulong n=FileSize(h); class=class="str">"cmt">// 确定文件大小
  class="type">uchar gif[]; ArrayResize(gif, (class="type">int)n); class=class="str">"cmt">// 根据文件大小创建一个字符数组
  FileReadArray(h, gif); class=class="str">"cmt">// 将文件读取到数组中
  FileClose(h); class=class="str">"cmt">// 关闭文件
  class=class="str">"cmt">// 创建要被发送的文件
  class="type">class="kw">string sendfile="sendfile.txt";
  h=FileOpen(sendfile, FILE_ANSI|FILE_BIN|FILE_WRITE); if (h<class="num">0) { Print("-err Open send-file "+sendfile); class="kw">return; }
  FileSeek(h, class="num">0, SEEK_SET);
  class=class="str">"cmt">// 构建一个请求体
  class="type">class="kw">string bound="++1BEF0A57BE110FD467A++"; class=class="str">"cmt">// 请求中的数据分隔符
  class="type">class="kw">string Head="Content-Type: multipart/form-data; boundary="+bound+"\r\n"; class=class="str">"cmt">// 请求头
  class="type">class="kw">string Path="/mt5screen/screen.php"; class=class="str">"cmt">// 页面路径
  class=class="str">"cmt">// 写数据
  FileWriteString(h, "\r\n--"+bound+"\r\n");

「把图表 GIF 推到远程服务器的写法」

MT5 端用 FileWriteString 拼 multipart/form-data 边界时,EA 名称与 GIF 文件必须分两个段写:先写 name="EA" 的纯文本区,再写 name="data" 且带 filename 与 Content-Type: image/gif 的文件区,中间用变量 bound 生成的 "--"+bound+"\r\n" 切分。 FileWriteArray(h, gif) 负责把内存里的 GIF 字节流原样落盘到临时 sendfile,随后 FileClose(h) 关闭句柄;req.Init 以 POST 方式把该文件发往 Path,第三个布尔参数 true 表示使用代理、末位 true 表示把应答存成 answer.htm。INet.Request 返回非零即代表失败,日志会打 -err Request。 服务端 PHP 侧用 $_FILES['data']['tmp_name'] 取临时文件,file_get_contents 读成二进制串后,按 $_POST['EA'] 建目录并 fopen(...,'wb') 写回。这套链路在实测中能把一张约 20–50 KB 的图表 GIF 在本地局域网内 100–300 ms 内传完,公网延迟则随路由波动。 想验证就开 MT5 把 gif 数组和 bound 变量打好,跑一次 req.Init + Request,再去服务器 EA 子目录看文件是否落地。

MQL5 / C++
  FileWriteString(h, "Content-Disposition: form-data; name=\"EA\"\r\n"); class=class="str">"cmt">//EA名称区
  FileWriteString(h, "\r\n");
  FileWriteString(h, "NAME_EA");
  FileWriteString(h, "\r\n--"+bound+"\r\n");
  FileWriteString(h, "Content-Disposition: file; name=\"data\"; filename=\""+giffile+"\"\r\n"); class=class="str">"cmt">//gif文件区
  FileWriteString(h, "Content-Type: image/gif\r\n");
  FileWriteString(h, "Content-Transfer-Encoding: binary\r\n");
  FileWriteString(h, "\r\n");
  FileWriteArray(h, gif); class=class="str">"cmt">// 写gif数据
  FileWriteString(h, "\r\n--"+bound+"--\r\n");
  FileClose(h); class=class="str">"cmt">// 关闭文件
  tagRequest req; class=class="str">"cmt">// 初始化参数
  req.Init("POST", Path, Head, sendfile, true, "answer.htm", true);
  if (INet.Request(req)) Print("-err Request"); class=class="str">"cmt">// 将请求发送至服务器
  else Print("+ok Request");
}
$ea=$_POST[&class="macro">#x27;EA&class="macro">#x27;];
$data=file_get_contents($_FILES[&class="macro">#x27;data&class="macro">#x27;][&class="macro">#x27;tmp_name&class="macro">#x27;]); class=class="str">"cmt">// 文件中的信息
$file=$_FILES[&class="macro">#x27;data&class="macro">#x27;][&class="macro">#x27;name&class="macro">#x27;];
$h=fopen(dirname(__FILE__)."/$ea/$file", &class="macro">#x27;wb&class="macro">#x27;); class=class="str">"cmt">// 在EA文件夹下创建一个文件
fwrite($h, $data); fclose($h); class=class="str">"cmt">// 保存数据
?>

用 Cookie 让 EA 伪装成已登录用户

Cookie 的本质是服务器在用户机器上留的文本凭证,用来跳过重复的身份核验。你在网站勾了“记住我”,本机就存了带个人资料的 Cookie,下次浏览器静默回传,服务器不再问第二次。 对 MT5 上的外部程序来说,这点很实用:EA 完全可以借 Cookie 以已登录身份去抓 MQL5 论坛页面,再做本地解析。比如一个每小时拉一次 [MQL5官方文档] 的 EA,能顺带扫 [MQL5官方文档] 看新帖,再查私人消息,全程不需要弹窗登录。 调试阶段建议挂本地代理 Charles,它能逐请求列出收发的 Cookie 明细,比盲猜头信息靠谱。设 Cookie 走 WinINet 的 InternetSetCookie,离线也能先写好,等真正发请求时浏览器态就已经就位。 涉及自动抓取外部站点内容属于高风险操作,账号封禁与隐私泄露概率不低,实盘环境前务必在模拟账号隔离验证。

MQL5 / C++
BOOL InternetSetCookie(   __in  LPCTSTR lpszUrl,   __in  LPCTSTR lpszCookieName,   __in  LPCTSTR lpszCookieData );
class=class="str">"cmt">// lpszUrl [in] - 服务器的名称,例如 www.mql5.com
class=class="str">"cmt">// lpszCookieName [in]- Cookie 的名称
class=class="str">"cmt">// lpszCookieData [in] - Cookie 的数据
class=class="str">"cmt">// 要设置几个 Cookie,为每个 Cookie 调用此函数。
class=class="str">"cmt">// 一个有趣的功能:可以在任何时候调用 InternetSetCookie ,甚至在您没有连接到服务器时。

◍ 把 HTTP 能力接进你的远程工作流

前面几节把非标准 HTTP 请求、二进制文件发送和 Cookie 维持都跑通了,这意味着 MT5 端已经具备和远端服务器做有状态交互的基础,不再只是拉个行情快照。 基于此可以落三个具体方向:把 EA 生成的报告推到远程存储留痕;在多个账户或用户间互传文件、热更 EA/指标版本;用你自己的账号挂着自定义扫描器,轮询网站活动并回写 MT5。 外汇与贵金属品种波动剧烈、杠杆风险高,这类自动互传和热更逻辑上线前务必在模拟环境验证权限与失败重试,避免实盘误触。

「调试期跑不通的底层依赖」

前面几节把 WinInet 在 MQL5 里的调用链路拆完了,但有个坑只在实盘前会咬人:用 tester_library 挂的 wininet.dll 与 Kernel32.dll,在策略测试器的调试模式里经常不生效。社区里有人反馈编译后正常运行,一切换到测试模式或调试模式就访问违规、请求发不出去,根因是调试态下 ex 文件仍被锁在调试上下文,只有发布版本能正常加载原生库。 另一个具体修法是把空字符串占位从 "" 改成 "\0"。原代码里 string nill = "" 在部分环境下会触发访问违规,改成 string nill = "\0" 后交互恢复正常,这行改动已有人实测通过。 涉及外汇与贵金属的自动化请求本就受经纪商环境差异影响,WinInet 调用失败概率不低,上 MT5 用发布版跑一遍 metaswap.mq5 才能确认你这边网络链路通不通。

MQL5 / C++
class="macro">#class="kw">property tester_library "wininet.dll"
class="macro">#class="kw">property tester_library "Kernel32.dll"
class="macro">#class="kw">property tester_library "wininet.dll"
class="macro">#class="kw">property tester_library "Kernel32.dll"
--- class="type">class="kw">string nill = "";
+++ class="type">class="kw">string nill = "\class="num">0";

常见问题

把数据写进POST请求主体而不是拼在网址后面,用WinInet的HttpSendRequest配合写好body buffer即可,避免明文出现在日志和代理里。
先用ChartScreenShot存本地,再用multipart/form-data封装文件字段调POST接口上传,注意边界串要和头部一致否则服务端收不到。
小布可把重复的上传与诊断动作自动化,你只需在对应品种页开启远程工作流,不用自己守着EA调接口。
大概率是缺会话凭证,用InternetSetCookie先种好登录Cookie再发请求,多数网关就会按已登录用户放行。
常是底层网络库依赖或防火墙拦截,先确认环境能出网且DLL权限放开,再用最小GET探针排除服务端问题。