请 [注册] 或 [登录]  | 返回主站

量化交易吧 /  量化策略 帖子:3364712 新帖:0

源代码的跟踪、调试和结构分析

我是游客发表于:4 月 17 日 18:21回复(1)

简介

本文介绍在执行期间创建一个调用栈的方法之一。本文介绍了以下功能:

  • 创建使用的类、函数和文件的结构。
  • 保持以前所有的栈创建调用栈。调用的顺序。
  • 执行期间查看 Watch(监视)参数的状态。
  • 代码的逐步式执行。
  • 分组和排列获得的栈,获取“极端”信息。


开发的主要原则

选择一种常见方法作为表示结构的方法 – 以树的形式显示。为此,我们需要两个信息类。CNode - 用于写入有关栈的所有信息的“节点”。CTreeCtrl - 处理所有节点的“树”。以及用于处理树的跟踪器本身 - CTraceCtrl

依据以下层次结构实施类:


CNodeBaseCTreeBase 类描述处理节点和树的基本属性和方法。

继承的类 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::~CInCTraceCtrl::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. 在构成程序的主要模块的 OnInitOnTimeOnDeinit 函数中,分别添加全局对象 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 交易进行。


全部回复

0/140

量化课程

    移动端课程