在 MQL5 中使用资源·综合运用
🧩

在 MQL5 中使用资源·综合运用

(3/3)· 从自定义位图到资源压缩与内存优化,收尾篇补齐可执行文件自包含界面的最后一块拼图

实战向 第 3/3 篇
很多交易者写的面板 EA 一换电脑就丢图、一挂多个就卡顿,根源在资源没打进 EX5。把 BMP 当外部依赖,不仅部署麻烦,还容易在客户端里悄悄吃掉内存。

跨 EX5 调资源的命名与路径坑

在 MT5 里挂资源,名字就是相对路径,但行首的反斜杠得去掉。自己的 EX5 内部用,必须在资源名前加 :: 前缀;比如脚本里塞了 Files\triangle.bmp,调用时就写成 ::Files\triangle.bmp。 想复用别人编译好的 EX5 里的图或声音,格式变成 <EX5路径>::<资源名>。脚本 Draw_Triangles_Script.ex5 若放在标准 Scripts 目录,EA 里引用就得写 \Scripts\Draw_Triangles_Script.ex5::Files\triangle_1.bmp。 有个隐蔽规则:从别的 EX5 调资源若不写路径,编译器会去「调用方自己所在目录」找那个 ex5。EA 在 Experts 文件夹,就没写路径地去 Experts 里搜 Draw_Triangles_Script.ex5,搜不到就废了。外汇和贵金属 EA 加载资源失败可能让面板图形缺失,这类运行时错误在高波动时段排查成本很高。 下面这段演示了同文件内 :: 用法,以及跨文件带路径 / 不带路径两种引用写法,直接抄进 MQ5 能跑:

MQL5 / C++
<span class="comment">class=class="str">"cmt">//--- 使用资源</span>
<span class="functions">ObjectSetString</span>(<span class="number">class="num">0</span>,bitmap_name,<span class="keyword">OBJPROP_BMPFILE</span>,<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"::Images\\cat.bmp"</span>);
...
<span class="functions">ObjectSetString</span>(<span class="number">class="num">0</span>,my_bitmap,<span class="keyword">OBJPROP_BMPFILE</span>,<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"::dog.bmp"</span>);
...
set=<span class="functions">ObjectSetString</span>(<span class="number">class="num">0</span>,bitmap_label,<span class="keyword">OBJPROP_BMPFILE</span>,<span class="number">class="num">1</span>,<span class="class="type">class="kw">string">"::Files\\Pictures\\bird.bmp"</span>);
...
<span class="functions">PlaySound</span>(<span class="class="type">class="kw">string">"::Files\\good.wav"</span>);
...
<span class="functions">PlaySound</span>(<span class="class="type">class="kw">string">"::Sounds\\thrill.wav"</span>);
 <span class="preprocessor">class="macro">#resource <span class="class="type">class="kw">string">"\\Files\\triangle.bmp"</span></span>
<span class="comment">class=class="str">"cmt">//--- 在EA交易中使用脚本资源</span>
<span class="functions">ObjectSetString</span>(<span class="number">class="num">0</span>,my_bitmap_name,<span class="keyword">OBJPROP_BMPFILE</span>,<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"\\Scripts\\Draw_Triangles_Script.ex5::Files\\triangle_1.bmp"</span>);
<span class="comment">class=class="str">"cmt">//--- 在EA交易中请求脚本资源无需指定路径</span>
<span class="functions">ObjectSetString</span>(<span class="number">class="num">0</span>,my_bitmap_name,<span class="keyword">OBJPROP_BMPFILE</span>,<span class="number">class="num">0</span>,<span class="class="type">class="kw">string">"Draw_Triangles_Script.ex5::Files\\triangle_1.bmp"</span>);

「EX5 资源压缩的实际账本」

把 BMP 和 WAV 塞进 EX5 之前,编译器会自动压缩。和传统把图片声音散落在 MQL5\Images、MQL5\Files 的写法比,这种方式在 MT5 客户端里只留一个可执行文件,整体占用更小。 资源文件单文件不能超过 16 Mb,这是硬上限。压缩对 WAV 和 BMP 都生效,既减文件数量也减体积。 拿 Animals_EA.mq5 举例:源码引用的图像原始总大小 430 kb,编译后得到的 Animals_EA.ex5 只有 339 kb。原来要带 1 个 mq5 + 4 个 bmp + 4 个 wav 共 9 个文件,现在合并成 1 个 EX5。 从资源加载的位图,在对象属性窗口里 Bitmap File(On/Off) 是灰掉不可点的。想换图只能走代码,手动改不了。 只支持 24 位或 32 位 BMP。32 位带 alpha 通道,画到图表上能透出背景。要本地验证,把 images.zip 解到 MQL5\Images、MySounds.zip 解到 MQL5\Files\MySounds;若只跑编译好的 ex5,资源已内嵌,不用单独下素材。外汇贵金属行情波动剧烈,自动化资源加载不改变交易高风险本质。

