ソースコードのトレーシング デバッギング 構造分析
--- | 29 10月, 2015
はじめに
この記事では、実行中にコールスタックを作成するメソッドの一つを紹介しています。以下の機能が述べられています。
- 使用済みクラス、関数、ファイルのストラクチャーの作成
- すべてのスタックを保持するコールスタックの作成それらを呼ぶ順序.
- 実行中の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 の3つのインスタンスを含みます。そして、一時的に保持するCInクラスこれは、新しいノードをCTraceCtrlに追加するために使用される補助的なクラスです。
CTraceCtrlとCIn Classes
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を取得します。CTraceCtrl::In関数は CIn内にて呼ばれ、スタック内に新しいノードを作成します。そのノードは、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__)サブブロックに深く進んでいった際には、macros _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());
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;
トレーシングのためにファイルを準備する
スタックを取得し、管理するために、3つのステップを踏まなければなりません。
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クラスがCTraceView::Create内の関数にて作成されたときに、CTraceViewが作成され、別のウィンドウにて動作するにも関わらずチャートウィンドウが作成され、すべてのオブジェクトがその中に描画されます。トレースされるコードの処理やチャート上に情報を表示することを妨害しないようにされます。
しかし、二つのウィンドウ間での連携を可能にするために、インジケーターをウィンドウに追加する必要です。それは、ユーザーのすべてのイベントをトレースされるプログラムを持つベースウィンドウへ送信します。
そのインジケーターは、同じ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ツリーでは、最初のスタックが表示されます。
選択したノードが(この例ではCTraceView::OnChartEvent)についての詳細な情報を含むINFOウィンドウが以下にあります。ツリーを持つ二つの隣接したウィンドウは同じスタックを表示しますが、(真ん中CLASSツリー)クラスごと、(右側FILEツリー)ファイルごとで 分類されています。
クラスとファイルのツリーは、便利な管理における手段とともに、主要なスタックのツリーとの同期化のためのメカニズムを持ちます。例えば、クラスツリーのクラス名をクリックすると、このクラスのすべての関数がスタックツリーとファイルツリーの中で選択されます。同様に、ファイル名をクリックすると、そのファイル内のすべての関数やクラスが選択されます。
スタックの使用特徴
- Watchパラメーターの追加
ご存知の通り、CNodeのパラメーターは、 tagWatchストラクチャーの配列を含んでいます。情報表示の利便性のためだけに作成されています。変数や式の名前の付けられた値を含みます。
Watchの値のストラクチャー
//------------------------------------------------------------------ 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));
追加された値に関する特別な制限は、名前の一意性を管理することにあります。Watchの値の名前は、 CNode::m_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がエキスパートアドバイザーを管理し、その処理を操作するユニークな特徴を持っていることを確認しました。トレーサーを開発するために使用されるすべての特徴は、MQL4では使用できません。これはMQL5のみの利点であることを示し、その可能性をまた広げるものです。
添付されたコードでは、すこの記事で紹介されていたすべてのクラスをサービスライブラリ(それらが目的ではないため最小限のみ)とともに見ることができます。さらに、すぐに使用できる、_INの場所にある標準ライブラリの更新済みファイルを添付しています。すべては、MACD Sample.mq5.- MetaTrader5の標準デリバリーに含まれるエキスパートアドバイザーにて行われています。