源代码的跟踪、调试和结构分析·综合运用
(3/3)·把类继承、进入离开对和监视参数揉成一张可逐步展开的树,调试不再靠盲猜
「把调用栈塞进独立子窗口」
栈结构就绪后,下一步是把它画出来给人看。这里拆出两个显示类:CTreeView 负责树形渲染,CTraceView 管整体布局并把栈和其他信息组织到界面上,二者都从 CView 派生。 CTraceView::Create 里会顺手开一个独立子窗口,所有对象都画在那里面,而 EA 本身跑在另一个窗口。这样做能避免大量跟踪信息挡住被调试程序的图表输出,也能防止和原程序自己的图形对象打架。 为了让两个窗口互通,Create 里还挂了一个 TraceAgent 指标,它只有一个外部参数——目标图表 ID,专门把用户在子窗口里的点击、事件转发回主窗口。 实际界面里,左侧 TRACE 树是原始栈,下方 INFO 窗口显示选中节点的细节(比如 CTraceView::OnChartEvent)。中间 CLASS 树按类分组,右侧 FILE 树按文件分组,三棵树共享同一份栈数据并互相联动:点类名会同时高亮栈树和文件树里该类全部函数,点文件名则选中该文件所有函数与类,快速过滤出关心的调用群。 下面这段是 CTreeView 与 CTraceView 的骨架声明,注意 Create 的参数里 fontsize 默认 8、corn 默认 0,意味着节点文字偏小、无圆角边框,上 MT5 改这两个值能直接看到密度变化。
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------ class CTreeView</span> <span class="keyword">class</span> CTreeView: <span class="keyword">class="kw">public</span> CView { <span class="comment">class=class="str">"cmt">//--- basic functions</span> <span class="keyword">class="kw">public</span>: CTreeView(); <span class="comment">class=class="str">"cmt">// constructor</span> ~CTreeView(); <span class="comment">class=class="str">"cmt">// destructor</span> <span class="keyword">class="type">void</span> Attach(CTreeCtrl *atree); <span class="comment">class=class="str">"cmt">// attached the tree object for displaying it</span> <span class="keyword">class="type">void</span> Create(<span class="keyword">class="type">long</span> chart,<span class="keyword">class="type">class="kw">string</span> name,<span class="keyword">class="type">int</span> wnd,<span class="keyword">class="type">class="kw">color</span> clr,<span class="keyword">class="type">class="kw">color</span> bgclr,<span class="keyword">class="type">class="kw">color</span> selclr,<span class="keyword"> class="type">int</span> x,<span class="keyword">class="type">int</span> y,<span class="keyword">class="type">int</span> dx,<span class="keyword">class="type">int</span> dy,<span class="keyword">class="type">int</span> corn=<span class="number">class="num">0</span>,<span class="keyword">class="type">int</span> fontsize=<span class="number">class="num">8</span>,<span class="keyword">class="type">class="kw">string</span> font=<span class="class="type">class="kw">string">"Arial"</span>); <span class="comment">class=class="str">"cmt">//--- functions of processing of state</span> <span class="keyword">class="kw">public</span>: CTreeCtrl *m_tree; <span class="comment">class=class="str">"cmt">// pointer to the tree object to be displayed</span> <span class="keyword">class="type">int</span> m_sid; <span class="comment">class=class="str">"cmt">// last selected object(for highlighting)</span> <span class="keyword">class="type">int</span> OnClick(<span class="keyword">class="type">class="kw">string</span> name); <span class="comment">class=class="str">"cmt">// processing the event of clicking on an object</span> <span class="comment">class=class="str">"cmt">//--- functions of displaying</span> <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">int</span> m_ndx, m_ndy; <span class="comment">class=class="str">"cmt">// size of margins from button for drawing</span> <span class="keyword">class="type">int</span> m_bdx, m_bdy; <span class="comment">class=class="str">"cmt">// size of button of nodes</span> CScrollView m_scroll; <span class="keyword">class="type">bool</span> m_bProperty; <span class="comment">class=class="str">"cmt">// show properties near the node</span> <span class="keyword">class="type">void</span> Draw(); <span class="comment">class=class="str">"cmt">// refresh the view</span> <span class="keyword">class="type">void</span> DrawTree(CNode *first,<span class="keyword">class="type">int</span> xpos,<span class="keyword">class="type">int</span> &ypos,<span class="keyword">class="type">int</span> &up,<span class="keyword">class="type">int</span> &dn); <span class="comment">class=class="str">"cmt">// redraw</span> <span class="keyword">class="type">void</span> DeleteView(CNode *root=<span class="predefines">NULL</span>,<span class="keyword">class="type">bool</span> delparent=<span class="keyword">true</span>); <span class="comment">class=class="str">"cmt">// class="kw">delete all displayed elements starting from a specified node</span> }; <span class="comment">class=class="str">"cmt">//------------------------------------------------------------------ class CTreeView</span> <span class="keyword">class</span> CTraceView: <span class="keyword">class="kw">public</span> CView { <span class="comment">class=class="str">"cmt">//--- base functions</span> <span class="keyword">class="kw">public</span>: CTraceView() { }; <span class="comment">class=class="str">"cmt">// constructor</span> ~CTraceView() { Deinit(); } <span class="comment">class=class="str">"cmt">// destructor</span> <span class="keyword">class="type">void</span> Deinit(); <span class="comment">class=class="str">"cmt">// full deinitialization of representation</span> <span class="keyword">class="type">void</span> Create(<span class="keyword">class="type">long</span> chart); <span class="comment">class=class="str">"cmt">// create and activate the representation</span> <span class="comment">class=class="str">"cmt">//--- function of processing of state</span> <span class="keyword">class="kw">public</span>: <span class="keyword">class="type">int</span> m_hagent; <span class="comment">class=class="str">"cmt">// handler of the indicator-agent for sending messages</span> CTraceCtrl *m_trace; <span class="comment">class=class="str">"cmt">// pointer to created tracer</span> CTreeView *m_viewstack; <span class="comment">class=class="str">"cmt">// tree for displaying the stack</span> CTreeView *m_viewinfo; <span class="comment">class=class="str">"cmt">// tree for displaying of node properties</span> CTreeView *m_viewfile; <span class="comment">class=class="str">"cmt">// tree for displaying of the stack with grouping by files</span> CTreeView *m_viewclass; <span class="comment">class=class="str">"cmt">// tree for displaying of stack with grouping by classes</span> <span class="keyword">class="type">void</span> <span class="functions">OnTimer</span>(); <span class="comment">class=class="str">"cmt">// handler of timer</span>
把图表事件甩给指定图表窗口
想在 MT5 里做多图表联动,核心不是自己画一遍,而是把当前图表的事件原样转发出去。上面这段就是典型做法:在 OnChartEvent 里拿到 id、lparam、dparam、sparam 四个参数,直接调 EventChartCustom 发往 cid 指定的接收图表。 输入参数 cid 默认是 0,也就是主图表;你在外部改成某个具体图表 ID,就能把鼠标点击、键盘、自定义消息全丢过去。注意 OnCalculate 这里直接 return(rates_total),说明这个片段只管事件桥接,不参与指标数值计算。 接收端只要在自己的 OnChartEvent 里用 id 区间(自定义事件一般从 CHARTEVENT_CUSTOM 起算)捕获,就能拿到转发来的 lparam/dparam/sparam。外汇和贵金属行情跳变快,这种跨图表通信延迟通常低于 1 个 tick 周期,但高频下仍可能丢事件,实盘前务必在模拟盘压测。
class="type">void OnChartEvent(const class="type">int,const class="type">long&,const class="type">class="kw">double&,const class="type">class="kw">string&); class=class="str">"cmt">// handler of event class=class="str">"cmt">//--- functions of displaying class="kw">public: class="type">void Draw(); class=class="str">"cmt">// refresh objects class="type">void DeleteView(); class=class="str">"cmt">// class="kw">delete the view class="type">void UpdateInfoTree(CNode *node,class="type">bool bclear); class=class="str">"cmt">// displaying the window of detailed information about a node class="type">class="kw">string TimeSeparate(class="type">long time); class=class="str">"cmt">// special function for transformation of time into class="type">class="kw">string }; class="macro">#class="kw">property indicator_chart_window input class="type">long cid=class="num">0; class=class="str">"cmt">// чарт получателя class=class="str">"cmt">//------------------------------------------------------------------ OnCalculate class="type">int OnCalculate(const class="type">int rates_total, const class="type">int prev_calculated, const class="type">int begin, const class="type">class="kw">double& price[]) { class="kw">return(rates_total); } class=class="str">"cmt">//------------------------------------------------------------------ OnChartEvent class="type">void OnChartEvent(const class="type">int id,const class="type">long& lparam,const class="type">class="kw">double& dparam,const class="type">class="kw">string& sparam) { EventChartCustom(cid, (class="type">class="kw">ushort)id, lparam, dparam, sparam); }
◍ 监视值与断点怎么落进栈树
CNode 里挂了一个 tagWatch 结构数组,专门用来把变量或表达式的命名值摆到信息窗口里看。往当前节点加监视值要走 CTrace::AddWatch,再套一层 _WATCH 宏;和节点同名约束一样,监视名必须唯一,重名不新增只覆盖旧值。 断点靠 while(true) 式的死循环卡住执行流,MQL5 的方便点在退出机制——点节点旁的红色按钮就能破循环。遇到 CTrace::Break 时,栈树会同步展开调用链,父节点自动打开,必要时滚动让你看见当前节点。 下面这段是断点函数的核心:先清掉所有节点的 m_break 标记,只给当前节点置真,再把视图选中切过去并展开父链,最后进循环等红色按钮被按下才继续。 别把监视名写重了 重名时系统静默更新旧值而非报错,调试时容易误以为新变量没生效,建议在 _WATCH 里带节点 id 前缀。
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------ class="kw">struct tagWatch</span> <span class="keyword">class="kw">struct</span> tagWatch { <span class="keyword">class="type">class="kw">string</span> m_name; <span class="comment">class=class="str">"cmt">// name </span> <span class="keyword">class="type">class="kw">string</span> m_val; <span class="comment">class=class="str">"cmt">// value</span> }; <span class="preprocessor">class="macro">#define _WATCH(w, v) if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, class="type">class="kw">string(v));</span> <span class="comment">class=class="str">"cmt">//------------------------------------------------------------------ Break</span> <span class="keyword">class="type">void</span> CTraceCtrl::Break() <span class="comment">class=class="str">"cmt">// checking the debug mode of a node</span> { <span class="keyword">if</span>(NIL(m_traceview)) <span class="keyword">class="kw">return</span>; <span class="comment">class=class="str">"cmt">// check of validity</span> m_stack.BreakBy(TG_ALL,<span class="predefines">NULL</span>,<span class="keyword">false</span>); <span class="comment">class=class="str">"cmt">// removed the m_break flags from all nodes</span> m_cur.m_break=<span class="keyword">true</span>; <span class="comment">class=class="str">"cmt">// activated only at the current one</span> m_traceview.m_viewstack.m_sid=m_cur.m_id; <span class="comment">class=class="str">"cmt">// moved selection to it</span> m_stack.ExpandBy(TG_UP,m_cur,<span class="keyword">true</span>,m_cur); <span class="comment">class=class="str">"cmt">// expand parent node if they are closed</span> m_traceview.Draw(); <span class="comment">class=class="str">"cmt">// drew everything</span> <span class="keyword">class="type">class="kw">string</span> name=m_traceview.m_viewstack.m_name+<span class="keyword">class="type">class="kw">string</span>(m_cur.m_id)+<span class="keyword">".dbg"</span>; <span class="comment">class=class="str">"cmt">// got name of the BREAK button</span> <span class="keyword">class="type">bool</span> state=<span class="functions">ObjectGetInteger</span>(m_traceview.m_chart,name,<span class="keyword">OBJPROP_STATE</span>); <span class="keyword">while</span>(!state) <span class="comment">class=class="str">"cmt">// button is not pressed, execute the loop</span> { <span class="functions">Sleep</span>(<span class="number">class="num">1000</span>); <span class="comment">class=class="str">"cmt">// made a pause</span> state=<span class="functions">ObjectGetInteger</span>(m_traceview.m_chart,name,<span class="keyword">OBJPROP_STATE</span>); <span class="comment">class=class="str">"cmt">// check its state</span> <span class="keyword">if</span>(!m_traceview.IsOpenView()) <span class="keyword">class="kw">break</span>; <span class="comment">class=class="str">"cmt">// if the window is closed, exit</span> m_traceview.Draw(); <span class="comment">class=class="str">"cmt">// drew possible changes</span> } m_cur.m_break=<span class="keyword">false</span>; <span class="comment">class=class="str">"cmt">// removed the flag</span> m_traceview.Draw(); <span class="comment">class=class="str">"cmt">// drew the update</span> }
「收束」
这套基于 CTraceCtrl 的跟踪思路,在 MT5 里实测只能靠 EA 拉起指标树,脚本因不处理 OnChartEvent 且无法主动回写,基本断掉了可视化反馈的路子。MQL4 没有等效的类体系,这也是 MQL5 在标准库层面多出的一截能力。 随附的 mql5.zip 约 24.67 KB,里面是本文涉及的类与加了 _IN 宏的标准库文件;验证时直接把 Expert 示例丢进 MT5 的 Experts 目录,别手动挂指标,EA 会自己调。 外汇与贵金属品种波动大、杠杆高,这类结构分析工具只帮你看清调用链,不替你过滤风险,上真仓前先在策略测试器跑通再谈。