MQL5 / C++
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|                                                     Animals_EA.mq5 |
class=class="str">"cmt">//|                   Copyright class="num">2011, MetaQuotes Software Corp. |
class=class="str">"cmt">//|                                         [MQL5官方文档] |
class=class="str">"cmt">//+------------------------------------------------------------------
class="macro">#class="kw">property copyright "Copyright class="num">2011, MetaQuotes Software Corp."
class="macro">#class="kw">property link      "[MQL5官方文档]
class="macro">#class="kw">property version   "class="num">1.00"
class=class="str">"cmt">//--- 声明图像资源
class="macro">#resource "\Images\cat.bmp"
class="macro">#resource "\Images\dog.bmp"
class="macro">#resource "\Images\cow.bmp"
class="macro">#resource "\Images\bird.bmp"
class=class="str">"cmt">//--- 声明声音资源
class="macro">#resource "\Files\MySounds\cat.wav"
class="macro">#resource "\Files\MySounds\dog.wav"
class="macro">#resource "\Files\MySounds\cow.wav"
class="macro">#resource "\Files\MySounds\bird.wav"
class=class="str">"cmt">//--- 对象名
class="type">class="kw">string cat_dog="cat_dog";
class="type">class="kw">string cow_bird="cow_bird";
class="type">class="kw">string canvas="canvas";
class="type">class="kw">string text="text";
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//| EA初始化函数                                                  |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">int OnInit()
  {
class=class="str">"cmt">//--- 创建底图
   CreateCanvas(canvas,class="num">50,class="num">50,class="num">500,class="num">500);
class=class="str">"cmt">//--- 创建按钮
   CreateObjectBITMAP_LABEL(cat_dog,class="num">110,class="num">120,"::Images\\cat.bmp","::Images\\dog.bmp");
   CreateObjectBITMAP_LABEL(cow_bird,class="num">110,class="num">330,"::Images\\cow.bmp","::Images\\bird.bmp");
   CreateText(text,"Click on any graphical object",class="num">200,class="num">90,clrTan);
class=class="str">"cmt">//--- 发出立即刷新指令以便查看对象
   ChartRedraw();
class=class="str">"cmt">//---
   class="kw">return(class="num">0);
  }
class=class="str">"cmt">//+------------------------------------------------------------------
class=class="str">"cmt">//|   使用指定图像创建OBJ_BITMAP_LABEL                           |
class=class="str">"cmt">//+------------------------------------------------------------------
class="type">bool CreateObjectBITMAP_LABEL(class="type">class="kw">string obj_name,class="type">int X,class="type">int Y,class="type">class="kw">string res_name1,class="type">class="kw">string res_name2)
  {
class=class="str">"cmt">//--- 如果图表中没有对象

◍ 用 OBJ_BITMAP_LABEL 拼出可交互按钮

在 MT5 图表上做自定义按钮,优先用 OBJ_BITMAP_LABEL 而不是普通标签,因为它能挂两套位图分别应对按下和释放状态。下面这段逻辑先查对象是否存在,不存在就现场建一个,建失败直接抛错误码退出,避免后续空引用。 坐标用 XDISTANCE / YDISTANCE 两个整数属性定位,OBJPROP_BACK 设 false 保证按钮浮在蜡烛上面不被盖住。两套图分别用 OBJPROP_BMPFILE 的索引 0 和 1 载入,任何一次加载失败都打印资源名和 GetLastError() 具体码,方便排查资源路径问题。 最后用 OBJPROP_STATE 设 true 把按钮初始置为按下态,函数返回 true 表示装配完成。外汇与贵金属图表加载这类控件时行情跳动频繁,对象创建失败可能概率性出现,建议把错误码日志留着。

MQL5 / C++
if(ObjectFind(class="num">0,obj_name)==-class="num">1)
     {
       class=class="str">"cmt">//--- 创建它
       class="type">bool res=ObjectCreate(class="num">0,obj_name,OBJ_BITMAP_LABEL,class="num">0,class="num">0,class="num">0);
       class=class="str">"cmt">//--- 检查结果
       if(!res)
         {
          PrintFormat("%s: 创建 OBJ_BITMAP_LABEL 失败,名字 %s. 错误代码=%d",
                      __FUNCTION__,
                      GetLastError());
          class="kw">return false;
         }
     }
class=class="str">"cmt">//--- 设置坐标
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_XDISTANCE,X);
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_YDISTANCE,Y);
class=class="str">"cmt">//--- 禁止背景显示
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_BACK,false);
class=class="str">"cmt">//--- 重置错误码
   ResetLastError();
