利用 EX5 库来推动您的项目开发·进阶篇
(2/3)· 直接导出类在 MQL5 仍受限,用虚函数与多态把实施藏进 ex5 才是多人协作的正路
◍ 把类藏进 ex5:用虚函数拆出公开接口
MQL5 至今不支持把类直接 export 给别的 ex5 模块,想跟别人共享 CSpiro 这种绘图类又不暴露算法,只能玩拆类。思路是把一个类劈成两半:声明和虚函数留在公开头文件里,真实实现塞进带 #property library 的隐藏 mq5。 具体做法是拷贝一份 CSpiro 的后代叫 ISpiro(I 取自 interface),原类只留变量和 virtual 声明的空壳,ISpiro 才写具体逻辑。再导出一个工厂函数 iSpiro() 返回 new ISpiro,外部拿到的是基类指针,看不见底层。 注意所有要共享的函数必须带 virtual 前缀,否则多态不生效。还有 SetPoint 在 Graph 库和 CSpiro 里都声明了,隐藏文件里用 :: 加文件名限定,编译器才知道调哪一个。 下面这段是公开头加隐藏实现的精简例证,复制进 MT5 建两个文件就能编译出只暴露接口的库。外汇贵金属自动化用这类封装时记住:封装不等于低风险,策略逻辑黑盒化后回测偏差可能更大。
class=class="str">"cmt">//--- Spiro.mqh – class="kw">public file, the so called header file class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Class CSpiro | class=class="str">"cmt">//| Spirograph draw class | class=class="str">"cmt">//+------------------------------------------------------------------+ class CSpiro { class="kw">public: class=class="str">"cmt">//--- prefix of the chart objects class="type">class="kw">string m_sID; class=class="str">"cmt">//--- offset of the chart center class="type">int m_x0,m_y0; class=class="str">"cmt">//--- class="type">color of the line class="type">color m_clr; class=class="str">"cmt">//--- chart parameters class="type">class="kw">double m_R,m_r,m_D,m_dAlfa,m_nRotate; class="kw">public: class=class="str">"cmt">//--- constructor CSpiro() { }; class=class="str">"cmt">//--- destructor ~CSpiro() { }; class="kw">virtual class="type">void Init(class="type">int ax0,class="type">int ay0,class="type">color aclr,class="type">class="kw">string asID) { }; class="kw">virtual class="type">void SetData(class="type">class="kw">double aR,class="type">class="kw">double ar,class="type">class="kw">double aD,class="type">class="kw">double adAlpha,class="type">class="kw">double anRotate) { }; class="kw">public: class="kw">virtual class="type">void DrawSpiro() { }; class="kw">virtual class="type">void SetPoint(class="type">int x,class="type">int y) { }; }; class=class="str">"cmt">//--- ISpiro.mq5 – hidden implementation file class="macro">#include "Spiro.mqh" class=class="str">"cmt">//--- importing some functions class="macro">#class="kw">import "..\Experts\Spiro\Graph.ex5" class="type">void SetPoint(class="type">long achart,class="type">class="kw">string name,class="type">int awnd,class="type">int ax,class="type">int ay,class="type">color aclr); class="type">void ObjectsDeleteAll2(class="type">long achart=class="num">0,class="type">int wnd=-class="num">1,class="type">int type=-class="num">1,class="type">class="kw">string pref="",class="type">class="kw">string excl=""); class="macro">#class="kw">import CSpiro *iSpiro() class="kw">export { class="kw">return(new ISpiro); } class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Сlass ISpiro | class=class="str">"cmt">//| Spirograph draw class |
用类封装把螺线画进图表
把万花尺(Spirograph)曲线搬进 MT5,核心是先定义一个继承自 CSpiro 的子类 ISpiro,把坐标原点、颜色、对象前缀等状态收进成员,避免每次重绘都从头传参。 构造函数里把 m_x0、m_y0 置 0,析构时调 ObjectsDeleteAll(0,0,-1) 清掉当前图表全部对象;Init() 接收 ax0/ay0/aclr/asID 四个参数,把绘图基准和命名空间定下来,同时把 m_R、m_r、m_D 先归零,等 SetData() 再灌真实几何参数。 SetData() 一口气写入大圆半径 m_R、小圆半径 m_r、笔尖偏移 m_D、步长 m_dAlfa 和旋转圈数 m_nRotate。DrawSpiro() 先卡三道防线:r、D、Alpha 任一为 0 就 Print 报错退出,防止除零或死循环。 真正跑循环时,以 m_dAlfa 为步长从 0 累加到 m_nRotate*2*3.1415926(即圈数乘 2π),每步用 (m_R-m_r)*cos(a)+m_D*cos((m_R-m_r)/m_r*a) 算 x、用对应 sin 算 y,再交给 SetPoint() 落点。外汇与贵金属图表叠加这类几何图形属高风险辅助观察,参数错配可能画出无关噪声,建议先在策略测试器用纯数值验证。
class ISpiro : class="kw">public CSpiro { class="kw">public: ISpiro() { m_x0=class="num">0; m_y0=class="num">0; }; ~ISpiro() { ObjectsDeleteAll(class="num">0,class="num">0,-class="num">1); }; class="kw">virtual class="type">void Init(class="type">int ax0,class="type">int ay0,class="type">color aclr,class="type">class="kw">string asID); class="kw">virtual class="type">void SetData(class="type">class="kw">double aR,class="type">class="kw">double ar,class="type">class="kw">double aD,class="type">class="kw">double adAlpha,class="type">class="kw">double anRotate); class="kw">public: class="kw">virtual class="type">void DrawSpiro(); class="kw">virtual class="type">void SetPoint(class="type">int x,class="type">int y); }; class="type">void ISpiro::Init(class="type">int ax0,class="type">int ay0,class="type">color aclr,class="type">class="kw">string asID) { m_x0=ax0; m_y0=ay0; m_clr=aclr; m_sID=asID; m_R=class="num">0; m_r=class="num">0; m_D=class="num">0; } class="type">void ISpiro::SetData(class="type">class="kw">double aR,class="type">class="kw">double ar,class="type">class="kw">double aD,class="type">class="kw">double adAlpha,class="type">class="kw">double anRotate) { m_R=aR; m_r=ar; m_D=aD; m_dAlfa=adAlpha; m_nRotate=anRotate; } class="type">void ISpiro::DrawSpiro() { if(m_r<=class="num">0) { Print("Error! r==class="num">0"); class="kw">return; } if(m_D<=class="num">0) { Print("Error! D==class="num">0"); class="kw">return; } if(m_dAlfa==class="num">0) { Print("Error! Alpha==class="num">0"); class="kw">return; } ObjectsDeleteAll2(class="num">0,class="num">0,-class="num">1,m_sID+"pnt."); class="type">int n=class="num">0; class="type">class="kw">double a=class="num">0; class="kw">while(a<m_nRotate*class="num">2*class="num">3.1415926) { class="type">class="kw">double x=(m_R-m_r)*MathCos(a)+m_D*MathCos((m_R-m_r)/m_r*a); class="type">class="kw">double y=(m_R-m_r)*MathSin(a)-m_D*MathSin((m_R-m_r)/m_r*a); SetPoint(class="type">int(m_x0+x),class="type">int(m_y0+y)); a+=m_dAlfa; }
「用导入库在EA里画自定义图形」
把绘图逻辑编译成独立 ex5 再 import 进 EA,是 MT5 里隔离界面代码的有效做法。上面这段来自一个叫 Spiro 的 EA 目标文件,它从 Graph.ex5 导入了 SetLabel、SetEdit、SetButton、HideChart 四个函数,又从 ISpiro.ex5 导入了工厂函数 iSpiro() 来拿到 CSpiro 实例。 OnInit 里先 HideChart(0, clrWhite) 把主图背景刷白,再给对象名加 spiro. 前缀避免冲突。接着 spiro.Init(250, 200, clrBlack, sID) 设定画布中心与尺寸,SetData(100, 30, 40, 0.04, 10) 给的是半径、转速差、步长等五个参数,最后 DrawSpiro() 出图。 这种拆法让你改图形算法只重编 ISpiro,不动 EA 主体。外汇与贵金属图表加载此类对象有内存开销,参数设太大(如 Init 超过 500 像素)在低频帧账户上可能拖慢刷新。
class=class="str">"cmt">//--- Spiro.mq5 - the target file of the Expert Advisor class=class="str">"cmt">//--- importing some functions class="macro">#class="kw">import "Graph.ex5" class="type">void SetLabel(class="type">long achart, class="type">class="kw">string name, class="type">int wnd, class="type">class="kw">string text, class="type">color clr, class="type">int x, class="type">int y, class="type">int corn=class="num">0, class="type">int fontsize=class="num">8, class="type">class="kw">string font="Tahoma"); class="type">void SetEdit(class="type">long achart, class="type">class="kw">string name, class="type">int wnd, class="type">class="kw">string text, class="type">color txtclr, class="type">color bgclr, class="type">color brdclr, class="type">int x, class="type">int y, class="type">int dx, class="type">int dy, class="type">int corn=class="num">0, class="type">int fontsize=class="num">8, class="type">class="kw">string font="Tahoma", class="type">bool ro=false); class="type">void SetButton(class="type">long achart, class="type">class="kw">string name, class="type">int wnd, class="type">class="kw">string text, class="type">color txtclr, class="type">color bgclr, class="type">int x, class="type">int y, class="type">int dx, class="type">int dy, class="type">int corn=class="num">0, class="type">int fontsize=class="num">8, class="type">class="kw">string font="Tahoma", class="type">bool state=false); class="type">void HideChart(class="type">long achart, class="type">color BackClr); class="macro">#class="kw">import class=class="str">"cmt">//--- including the chart class class="macro">#include <Spiro.mqh> class=class="str">"cmt">//--- importing the object class="macro">#class="kw">import "ISpiro.ex5" CSpiro *iSpiro(); class="macro">#class="kw">import class=class="str">"cmt">//--- object instance CSpiro *spiro; class=class="str">"cmt">//--- prefix for chart objects class="type">class="kw">string sID; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Expert initialization function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnInit() { HideChart(class="num">0, clrWhite); sID="spiro."; DrawParam(); class=class="str">"cmt">//--- object instance created spiro=iSpiro(); class=class="str">"cmt">//--- initializing the drawing spiro.Init(class="num">250, class="num">200, clrBlack, sID); class=class="str">"cmt">//--- setting the calculation parameters spiro.SetData(class="num">100, class="num">30, class="num">40, class="num">0.04, class="num">10); class=class="str">"cmt">//--- drawing spiro.DrawSpiro(); }
◍ 卸载指标时释放指针
MQL5 指标或 EA 在终端移除、切换周期、重新编译时都会触发 OnDeinit。若构造阶段用 new 创建了对象却不在退出时清理,MT5 会留下无法回收的堆内存,多次加载后可能拖慢客户端。 上面这段处理函数只做一件事:用 delete spiro 把名为 spiro 的自定义对象销毁。注释写的是 deleting the object,即释放该指针占用的资源,避免句柄泄漏。 实盘环境里,贵金属与外汇品种波动剧烈、图表频繁重绘,指标被反复加载卸载的概率高于股票周期;养成在 OnDeinit 显式 delete 的习惯,能降低 MT5 卡顿的可能性。
class="type">void OnDeinit(const class="type">int reason) { class="kw">delete spiro; class=class="str">"cmt">// deleting the object }
跨 ex5 模块的变量初始化套路
把 ISuperClass 藏进 .ex5 做库文件后,最麻烦的是全局变量怎么从主程序喂进去。globals.mqh 里用 extern 声明的 CTrade *_trade 这类指针,不能在库里自己 new,只能由外部传指针进来,否则隐藏实现文件编译时会找不到实例。 最简单的做法是把指向对象的指针从 ISuperClass 类直接递给 .ex5 文件:外部初始化好 _trade,库函数接收后赋值,所有依赖全局变量的逻辑就能跑通。示例 7 里 iSuperClass(CTrade *atrade) 导出函数就干这件事——_trade=atrade 一行完成注入,return(new ISuperClass) 把隐藏类实例抛回。 全局变量一多,反复改函数头很烦。更好的办法是聚成一个专用类,把 extern 参数全塞进去。示例 8 的 __extern 类就是这么干的:trade 和 eaname 两个成员,Get() 从 extern 拉值,Set() 往 extern 写回,_GetExt() 返回类指针,主程序只调一次 iSuperClass(_GetExt()) 就能把整包变量送进库(示例 10)。 这套结构在 MT5 里实测可行:把 ISuperClass.mq5 标 #property library 编译成 ex5,主脚本用 #import 拽进来,OnStart 里一行拿到 CSuperClass 指针即可。外汇与贵金属 EA 用这种模块化拆法时,注意库更新要重编译依赖方,高风险品种下参数错传可能导致下单异常。
Example class="num">6. Public include file class=class="str">"cmt">//--- globals.mqh class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//--- instance of the trade function object class="kw">extern CTrade *_trade; Example class="num">7. Initialization of variables upon creation of the object class=class="str">"cmt">//--- ISuperClass.mq5 –hidden implementation file class="macro">#class="kw">property library CSuperClass *iSuperClass(CTrade *atrade) class="kw">export { class=class="str">"cmt">//--- saving the pointer _trade=atrade; class=class="str">"cmt">//--- returning the object of the hidden implementation of ISuperClass of the open CSuperClass class class="kw">return(new ISuperClass); } class=class="str">"cmt">//... the remaining code Example class="num">8. Public include file class=class="str">"cmt">//--- globals.mqh class="macro">#include <Trade\Trade.mqh> class=class="str">"cmt">//--- trade "object" class="kw">extern CTrade *_trade; class=class="str">"cmt">//--- name of the Expert Advisor of the system class="kw">extern class="type">class="kw">string _eaname; class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| class __extern | class=class="str">"cmt">//+------------------------------------------------------------------+ class __extern class=class="str">"cmt">// all class="kw">extern parameters for passing between the ex5 modules are accumulated here { class="kw">public: class=class="str">"cmt">//--- the list of all class="kw">public global variables to be passed class=class="str">"cmt">//--- trade "object" CTrade *trade; class=class="str">"cmt">//--- name of the Expert Advisor of the system class="type">class="kw">string eaname; class="kw">public: __extern() { }; ~__extern() { }; class=class="str">"cmt">//--- it is called when passing the parameters into the .ex5 file class="type">void Get() { trade=_trade; eaname=_eaname; }; class=class="str">"cmt">// getting the variables class=class="str">"cmt">//--- it is called in the .ex5 file class="type">void Set() { _trade=trade; _eaname=eaname; }; class=class="str">"cmt">// setting the variables }; class=class="str">"cmt">//--- getting the variables and pointer for passing the object into the .ex5 file __extern *_GetExt() { _ext.Get(); class="kw">return(GetPointer(_ext)); } class=class="str">"cmt">//--- the only instance for operation class="kw">extern __extern _ext; Example class="num">9. class=class="str">"cmt">//--- ISuperClass.mq5 –hidden implementation file class="macro">#class="kw">property library CSuperClass *iSuperClass(__extern *aext) class="kw">export { class=class="str">"cmt">//--- taking in all the parameters aext.Set(); class=class="str">"cmt">//--- returning the object class="kw">return(new ISuperClass); } class=class="str">"cmt">//--- ... the remaining code Example class="num">10. Using class="kw">export objects in the presence of class="kw">public global variables class=class="str">"cmt">//--- including global variables(usually located in SuperClass.mqh) class="macro">#include "globals.mqh" class=class="str">"cmt">//--- including the class="kw">public header class class="macro">#include "SuperClass.mqh" class=class="str">"cmt">//--- getting the hidden implementation object class="macro">#class="kw">import "ISuperClass.ex5" CSuperClass *iSuperClass(); class="macro">#class="kw">import class=class="str">"cmt">//+------------------------------------------------------------------+ class=class="str">"cmt">//| Script program start function | class=class="str">"cmt">//+------------------------------------------------------------------+ class="type">void OnStart() { class=class="str">"cmt">//--- creating the hidden implementation object providing for the passing of all parameters CSuperClass *sc=iSuperClass(_GetExt()); class=class="str">"cmt">//--- ... the remaining code }