
Traçage, Débogage et Analyse Structurelle du Code Source
Introduction
Cet article décrit l'une des méthodes de création d'une pile d'appels pendant l'exécution. Les caractéristiques suivantes sont décrites dans l'article :
- Création de la structure des classes, fonctions et fichiers utilisés.
- créer la pile d'appels en conservant toutes les piles précédentes. Séquence de les appeler.
- Affichage de l'état des paramètres de Veille pendant l'exécution.
- Exécution pas à pas du code.
- Regroupement et tri des piles obtenues, obtention d'informations "extrêmes".
Principes de Base d’Élaboration
Une approche commune est choisie comme méthode de représentation de la structure – l'affichage sous la forme d'un arbre. Pour cela, nous avons besoin de deux classes d'information. CNode- un «nœud» utilisé pour écrire toutes les informations sur une pile. CTreeCtrl - un «arbre» qui traite tous les nœuds. Et le traceur lui-mêmeCTraceCtrl -,utilisé pour le traitement des arbres.
Les classesCNodeBase et CTreeBase décrivent les propriétés de base et les méthodes de travail avec les nœuds et les arbores.
La classe héritéeCNode étend la fonctionnalité basique de CNodeBase, et la classe CTreeBase travaille avec la classe dérivée class CNode. Cela est dû au fait que la classeCNodeBase est le parent des autres nœuds standard et qu'elle est isolée en tant que classe indépendante pour la commodité de la hiérarchie et de l'héritage.
Contrairement àCTreeNode de la bibliothèque standard, la classe CNodeBase comporte un tableau de pointeurs vers des nœuds, ainsi le nombre de "branches" sortant de ce nœud est illimité.
Les classes CNodeBase et 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); };
Vous pouvez trouver l'implémentation de toutes les classes dans les fichiers joints. Dans l'article, nous allons uniquement indiquer leurs en-têtes et leurs fonctions importantes.
Selon la classification acceptéeCTreeBase, représente un graphe acyclique orienté. La classe dérivée CTreeCtrl utilise CNode sert toutes ses fonctionnalités: ajouter, modifier et supprimer les nœudsCNode.
CTreeCtrl et CNodepeuvent remplacer avec succès les classes correspondantes de la bibliothèque standard, car elles disposent d’ une fonctionnalité légèrement plus large.
Les classes CTreeBase et 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 };
L'architecture se termine par deux classes : CTraceCtrl - son unique instance est utilisée directement pour le traçage, elle comporte trois instances de la classeCTreeCtrl pour la création de la structure requise des fonctions ; et un conteneur temporaire - la classe CIn Il s'agit simplement d'une classe auxiliaire utilisée pour ajouter de nouveaux nœuds àCTraceCtrl.
Les classes CTraceCtrl et 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 };
Modèle de Fonctionnement de la Classe Cln
Cette classe est en charge de la création de l'arbre de la pile.
La formation du graphe s'effectue pas à pas en deux étapes à l'aide de deux fonctionsCTraceCtrl :
void In(string afile, int aline, string aname, int aid); // entering a specified node void Out(int aid); // exit before a specified node
En d'autres termes, pour former un arbre, des appels continus deIn-Out-In-Out-In-In-Out-Out,etc. sont effectués.
La paireIn-Out fonctionne de la manière suivante :
1. Saisie d'un bloc (fonction, cycle, condition...), c'est-à-dire juste après la parenthèse "{".
Lors de la saisie du bloc, une nouvelle instance de CIn est créée, elle obtient l’CTraceCtrlactuel qui est déjà mis en marche avec certains nœuds précédents. La fonctionCTraceCtrl::In est appelée dansCIn , elle crée un nouveau nœud dans la pile. Le nœud est créé sous le nœud courant CTraceCtrl::m_cur. Toutes les informations réelles sur la saisie y sont écrites : nom de fichier, numéro de ligne, nom de classe, fonctions, heure actuelle, etc.
2. Sortie du bloc lors de la rencontre d'un "}" crochet.
En sortant du bloc, MQL appelle automatiquement le destructeurCIn::~CIn. CTraceCtrl::Outest appelé dans le destructeur. Le pointeur de l’actuel nœud CTraceCtrl::m_cur est élevé d’un niveau supérieur dans l’arbre. A ce que le destructeur n'est pas appelé pour le nouveau nœud, le nœud reste dans l'arbre.
Schéma de Formation d'une Pile
La formation de la pile d'appels sous forme d'arbre avec le remplissage de toutes les informations sur un appel est réalisée à l'aide duCIn container.
Des macros pour Faciliter les Appels
Pour éviter de réécrire les longues lignes de code de création de l'objet CInet de saisie d'un nœud dans votre code, il est pratique de le remplacer par l'appel de la macro :#define _IN CIn _in; _in.In(__FILE__, __LINE__, __FUNCTION__)
Comme vous le constatez,l'objetCIn est créé et en suite, nous saisissons le nœud.
Puisque MQL donne un avertissement au cas où les noms des variables locales sont les mêmes que les variables globales, il est préférable (plus précis et clair) de créer 3-4 définitions analogues avec les autres noms de variables sous la forme suivante :
#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__)Au fur et à mesure que vous approfondissez vers les sous-blocs,utilisez les macros suivantes _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());
Avec l'apparition de macros dans la build 411,vous pouvez pleinement utiliser la transmission de paramètres à l'aide de#define.
C'est pourquoi dans la classeCTraceCtrl, vous trouverez la définition suivante de macro :
#define NIL(p) (CheckPointer(p)==POINTER_INVALID)
Il permet de raccourcir le contrôle de validité du pointeur.
Par exemple, la ligne :
if (CheckPointer(m_tree))==POINTER_INVALID || CheckPointer(m_cur))==POINTER_INVALID) return;
est remplacée par la variante plus courte :
if (NIL(m_tree) || NIL(m_cur)) return;
Préparation de Vos Fichiers pour le Traçage
Pour contrôler et obtenir la pile, vous devez suivre trois étapes.
1. Ajouter les fichiers requis#include <Trace.mqh>
Toute la bibliothèque standard est basée sur la classeCObjectpour le moment. Ainsi, si elle est également utilisée comme classe de base dans vos fichiers, il suffit d'ajouterTrace.mqh uniquement à Object.mqh
2. Placez_IN les macros dans les blocs requis (vous pouvez utiliser rechercher/remplacer)
L'exemple d'utilisation de la macro _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. Dans les fonctions OnInit, OnTime, et OnDeinit constituant le module principal du programme, ajoutez respectivement la création, la modification et la suppression de l'objet globalCTraceCtrl Vous trouverez ci-dessous le code prêt à l'emploi pour insertion :
Intégrer le traceur dans le code principal
//------------------------------------------------------------------ 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… }
Classes d'Affichage des Traces
Ainsi, la pile a été organisée. Examinons maintenant l'affichage des informations obtenues.
Pour cela, nous devons créer deux classes. CTreeView - pour l'affichage de l'arbre,et CTraceView - pour le contrôle de l'affichage des arbres et des informations supplémentaires sur la pile. Les deux classes sont dérivées de la classe de baseCView
Les classes CTreeView et 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 };
Nous avons choisi d'afficher la pile dans une sous-fenêtre séparée comme variante optimale.
En d'autres termes, lorsque la classe CTraceView est créée dans la fonctionCTraceView::Create, la fenêtre graphique est créée et tous les objets y sont dessinés, malgré le fait queCTraceView est créé et fonctionne dans Expert Advisor dans une autre fenêtre. C'est fait pour éviter d'entraver le fonctionnement du code source du programme tracé et l'affichage de ses propres informations sur le graphique par l'énorme quantité d'informations.
Mais pour rendre possible l'interaction entre deux fenêtres, nous devons ajouter un indicateur à fenêtre, qui enverra tous les événements de l'utilisateur à la fenêtre de base avec le programme tracé.
L'indicateur est créé dans la même fonctionCTraceView::Create. Il n'a qu'un seul paramètre externe - l' ID du graphique vers lequel il doit envoyer tous les événements.
L'indicateur 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); }
Au final, nous avons une représentation assez structurée de la pile.
Dans l'arbre TRACE affichée à gauche, la pile initiale est affichée.
En dessous se trouve la fenêtre INFO comprenant des informations détaillées sur le nœud sélectionné (CTraceView::OnChartEventdans cet exemple). Deux fenêtres adjacentes comportant des arbres affichent la même pile, mais elle est regroupée par classes (l'arbre CLASS au milieu) etpar fichiers (l'arbre FILE à droite).
Les arbres de classes et de fichiers ont le mécanisme intégré de synchronisation avec l'arbre principal de la pile ainsi que des moyens de contrôle pratiques. Par exemple, lorsque vous cliquez sur un nom de classe dans l'arbre des classes toutes les fonctions de cette classe sont sélectionnées dans l'arbre de pile et dans l'arbre de fichiers. Et de la même manière, lorsque vous cliquez sur un nom de fichier, toutes les fonctions et classes de ce fichier sont sélectionnées.
Fonctionnalités d’Opération avec la Pile
- Ajout de Paramètres de Surveillance
Comme vous l'avez déjà remarqué, les paramètres du nœud CNodecomportent le tableau de structures tagWatch. Il est créé uniquement pour la commodité de la représentation de l'information. EIle comporte une valeur nommée d'une variable ou d'une expression.
Structure d'une Valeur de Surveillance
//------------------------------------------------------------------ struct tagWatch struct tagWatch { string m_name; // name string m_val; // value };
Pour ajouter une nouvelle valeur de Surveillance au nœud actuel, vous devez appeler la fonctionCTrace::AddWatch et utiliser le macro _WATCH.
#define _WATCH(w, v) if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, string(v));
La limitation spéciale sur les valeurs ajoutées (la même qu'avec les nœuds) contrôle l'unicité des noms. Cela signifie que le nom d'une valeur Surveillance est vérifié pour son unicité avant d'être ajouté au tableau CNode::m_watch[] Si le tableau comporte une valeur du même nom, la nouvelle ne sera pas ajoutée, mais la valeur de l'existant sera mise à jour.
Toutes les valeurs de Surveillance suivies sont affichées dans la fenêtre d'information.
- Exécution pas à pas du code.
Une autre caractéristique pratique assurée par MQL5 est l'organisation d'une rupture forcée dans le code lors de son exécution.
La pause est implémentée à l'aide d'une simple boucle infiniewhile (true).. La commodité de MQL5 ici est la gestion de l'événement de sortie de cette boucle - en cliquant sur le bouton de contrôlerouge. Pour créer un point de rupture lors de l'exécution, utilisez la fonctionCTrace::Break.
La Fonction d’Implémentation des Points de Rupture
//------------------------------------------------------------------ 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 }
Lorsqu'elles rencontrent un tel point de rupture, les arbres de pile sont synchronisés pour afficher la fonction qui a appelé cette macro. Si un nœud est fermé, le nœud parent sera étendu pour l'afficher. Et si nécessaire, l'arbre défile vers le haut ou vers le bas pour amener le nœud dans la zone visible.
Pour quitterCTraceCtrl::Break ,cliquez sur le bouton rouge situé près du nom du nœud.
Conclusion
Eh bien, nous avons un "jouet" intéressant maintenant. Lors de la rédaction de cet article, j'ai essayé de nombreuses variantes de travail avecCTraceCtrl et j’ai veillé à ce que MQL5 dispose de perspectives uniques pour contrôler les Expert Advisors et organiser leur fonctionnement. Toutes les caractéristiques utilisées pour développer le traceur ne sont pas disponibles en MQL4, ce qui prouve une fois de plus les avantages du MQL5 et ses vastes possibilités.
Dans le code joint, vous pouvez trouver toutes les classes décrites dans l'article ainsi que les bibliothèques de services (l'ensemble minimum requis d'entre elles, car elles ne sont pas l’objectif). De plus, j'ai joint l'exemple prêt à l'emploi - les fichiers mis à jour de la bibliothèque standard où les _INmacros sont placées. Toutes les expériences ont été menées avec l'Expert Advisor inclus dans la livraison standard de MetaTrader 5-MACD Sample.mq5.
Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/272





- Applications de trading gratuites
- Plus de 8 000 signaux à copier
- Actualités économiques pour explorer les marchés financiers
Vous acceptez la politique du site Web et les conditions d'utilisation