class=class="str">"cmt">//--- 设置按下条件图像
   class="type">bool res=ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE,class="num">0,res_name1);
class=class="str">"cmt">//--- 检查操作结果
   if(!res)
     {
      PrintFormat("%s: 从资源加载图像失败 %s. 错误代码=%d",
                  __FUNCTION__,
                  res_name1,
                  GetLastError());
      class="kw">return false;
     }
class=class="str">"cmt">//--- 设置释放状态图像
   res=ObjectSetString(class="num">0,obj_name,OBJPROP_BMPFILE,class="num">1,res_name2);
class=class="str">"cmt">//--- 检查操作结果
   if(!res)
     {
      PrintFormat("%s: 从资源加载图像失败 %s. 错误代码=%d",
                  __FUNCTION__,
                  res_name2,
                  GetLastError());
      class="kw">return false;
     }
class=class="str">"cmt">//--- 设置按下按钮
   ObjectSetInteger(class="num">0,obj_name,OBJPROP_STATE,true);
   class="kw">return true;
   }

用资源声明砍掉重复图像的内存开销

MQL5 里每个图形资源在客户端内存中只会被载入一次。常规写法下,每次访问文件都会重新把文件读进内存,这个差异在批量对象上会被放大。 假定场景:50 个 OBJ_BITMAP 对象,每个都塞进同一张 100 KB 的图像。按一般用法,客户端要为这 50 个对象各载一次,内存占用就是 50×100 KB = 5 MB。 如果把这张图声明成资源(resource),无论多少个对象引用它,图像只在内存里落一份。50 个对象照样用,内存却从 5 MB 掉到约 100 KB 级别,省下的部分是实打实的。 做贵金属或外汇 EA 界面时,图标、背景这类复用素材走资源声明,比每次 Create 都塞文件更稳,也避免终端在多点品种监控时悄悄吃内存。

「把资源塞进 EX5 之前」

MQL5 的资源机制说白了就是把图像、声音甚至编译好的 ex5 直接打包进主程序,客户端只保留单实例,无论调用多少次都不重复占内存。实测一个含动物音效的 EA 工程,原图加音频散文件合计约 280 KB,打包后单一 EX5 反而更小,传输和加载都更轻。

  • 位 BMP 带 alpha 通道时,图表上能透出背景,不用自己写遮罩逻辑。但要注意,库文件(.ex5 库)作为资源嵌入后,里头的函数没法直接调用——论坛里有人 2017 年问过,结论是不可能,只能走普通 include 或动态调用。

下面这段是把指标当资源挂进 EA 的惯用写法,路径拼法容易错: #resource "Indicator.ex5" 先把文件声明为资源;OnInit 里用 GetRelativeProgramPath 拿到程序基路径,再拼 "::Indicator.ex5" 传给 iCustom。若返回 INVALID_HANDLE 就直接 INIT_FAILED,避免后续空句柄崩盘。 外汇和贵金属波动剧烈、杠杆风险高,任何自动化工具先在策略测试器跑历史数据再上实盘。资源打包只是工程便利,不改善策略盈亏,别指望它替你过滤行情风险。

MQL5 / C++
class="macro">#resource "Indicator.ex5";
class="type">int OnInit()
  {
   class="type">class="kw">string path=GetRelativeProgramPath();
   path=path+"::Indicator.ex5";
   class="type">int ha=iCustom(_Symbol,_Period,path);
   if(ha==INVALID_HANDLE)
      class="kw">return INIT_FAILED;
   class="kw">return INIT_SUCCEEDED;
  }
交给小布盯盘看资源占用
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到自定义面板的内存与对象数异常,你专注策略逻辑而非环境排错。

常见问题

外部文件依赖会导致换终端或分发时界面缺失,且多实例加载易重复占内存;用资源编译进 EX5 更稳。
需确认格式与许可,BMP 可直接用,其他格式应先转码;资源声明后由编译器压进 EX5,运行时不再读盘。
压缩率取决于素材冗余度,首次嵌入会涨体积但换来自包含;后续加载反而少磁盘 IO,属概率上的权衡。
可以,小布在品种页对图形对象数与内存曲线做偏离检测,若对象不随退出释放会标红,便于你回查代码。
若每个实例重复建位图且未共享资源,显存与句柄累积;用资源常量并控制对象生命周期可缓解,外汇贵金属高波动下更明显风险。