源代码的跟踪、调试和结构分析·综合运用
🌲

源代码的跟踪、调试和结构分析·综合运用

(3/3)·把类继承、进入离开对和监视参数揉成一张可逐步展开的树,调试不再靠盲猜

进阶 第 3/3 篇
很多人调试 EA 只知道 Print 打点,函数嵌套一深就分不清谁调了谁。把调用顺序和监视状态建成树,逐步执行时哪里分叉、哪里回退一目了然。

「把调用栈塞进独立子窗口」

栈结构就绪后,下一步是把它画出来给人看。这里拆出两个显示类:CTreeView 负责树形渲染,CTraceView 管整体布局并把栈和其他信息组织到界面上,二者都从 CView 派生。 CTraceView::Create 里会顺手开一个独立子窗口,所有对象都画在那里面,而 EA 本身跑在另一个窗口。这样做能避免大量跟踪信息挡住被调试程序的图表输出,也能防止和原程序自己的图形对象打架。 为了让两个窗口互通,Create 里还挂了一个 TraceAgent 指标,它只有一个外部参数——目标图表 ID,专门把用户在子窗口里的点击、事件转发回主窗口。 实际界面里,左侧 TRACE 树是原始栈,下方 INFO 窗口显示选中节点的细节(比如 CTraceView::OnChartEvent)。中间 CLASS 树按类分组,右侧 FILE 树按文件分组,三棵树共享同一份栈数据并互相联动:点类名会同时高亮栈树和文件树里该类全部函数,点文件名则选中该文件所有函数与类,快速过滤出关心的调用群。 下面这段是 CTreeView 与 CTraceView 的骨架声明,注意 Create 的参数里 fontsize 默认 8、corn 默认 0,意味着节点文字偏小、无圆角边框,上 MT5 改这两个值能直接看到密度变化。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------&nbsp;&nbsp;&nbsp;&nbsp;class CTreeView</span>
<span class="keyword">class</span> CTreeView: <span class="keyword">class="kw">public</span> CView
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- basic functions</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CTreeView(); <span class="comment">class=class="str">"cmt">// constructor</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ~CTreeView(); <span class="comment">class=class="str">"cmt">// destructor</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; Attach(CTreeCtrl *atree); <span class="comment">class=class="str">"cmt">// attached the tree object for displaying it</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; 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>);
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- functions of processing of state</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; CTreeCtrl&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;*m_tree; <span class="comment">class=class="str">"cmt">// pointer to the tree object to be displayed</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp; m_sid; <span class="comment">class=class="str">"cmt">// last selected object(for highlighting)</span>
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp; 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>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- functions of displaying</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <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>
&nbsp;&nbsp; <span class="keyword">class="type">int</span>     m_bdx, m_bdy; <span class="comment">class=class="str">"cmt">// size of button of nodes</span>
&nbsp;&nbsp; CScrollView&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; m_scroll;
&nbsp;&nbsp; <span class="keyword">class="type">bool</span>&nbsp;&nbsp;&nbsp; m_bProperty; <span class="comment">class=class="str">"cmt">// show properties near the node</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; Draw(); <span class="comment">class=class="str">"cmt">// refresh the view</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; DrawTree(CNode *first,<span class="keyword">class="type">int</span> xpos,<span class="keyword">class="type">int</span> &amp;ypos,<span class="keyword">class="type">int</span> &amp;up,<span class="keyword">class="type">int</span> &amp;dn); <span class="comment">class=class="str">"cmt">// redraw</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; 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>
&nbsp;&nbsp;};
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------&nbsp;&nbsp;&nbsp;&nbsp;class CTreeView</span>
<span class="keyword">class</span> CTraceView: <span class="keyword">class="kw">public</span> CView
&nbsp;&nbsp;{
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- base functions</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; CTraceView() { }; <span class="comment">class=class="str">"cmt">// constructor</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ~CTraceView() { Deinit(); } <span class="comment">class=class="str">"cmt">// destructor</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; Deinit(); <span class="comment">class=class="str">"cmt">// full deinitialization of representation</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; Create(<span class="keyword">class="type">long</span> chart); <span class="comment">class=class="str">"cmt">// create and activate the representation</span>
&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">//--- function of processing of state</span>
<span class="keyword">class="kw">public</span>:
&nbsp;&nbsp; <span class="keyword">class="type">int</span>&nbsp;&nbsp;&nbsp;&nbsp; m_hagent; <span class="comment">class=class="str">"cmt">// handler of the indicator-agent for sending messages</span>
&nbsp;&nbsp; CTraceCtrl&nbsp;&nbsp; *m_trace; <span class="comment">class=class="str">"cmt">// pointer to created tracer</span>
&nbsp;&nbsp; CTreeView&nbsp;&nbsp;&nbsp; *m_viewstack; <span class="comment">class=class="str">"cmt">// tree for displaying the stack</span>
&nbsp;&nbsp; CTreeView    *m_viewinfo; <span class="comment">class=class="str">"cmt">// tree for displaying of node properties</span>
&nbsp;&nbsp; CTreeView    *m_viewfile; <span class="comment">class=class="str">"cmt">// tree for displaying of the stack with grouping by files</span>
&nbsp;&nbsp; CTreeView&nbsp;&nbsp;&nbsp; *m_viewclass; <span class="comment">class=class="str">"cmt">// tree for displaying of stack with grouping by classes</span>
&nbsp;&nbsp; <span class="keyword">class="type">void</span>&nbsp;&nbsp;&nbsp; <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 周期,但高频下仍可能丢事件,实盘前务必在模拟盘压测。

MQL5 / C++
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 前缀。

MQL5 / C++
<span class="comment">class=class="str">"cmt">//------------------------------------------------------------------&nbsp;&nbsp;&nbsp;&nbsp;class="kw">struct tagWatch</span>
<span class="keyword">class="kw">struct</span> tagWatch
{
&nbsp;&nbsp;&nbsp;&nbsp;<span class="keyword">class="type">class="kw">string</span> m_name;&nbsp;&nbsp;&nbsp;&nbsp; <span class="comment">class=class="str">"cmt">// name
</span>&nbsp;&nbsp;&nbsp; <span class="keyword">class="type">class="kw">string</span> m_val;&nbsp;&nbsp;&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// value</span>
};
<span class="preprocessor">class="macro">#define _WATCH(w, v)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; if (!NIL(m_trace) &amp;&amp; !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">//------------------------------------------------------------------&nbsp;&nbsp;&nbsp;&nbsp;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>
&nbsp;&nbsp;{
&nbsp;&nbsp; <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>
&nbsp;&nbsp; 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>
&nbsp;&nbsp; m_cur.m_break=<span class="keyword">true</span>; <span class="comment">class=class="str">"cmt">// activated only at the current one</span>
&nbsp;&nbsp; m_traceview.m_viewstack.m_sid=m_cur.m_id; <span class="comment">class=class="str">"cmt">// moved selection to it</span>
&nbsp;&nbsp; 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>
&nbsp;&nbsp; m_traceview.Draw(); <span class="comment">class=class="str">"cmt">// drew everything</span>
&nbsp;&nbsp; <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>
&nbsp;&nbsp; <span class="keyword">class="type">bool</span> state=<span class="functions">ObjectGetInteger</span>(m_traceview.m_chart,name,<span class="keyword">OBJPROP_STATE</span>);
&nbsp;&nbsp; <span class="keyword">while</span>(!state) <span class="comment">class=class="str">"cmt">// button is not pressed, execute the loop</span>
&nbsp;&nbsp;&nbsp;&nbsp; {
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<span class="functions">Sleep</span>(<span class="number">class="num">1000</span>); <span class="comment">class=class="str">"cmt">// made a pause</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;state=<span class="functions">ObjectGetInteger</span>(m_traceview.m_chart,name,<span class="keyword">OBJPROP_STATE</span>);&nbsp;&nbsp;<span class="comment">class=class="str">"cmt">// check its state</span>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;<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>
&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;m_traceview.Draw(); <span class="comment">class=class="str">"cmt">// drew possible changes</span>
&nbsp;&nbsp;&nbsp;&nbsp; }
&nbsp;&nbsp; m_cur.m_break=<span class="keyword">false</span>; <span class="comment">class=class="str">"cmt">// removed the flag</span>
&nbsp;&nbsp; m_traceview.Draw(); <span class="comment">class=class="str">"cmt">// drew the update</span>
&nbsp;&nbsp;}

「收束」

这套基于 CTraceCtrl 的跟踪思路,在 MT5 里实测只能靠 EA 拉起指标树,脚本因不处理 OnChartEvent 且无法主动回写,基本断掉了可视化反馈的路子。MQL4 没有等效的类体系,这也是 MQL5 在标准库层面多出的一截能力。 随附的 mql5.zip 约 24.67 KB,里面是本文涉及的类与加了 _IN 宏的标准库文件;验证时直接把 Expert 示例丢进 MT5 的 Experts 目录,别手动挂指标,EA 会自己调。 外汇与贵金属品种波动大、杠杆高,这类结构分析工具只帮你看清调用链,不替你过滤风险,上真仓前先在策略测试器跑通再谈。

把调用栈树交给小布盯盘
这些诊断小布盯盘的 AIGC 已内置,打开对应品种页即可看到实时结构快照,你只管判断分支是否如预期。

常见问题

CNodeBase 持有指向子节点的指针数组,分支数量无上限;标准库节点通常受限,复杂调用栈容易写不下。
每次进入块建 CIn 实例并调 In 挂新节点,离开时归位当前节点,反复进出自然长成有向非循环图。
可以,小布盯盘对应品种页内置了结构快照,省去自己接 CTraceCtrl 的成本,专注决策即可。
分别管函数结构、临时容器和所需栈形态,配合 CIn 辅助类分阶段拼出完整跟踪树。
仅为开发期诊断工具,不影响实盘逻辑;但外汇贵金属杠杆高,任何代码改动都应在模拟盘充分验证。