
源代码的跟踪、调试和结构分析
简介
本文介绍在执行期间创建一个调用栈的方法之一。本文介绍了以下功能:
- 创建使用的类、函数和文件的结构。
- 保持以前所有的栈创建调用栈。调用的顺序。
- 执行期间查看 Watch(监视)参数的状态。
- 代码的逐步式执行。
- 分组和排列获得的栈,获取“极端”信息。
开发的主要原则
选择一种常见方法作为表示结构的方法 – 以树的形式显示。为此,我们需要两个信息类。CNode - 用于写入有关栈的所有信息的“节点”。CTreeCtrl - 处理所有节点的“树”。以及用于处理树的跟踪器本身 - CTraceCtrl。
CNodeBase 和 CTreeBase 类描述处理节点和树的基本属性和方法。
继承的类 CNode 扩展 CNodeBase 的基本功能,CTreeBase 类处理派生类 CNode。由于 CNodeBase 是其他标准节点的父节点,并且出于层次结构和继承的方便性,它被分出来作为一个独立的类,这样就完成了。
与来自标准库的 CTreeNode 不同,CNodeBase 类包含一个指向节点的指针数组,因此从这个节点发出的“分支”是没有数量限制的。
CNodeBase 和 CNode 类
class CNode; // forward declaration //------------------------------------------------------------------ class CNodeBase class CNodeBase { public: CNode *m_next[]; // list of nodes it points to CNode *m_prev; // parent node int m_id; // unique number string m_text; // text public: CNodeBase() { m_id=0; m_text=""; } // constructor ~CNodeBase(); // destructor }; //------------------------------------------------------------------ class CNode class CNode : public CNodeBase { public: bool m_expand; // expanded bool m_check; // marked with a dot bool m_select; // highlighted //--- run-time information int m_uses; // number of calls of the node long m_tick; // time spent in the node long m_tick0; // time of entering the node datetime m_last; // time of entering the node tagWatch m_watch[]; // list of name/value parameters bool m_break; // debug-pause //--- parameters of the call string m_file; // file name int m_line; // number of row in the file string m_class; // class name string m_func; // function name string m_prop; // add. information public: CNode(); // constructor ~CNode(); // destructor void AddWatch(string watch,string val); };
您可以在附带的文件中找到所有类的实施。在本文中,我们将只介绍它们的头部和重要的函数。
依据接受的分类,CTreeBase 表示一个有向非循环图。派生类 CTreeCtrl 使用 CNode 并为其所有功能服务:添加、更改和删除 CNode 节点。
CTreeCtrl 和 CNode 可以成功地代替标准库中的对应类,因为它们具有稍多一些的功能。
CTreeBase 和 CTreeCtrl 类
//------------------------------------------------------------------ class CTreeBase class CTreeBase { public: CNode *m_root; // first node of the tree int m_maxid; // counter of ID //--- base functions public: CTreeBase(); // constructor ~CTreeBase(); // destructor void Clear(CNode *root=NULL); // deletion of all nodes after a specified one CNode *FindNode(int id,CNode *root=NULL); // search of a node by its ID starting from a specified node CNode *FindNode(string txt,CNode *root=NULL); // search of a node by txt starting from a specified node int GetID(string txt,CNode *root=NULL); // getting ID for a specified Text, the search starts from a specified node int GetMaxID(CNode *root=NULL); // getting maximal ID in the tree int AddNode(int id,string text,CNode *root=NULL); // adding a node to the list, search is performed by ID starting from a specified node int AddNode(string txt,string text,CNode *root=NULL); // adding a node to the list, search is performed by text starting from a specified node int AddNode(CNode *root,string text); // adding a node under root }; //------------------------------------------------------------------ class CTreeCtrl class CTreeCtrl : public CTreeBase { //--- base functions public: CTreeCtrl() { m_root.m_file="__base__"; m_root.m_line=0; m_root.m_func="__base__"; m_root.m_class="__base__"; } // constructor ~CTreeCtrl() { delete m_root; m_maxid=0; } // destructor void Reset(CNode *root=NULL); // reset the state of all nodes void SetDataBy(int mode,int id,string text,CNode *root=NULL); // changing text for a specified ID, search is started from a specified node string GetDataBy(int mode,int id,CNode *root=NULL); // getting text for a specified ID, search is started from a specified node //--- processing state public: bool IsExpand(int id,CNode *root=NULL); // getting the m_expand property for a specified ID, search is started from a specified node bool ExpandIt(int id,bool state,CNode *root=NULL); // change the m_expand state, search is started from a specified node void ExpandBy(int mode,CNode *node,bool state,CNode *root=NULL); // expand node of a specified node bool IsCheck(int id,CNode *root=NULL); // getting the m_check property for a specified ID, search is started from a specified node bool CheckIt(int id,bool state,CNode *root=NULL); // change the m_check state to a required one starting from a specified node void CheckBy(int mode,CNode *node,bool state,CNode *root=NULL); // mark the whole tree bool IsSelect(int id,CNode *root=NULL); // getting the m_select property for a specified ID, search is started from a specified node bool SelectIt(int id,bool state,CNode *root=NULL); // change the m_select state to a required one starting from a specified node void SelectBy(int mode,CNode *node,bool state,CNode *root=NULL); // highlight the whole tree bool IsBreak(int id,CNode *root=NULL); // getting the m_break property for a specified ID, search is started from a specified node bool BreakIt(int id,bool state,CNode *root=NULL); // change the m_break state, search is started from a specified node void BreakBy(int mode,CNode *node,bool state,CNode *root=NULL); // set only for a selected one //--- operations with nodes public: void SortBy(int mode,bool ascend,CNode *root=NULL); // sorting by a property void GroupBy(int mode,CTreeCtrl *atree,CNode *node=NULL); // grouping by a property };
结构结束于以下两个类:CTraceCtrl - 其唯一的实例直接用于跟踪,它包含 CTreeCtrl 类的三个实例,用于创建函数的所需结构;另一个是 CIn 类,是一个临时容器。这只是一个用于向 CTraceCtrl 添加新节点的辅助类。
CTraceCtrl 和 CIn 类
class CTraceView; // provisional declaration //------------------------------------------------------------------ class CTraceCtrl class CTraceCtrl { public: CTreeCtrl *m_stack; // object of graph CTreeCtrl *m_info; // object of graph CTreeCtrl *m_file; // grouping by files CTreeCtrl *m_class; // grouping by classes CTraceView *m_traceview; // pointer to displaying of class CNode *m_cur; // pointer to the current node CTraceCtrl() { Create(); Reset(); } // tracer created ~CTraceCtrl() { delete m_stack; delete m_info; delete m_file; delete m_class; } // tracer deleted void Create(); // tracer created void In(string afile,int aline,string aname,int aid); // entering a specified node void Out(int aid); // exit from a specified node bool StepBack(); // exit from a node one step higher (going to the parent) void Reset() { m_cur=m_stack.m_root; m_stack.Reset(); m_file.Reset(); m_class.Reset(); } // resetting all nodes void Clear() { m_cur=m_stack.m_root; m_stack.Clear(); m_file.Clear(); m_class.Clear(); } // resetting all nodes public: void AddWatch(string name,string val); // checking the debug mode for a node void Break(); // pause for a node }; //------------------------------------------------------------------ CIn class CIn { public: void In(string afile,int aline,string afunc) { if(NIL(m_trace)) return; // exit if there is no graph if(NIL(m_trace.m_tree)) return; if(NIL(m_trace.m_tree.m_root)) return; if(NIL(m_trace.m_cur)) m_trace.m_cur=m_trace.m_tree.m_root; m_trace.In(afile,aline,afunc,-1); // entering the next one } void ~CIn() { if(!NIL(m_trace)) m_trace.Out(-1); } // exiting higher };
CIn 类的操作模式
此类负责创建栈树。
图形的形成是使用两个 CTraceCtrl 函数,在两个阶段中逐步进行的:
void In(string afile, int aline, string aname, int aid); // entering a specified node void Out(int aid); // exit before a specified node
换言之,要形成树,进行了对 进入-离开-进入-离开-进入-进入-离开-离开 等的连续调用。
进入-离开 对按以下方式工作:
1. 进入块(函数、循环、条件等),即正好在大括号 "{" 的后面。
在进入块时,创建 CIn 的一个新实例,它获取已经从先前的节点开始的当前 CTraceCtrl。在 CIn 中调用 CTraceCtrl::In 函数,它在栈中创建一个新节点。该节点创建于当前节点 CTraceCtrl::m_cur 之下。关于进入的所有实际信息都被写入该节点:文件名、行号、类名、函数、当前时间等。
2. 遇到括号 "}" 时从块退出。
在从块退出时,MQL 自动调用析构函数 CIn::~CIn。CTraceCtrl::Out 是在析构函数中调用的。当前节点 CTraceCtrl::m_cur 的指针在树中升高一级。此时不为新的节点调用析构函数,节点保留在树中。
栈的形成方案
调用栈以树的形式的形成,并且填写有关调用的所有信息是使用 CIn 容器执行的。
让调用更加容易的宏
为了避免在您的代码中重写很长的创建 CIn 对象并进入节点的代码行,用调用宏来代替它非常方便:#define _IN CIn _in; _in.In(__FILE__, __LINE__, __FUNCTION__)
如您所见,创建了 CIn 对象,然后我们进入节点。
因为 MQL 在局部变量的名称与全局变量的名称相同时会发出警告,最好采用以下形式(更加精确和清晰地)创建 3-4 个具有其它变量名的类似定义:
#define _IN1 CIn _in1; _in1.In(__FILE__, __LINE__, __FUNCTION__) #define _IN2 CIn _in2; _in2.In(__FILE__, __LINE__, __FUNCTION__) #define _IN3 CIn _in3; _in3.In(__FILE__, __LINE__, __FUNCTION__)在您深入到子块时,使用下一个宏 _INx
bool CSampleExpert::InitCheckParameters(int digits_adjust) { _IN; //--- initial data checks if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel()) { _IN1; printf("Take Profit must be greater than %d",m_symbol.StopsLevel());
随着在 build 411 中宏的出现,您可以充分使用通过 #define 进行的参数传递。
这是为什么您可以在 CTraceCtrl 类中找到以下宏定义的原因:
#define NIL(p) (CheckPointer(p)==POINTER_INVALID)
它允许缩短指针有效性的检查。
例如,以下代码行:
if (CheckPointer(m_tree))==POINTER_INVALID || CheckPointer(m_cur))==POINTER_INVALID) return;
被更短的形式代替:
if (NIL(m_tree) || NIL(m_cur)) return;
为跟踪准备您的文件
为了控制和获取栈,您需要采取三个步骤。
1. 添加需要的文件#include <Trace.mqh>
此时整个标准库以 CObject 类为基础。因此,如果它在您的文件中也用作一个基类,则将 Trace.mqh 添加到 Object.mqh 就已经足够了。
2. 将 _IN 宏放入需要的块(您可以使用搜索/替换)
使用 _IN 宏的例子:bool CSampleExpert::InitCheckParameters(int digits_adjust) { _IN; //--- initial data checks if(InpTakeProfit*digits_adjust<m_symbol.StopsLevel()) { _IN1; printf("Take Profit must be greater than %d",m_symbol.StopsLevel());
3. 在构成程序的主要模块的 OnInit、OnTime 和 OnDeinit 函数中,分别添加全局对象 CTraceCtrl 的创建、修改和删除。您可以在下面找到用于插入的现成代码:
在主代码中嵌入跟踪器
//------------------------------------------------------------------ OnInit int OnInit() { //**************** m_traceview= new CTraceView; // created displaying of the graph m_trace= new CTraceCtrl; // created the graph m_traceview.m_trace=m_trace; // attached the graph m_trace.m_traceview=m_traceview; // attached displaying of the graph m_traceview.Create(ChartID()); // created chart //**************** // remaining part of your code… return(0); } //------------------------------------------------------------------ OnDeinit void OnDeinit(const int reason) { //**************** delete m_traceview; delete m_trace; //**************** // remaining part of your code… } //------------------------------------------------------------------ OnTimer void OnTimer() { //**************** if (m_traceview.IsOpenView(m_traceview.m_chart)) m_traceview.OnTimer(); else { m_traceview.Deinit(); m_traceview.Create(ChartID()); } // if the window is accidentally closed //**************** // remaining part of your code… } //------------------------------------------------------------------ OnChartEvent void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam) { //**************** m_traceview.OnChartEvent(id, lparam, dparam, sparam); //**************** // remaining part of your code… }
跟踪显示类
这样,栈就组织好了。现在,让我们研究获得的信息的显示。
为此,我们要创建两个类。CTreeView – 用于树的显示,CTraceView – 用于控制树和有关栈的其他信息的显示。两个类都派生于基类 CView。
CTreeView 和 CTraceView 类
//------------------------------------------------------------------ class CTreeView class CTreeView: public CView { //--- basic functions public: CTreeView(); // constructor ~CTreeView(); // destructor void Attach(CTreeCtrl *atree); // attached the tree object for displaying it void Create(long chart,string name,int wnd,color clr,color bgclr,color selclr, int x,int y,int dx,int dy,int corn=0,int fontsize=8,string font="Arial"); //--- functions of processing of state public: CTreeCtrl *m_tree; // pointer to the tree object to be displayed int m_sid; // last selected object (for highlighting) int OnClick(string name); // processing the event of clicking on an object //--- functions of displaying public: int m_ndx, m_ndy; // size of margins from button for drawing int m_bdx, m_bdy; // size of button of nodes CScrollView m_scroll; bool m_bProperty; // show properties near the node void Draw(); // refresh the view void DrawTree(CNode *first,int xpos,int &ypos,int &up,int &dn); // redraw void DeleteView(CNode *root=NULL,bool delparent=true); // delete all displayed elements starting from a specified node }; //------------------------------------------------------------------ class CTreeView class CTraceView: public CView { //--- base functions public: CTraceView() { }; // constructor ~CTraceView() { Deinit(); } // destructor void Deinit(); // full deinitialization of representation void Create(long chart); // create and activate the representation //--- function of processing of state public: int m_hagent; // handler of the indicator-agent for sending messages CTraceCtrl *m_trace; // pointer to created tracer CTreeView *m_viewstack; // tree for displaying the stack CTreeView *m_viewinfo; // tree for displaying of node properties CTreeView *m_viewfile; // tree for displaying of the stack with grouping by files CTreeView *m_viewclass; // tree for displaying of stack with grouping by classes void OnTimer(); // handler of timer void OnChartEvent(const int,const long&,const double&,const string&); // handler of event //--- functions of displaying public: void Draw(); // refresh objects void DeleteView(); // delete the view void UpdateInfoTree(CNode *node,bool bclear); // displaying the window of detailed information about a node string TimeSeparate(long time); // special function for transformation of time into string };
作为一种最佳情形,我们已经选择在一个单独的子窗口中显示栈。
换言之,当在函数 CTraceView::Create 中创建 CTraceView 类时,也创建了图表窗口,并且在该窗口中绘制所有对象,尽管 CTraceView 是在 EA 交易于另一个窗口中创建和工作的。这样做能够防止大量的信息阻碍被跟踪程序的源代码的运行,并且防止阻碍在图表中显示其自己的信息。
但是为了让两个窗口能够互动,我们需要向窗口添加一个指标,该指标将用户的所有事件发送到带被跟踪程序的基本窗口。
指标在同一函数 CTraceView::Create 中创建。它只有一个外部参数 - 图表的 ID(所有事件应发送目的地)。
TraceAgent 指标
#property indicator_chart_window input long cid=0; // чарт получателя //------------------------------------------------------------------ OnCalculate int OnCalculate(const int rates_total, const int prev_calculated, const int begin, const double& price[]) { return(rates_total); } //------------------------------------------------------------------ OnChartEvent void OnChartEvent(const int id,const long& lparam,const double& dparam,const string& sparam) { EventChartCustom(cid, (ushort)id, lparam, dparam, sparam); }
结果,我们有一个非常完美的对栈的结构化表示。
初始栈显示在左侧的树 TRACE(跟踪) 中。
其下方是 INFO(信息)窗口,包含所选节点的详细信息(在本例中为 CTraceView::OnChartEvent)。两个含有树的相邻窗口显示相同的栈,但它们分别是按类(中间的 CLASS 树)和按文件(右侧的 FILE 树)分组的。
类树和文件树具有与栈的主树同步以及便于控制的嵌入式机制。例如,当您单击类树中一个类名时,则将在栈树和文件树中选中此类的所有函数。同样的,当您单击一个文件名时,该文件中的所有函数和类都会被选中。
处理栈的功能
- 添加监视参数
如您已经注意到的,CNode 节点参数包括结构数组 tagWatch。它的创建仅是为了方便表示信息。它包含变量或表达式的命名值。
监视值的结构
//------------------------------------------------------------------ struct tagWatch struct tagWatch { string m_name; // name string m_val; // value };
为了向当前节点添加新的 Watch(监视)值,您需要调用 CTrace::AddWatch 函数并使用 _WATCH 宏。
#define _WATCH(w, v) if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, string(v));
有关添加值的特别限制(与节点相同)是名称唯一性的控制。这意味着在将其添加到 CNode::m_watch[] 数组之前,将检查 Watch(监视)值的名称是否是唯一的。如果数组包含一个具有相同名称的值,则不会添加新的值,但会更新现有的值。
所有被跟踪的 Watch(监视)值都显示在信息窗口中。
- 代码的逐步式执行。
MQL5 提供的另一个便利功能是在代码执行期间在代码中强制中断的组织。
暂停是通过使用简单的无限循环 while (true) 实施的。在这里,MQL5 的方便性体现在对退出此循环的事件的处理 - 单击红色控制按钮。要在执行期间创建一个断点,使用 CTrace::Break 函数。
实施断点的函数
//------------------------------------------------------------------ Break void CTraceCtrl::Break() // checking the debug mode of a node { if(NIL(m_traceview)) return; // check of validity m_stack.BreakBy(TG_ALL,NULL,false); // removed the m_break flags from all nodes m_cur.m_break=true; // activated only at the current one m_traceview.m_viewstack.m_sid=m_cur.m_id; // moved selection to it m_stack.ExpandBy(TG_UP,m_cur,true,m_cur); // expand parent node if they are closed m_traceview.Draw(); // drew everything string name=m_traceview.m_viewstack.m_name+string(m_cur.m_id)+".dbg"; // got name of the BREAK button bool state=ObjectGetInteger(m_traceview.m_chart,name,OBJPROP_STATE); while(!state) // button is not pressed, execute the loop { Sleep(1000); // made a pause state=ObjectGetInteger(m_traceview.m_chart,name,OBJPROP_STATE); // check its state if(!m_traceview.IsOpenView()) break; // if the window is closed, exit m_traceview.Draw(); // drew possible changes } m_cur.m_break=false; // removed the flag m_traceview.Draw(); // drew the update }
遇到此类断点时,栈树进行同步处理以显示调用这个宏的函数。如果一个节点关闭,则父节点将展开以显示该节点。如有必要,可向上或向下滚动树以让节点处于可视区域内。
要退出 CTraceCtrl::Break,单击节点名称旁边的红色按钮。
总结
现在,我们有一个有趣的“玩具”了。在撰写本文时,我已经尝试过使用 CTraceCtrl 的很多方式,并且确定 MQL5 对控制 EA 交易及组织它们的运行有独特的视角。用于开发跟踪器的所有功能都不可以在 MQL4 中使用,这再一次证明了 MQL5 的优点及其广泛的可能性。
在附带的代码中,您可以找到本文描述的所有类以及服务库(它们的最低要求的集合,因为它们不是目标)。此外,我还附带了现成的例子 - 更新后的标准库文件,在其中添加了 _IN 宏。所有试验都使用包含在 MetaTrader 5 标准交付件 MACD Sample.mq5 中的 EA 交易进行。
本文由MetaQuotes Ltd译自俄文
原文地址: https://www.mql5.com/ru/articles/272
注意: MetaQuotes Ltd.将保留所有关于这些材料的权利。全部或部分复制或者转载这些材料将被禁止。
This article was written by a user of the site and reflects their personal views. MetaQuotes Ltd is not responsible for the accuracy of the information presented, nor for any consequences resulting from the use of the solutions, strategies or recommendations described.




我想是的。但通常情况下,脚本中的代码分支不多(当然,除非脚本处于循环中)。
此外,还有一个不便之处--脚本不处理 OnChartEvent 事件。
如果我的脚本使用了许多不同的类,类的层次结构如何?
我认为也有必要改进脚本工具......
CTraceView 类并不关心是谁调用了它,它会生成一棵树并显示出来。
但脚本有一个无法解决的反馈问题。你将无法主动处理树。
亲爱的 sergeev,请帮我弄明白!
我甚至无法重现示例中的 EA 树,我做错了什么?它应该显示出来的:(
我把 mql5-3.zip(最后一个)解压缩,把 MQH 文件夹放到 include\expert\ - 指标放到 Indicators,EXPERT(示例)放到 Expert 文件夹。
是的,在对象中我放了 <Trace>。
我修正了所有路径,编译后一切正常。
但是,我把指标放到图表上,窗口 没有打开;我把专家放到图表上, 在其属性中没有 "是,确定 "按钮,只有 "取消和重置"。
谢谢。
此外--我在图表上 抛出指标--窗口 没有打开;我抛出智能交易系统--然后在其属性中没有 "是,确定 "按钮,只有 "取消和重置"。
您不需要在任何地方抛出指标,"智能交易系统 "会自己抛出指标。
2. 阅读手册。
新文章《源代码的跟踪、调试和结构分析》已发布:
作者:o_Omp.5