
Tracciamento, debug e analisi strutturale del codice sorgente
Introduzione
Questo articolo parla di uno dei metodi per creare un call stack durante l'esecuzione. Le seguenti caratteristiche sono descritte nell'articolo:
- Realizzare la struttura delle classi, delle funzioni e dei file utilizzati.
- Creare il call stack mantenendo tutti gli stack precedenti. Sequenza di chiamate.
- Visualizzazione dello stato dei parametri Watch durante l'esecuzione.
- Esecuzione graduale del codice.
- Raggruppare e ordinare gli stack ottenuti, ottenendo informazioni "estreme".
Principi fondamentali dello sviluppo
Viene scelto un approccio comune come metodo di rappresentazione della struttura – la visualizzazione sotto forma di albero. A tal fine, abbiamo bisogno di due classi informative. CNode - un "nodo" utilizzato per scrivere tutte le informazioni su uno stack. CTreeCtrl - un "albero" che elabora tutti i nodi. E il tracciatore - CTraceCtrl, utilizzato per l'elaborazione degli alberi.
Le classi CNodeBase e CTreeBase descrivono proprietà e metodi di base per lavorare con nodi e alberi.
La classe ereditata CNode estende le funzionalità di base di CNodeBase, mentre la classe CTreeBase funziona con la classe derivata CNode. È così perché la classe CNodeBase è il genitore degli altri nodi standard ed è isolata come classe indipendente per comodità della gerarchia e dell'ereditarietà.
A differenza di CTreeNode della libreria standard, la classe CNodeBase contiene un array di puntatori ai nodi, quindi il numero di "rami" che escono da questo nodo è illimitato.
Le classi CNodeBase e 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); };
Puoi trovare l'implementazione di tutte le classi nei file allegati. Nell'articolo, mostreremo solo le loro intestazioni e le funzioni importanti.
Secondo la classificazione accettata, CTreeBase rappresenta un grafico aciclico orientato. La classe derivata CTreeCtrl utilizza CNode e serve tutte le sue funzionalità: aggiunta, modifica ed eliminazione dei nodi CNode.
CTreeCtrl e CNode possono sostituire con successo le classi corrispondenti della libreria standard, poiché hanno una funzionalità leggermente più ampia.
Le classi CTreeBase e 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'architettura si conclude con due classi: CTraceCtrl - la sua unica istanza viene utilizzata direttamente per il tracciamento, contiene tre istanze della classe CTreeCtrl per la creazione della struttura richiesta delle funzioni; e un contenitore temporaneo - la classe CIn. Questa è solo una classe ausiliaria che viene utilizzata per aggiungere nuovi nodi a CTraceCtrl.
Le classi CTraceCtrl e 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 };
Modello di funzionamento della Classe CIn
Questa classe è responsabile della creazione dell'albero dello stack.
La formazione del grafico viene eseguita gradualmente in due fasi utilizzando due funzioni CTraceCtrl:
void In(string afile, int aline, string aname, int aid); // entering a specified node void Out(int aid); // exit before a specified node
In altre parole, per formare un albero si effettuano chiamate continue di In-Out-In-Out-In-In-Out-Out, ecc.
La coppia In-Out funziona in questo modo:
1. Inserimento di un blocco (funzione, ciclo, condizione, ecc.), ovvero subito dopo la parentesi "{".
Quando si entra nel blocco, viene creata una nuova istanza di CIn, la quale ottiene il CTraceCtrl corrente che è già stato avviato con alcuni nodi precedenti. La funzione CTraceCtrl::In viene chiamata in CIn e crea un nuovo nodo nello stack. Il nodo viene creato sotto il nodo corrente CTraceCtrl::m_cur. In esso sono scritte tutte le informazioni effettive sull'inserimento: nome del file, numero di riga, nome della classe, funzioni, ora corrente, ecc.
2. Uscita dal blocco quando si incontra una parentesi "}".
All'uscita dal blocco, MQL chiama automaticamente il distruttore CIn::~CIn. CTraceCtrl::Out viene chiamato nel distruttore. Il puntatore del nodo corrente CTraceCtrl::m_cur viene sollevato di un livello più in alto nell'albero. A quel punto, il distruttore non viene chiamato per il nuovo nodo e il nodo rimane nell'albero.
Schema di formazione di uno stack
La formazione del call stack sotto forma di albero con il riempimento di tutte le informazioni su una chiamata viene eseguita utilizzando il contenitore CIn.
Macro per semplificare le chiamate
Per evitare di riscrivere le lunghe righe di codice di creazione dell'oggetto CIn e di inserire un nodo nel codice, è conveniente sostituirlo con la chiamata della macro:#define _IN CIn _in; _in.In(__FILE__, __LINE__, __FUNCTION__)
Come vedete, viene creato l'oggetto CIn e quindi entriamo nel nodo.
Poiché MQL fornisce un avviso nel caso in cui i nomi delle variabili locali siano gli stessi delle variabili globali, è meglio (più preciso e chiaro) creare 3-4 definizioni analoghe con gli altri nomi di variabili nella seguente forma:
#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__)Man mano che approfondisci i sottoblocchi, usa le macro successive _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());
Con la comparsa delle macro nella versione 411, puoi utilizzare completamente il passaggio di parametri usando #define.
Ecco perché nella classe CTraceCtrl troverai la seguente definizione di macro:
#define NIL(p) (CheckPointer(p)==POINTER_INVALID)
Consente di abbreviare il controllo di validità del puntatore.
Ad esempio, la riga:
if (CheckPointer(m_tree))==POINTER_INVALID || CheckPointer(m_cur))==POINTER_INVALID) return;
viene sostituita con la variante più corta:
if (NIL(m_tree) || NIL(m_cur)) return;
Preparare i file per il tracciamento
Per controllare e ottenere lo stack, devi compiere tre passaggi.
1. Aggiungi i file richiesti#include <Trace.mqh>
L'intera libreria standard è attualmente basata sulla classe CObject. Quindi, se viene utilizzato anche come classe base nei tuoi file, è sufficiente aggiungere Trace.mqh solo a Object.mqh.
2. Posiziona le macro _IN nei blocchi richiesti (puoi usare cerca/sostituisci)
L'esempio dell'utilizzo della 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. Nelle funzioni OnInit, OnTime e OnDeinit, che costituiscono il modulo principale del programma, aggiungere rispettivamente la creazione, la modifica e la cancellazione dell'oggetto globale CTraceCtrl. Di seguito puoi trovare il codice già pronto per l'inserimento:
Incorporare il tracciatore nel codice principale
//------------------------------------------------------------------ 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… }
Classi di visualizzazione delle tracce
Quindi lo stack è stato organizzato. Consideriamo ora la visualizzazione delle informazioni ottenute.
A questo scopo, dobbiamo creare due classi. CTreeView per la visualizzazione dell'albero e CTraceView per il controllo della visualizzazione degli alberi e informazioni aggiuntive sullo stack. Entrambe le classi sono derivate dalla classe base CView.
Le classi CTreeView e 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 };
Abbiamo scelto di visualizzare lo stack in una sottofinestra separata come variante ottimale.
In altre parole, quando viene creata la classe CTraceView nella funzione CTraceView::Create, viene creata la finestra del grafico e tutti gli oggetti vengono disegnati in essa, nonostante il fatto che CTraceView venga creata e funzioni nell’Expert Advisor in un'altra finestra. È fatto per impedire che l'enorme quantità di informazioni impedisca il funzionamento del codice sorgente del programma tracciato e la visualizzazione delle proprie informazioni sul grafico.
Ma, per rendere possibile l'interazione tra due finestre, dobbiamo aggiungere un indicatore alla finestra, il quale invierà tutti gli eventi dell'utente alla finestra di base con il programma tracciato.
L'indicatore viene creato nella stessa funzione CTraceView::Create. Ha un solo parametro esterno: l'ID del grafico a cui deve inviare tutti gli eventi.
L'indicatore 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); }
Nel risultato, abbiamo una rappresentazione piuttosto strutturata dello stack.
Nell'albero TRACE, a sinistra, viene visualizzato lo stack iniziale.
Sotto c'è la finestra INFO contenente informazioni dettagliate sul nodo selezionato (CTraceView::OnChartEvent in questo esempio). Due finestre adiacenti contenenti degli alberi mostrano lo stesso stack, ma che è raggruppato per classi (l'albero CLASS al centro) e per file (l'albero FILE a destra).
Gli alberi delle classi e dei file hanno il meccanismo incorporato di sincronizzazione con l'albero principale dello stack oltre a comodi mezzi di controllo. Ad esempio, quando si fa clic sul nome di una classe nell'albero delle classi, tutte le funzioni di questa classe vengono selezionate nell'albero dello stack e nell'albero dei file. E allo stesso modo, quando fai clic sul nome di un file, vengono selezionate tutte le funzioni e le classi in quel file.
Caratteristiche del lavoro con lo stack
- Aggiunta di parametri Watch
Come avrai già notato, i parametri del nodo CNode includono l'array di strutture tagWatch. Viene creato solo per comodità di rappresentazione delle informazioni. Contiene un valore denominato di una variabile o di un'espressione.
Struttura di un valore Watch
//------------------------------------------------------------------ struct tagWatch struct tagWatch { string m_name; // name string m_val; // value };
Per aggiungere un nuovo valore Watch al nodo corrente, è necessario chiamare la funzione CTrace::AddWatch e utilizzare la macro _WATCH.
#define _WATCH(w, v) if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, string(v));
La limitazione speciale sui valori aggiunti (la stessa dei nodi) è il controllo dell'unicità dei nomi. Significa che il nome di un valore Watch viene controllato per l'unicità prima di essere aggiunto all'array CNode::m_watch[]. Se l'array contiene un valore con lo stesso nome, quello nuovo non verrà aggiunto, ma verrà aggiornato il valore di quello esistente.
Tutti i valori tracciati del Watch vengono visualizzati nella finestra delle informazioni.
- Esecuzione graduale del codice.
Un'altra comoda funzionalità data da MQL5 è l'organizzazione di un'interruzione forzata del codice durante la sua esecuzione.
La pausa viene implementata utilizzando un semplice ciclo infinito while (true). La comodità di MQL5 qui è data dalla gestione dell'evento di uscita da questo ciclo - cliccando il pulsante rosso di controllo. Per creare un punto di interruzione durante l'esecuzione, utilizzare la funzione CTrace::Break.
La funzione per l'implementazione dei punti di interruzione
//------------------------------------------------------------------ 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 }
Quando si incontra tale punto di interruzione, gli alberi dello stack vengono sincronizzati per visualizzare la funzione che ha chiamato questa macro. Se un nodo è chiuso, il nodo genitore verrà espanso per visualizzarlo. E se necessario, l'albero viene fatto scorrere verso l'alto o verso il basso per portare il nodo nell'area visibile.
Per uscire da CTraceCtrl::Break, cliccare sul pulsante rosso situato vicino al nome del nodo.
Conclusione
Bene, ora abbiamo un "giochino" interessante. Durante la stesura dell'articolo, ho provato molte varianti del lavoro con CTraceCtrl e mi sono assicurato che MQL5 avesse prospettive uniche di controllare gli Expert Advisor e di organizzare il loro funzionamento. Tutte le funzionalità utilizzate per lo sviluppo del tracciatore non sono disponibili in MQL4, il che dimostra ancora una volta i vantaggi di MQL5 e le sue ampie possibilità.
Nel codice allegato, puoi trovare tutte le classi descritte nell'articolo insieme alle librerie di servizi (il set minimo richiesto di esse, poiché non sono l'obiettivo). Inoltre, ho allegato l'esempio già pronto: file aggiornati della libreria standard in cui sono posizionate le macro _IN. Tutti gli esperimenti sono stati condotti con l'Expert Advisor incluso nella fornitura standard di MetaTrader 5 - MACD Sample.mq5.
Tradotto dal russo da MetaQuotes Ltd.
Articolo originale: https://www.mql5.com/ru/articles/272





- App di trading gratuite
- Oltre 8.000 segnali per il copy trading
- Notizie economiche per esplorare i mercati finanziari
Accetti la politica del sito e le condizioni d’uso