本文介绍在执行期间创建一个调用栈的方法之一。本文介绍了以下功能:
选择一种常见方法作为表示结构的方法 – 以树的形式显示。为此,我们需要两个信息类。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 };
此类负责创建栈树。
图形的形成是使用两个 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 容器执行的。
#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 交易进行。
本社区仅针对特定人员开放
查看需注册登录并通过风险意识测评
5秒后跳转登录页面...
移动端课程