Rastreo, Depuración y Análisis Estructural de Código Fuente
Introducción
Este artículo trata sobre uno de los métodos de creación de una serie de llamadas durante la ejecución. En el artículo se explicarán las siguientes prestaciones:
- Creación de estructura de clases, funciones y artículos usados.
- Realización de la serie de llamadas manteniendo todas las series anteriores. Secuencia de llamada.
- Visualización del estado de los parámetros Watch durante la ejecución.
- Ejecución por pasos del código.
- Agrupación y distribución de series obtenidas, obteniendo información "extrema".
Principios de Desarrollo
Hemos elegido un enfoque frecuente como método para la representación de la estructura, mostrándola en forma de árbol. Para ello, necesitaremos dos clases informacionales. CNode - un "nodo" usado para escribir toda la información sobre una serie. CTreeCtrl - un "árbol" que procesa todos los nodos. Y el rastreador mismo - CTraceCtrl, usado para procesar árboles.
Las clases CNodeBase y CTreeBase describen las propiedades y métodos básicos para trabajar con nodos y árboles.
La clase heredada CNode extiende la funcionalidad básica de CNodeBase, y la clase CTreeBase funciona con la clase derivada CNode. Se hace así porque la clase CNodeBase es la progenitora de los otros nodos estándar, y está aislada como una clase independiente para la conveniencia de la jerarquía y herencia.
A diferencia de CTreeNode de la biblioteca estándar, la clase CNodeBase contiene un array de punteros para nodos, por ello el número de "ramas" que salen de este nodo es ilimitado.
Las Clases CNodeBase y 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); };
Puede encontrar las implementaciones de todas las clases en los archivos adjuntos. En el artículo mostraremos solo sus encabezamientos y funciones importantes.
Según la clasificación aceptada, CTreeBase representa y orienta el gráfico acíclico. La clase derivada CTreeCtrl usa CNode y facilita toda su funcionalidad: añadir, cambiar y eliminar los nodos de CNode.
CTreeCtrl y CNode puede sustituir efectivamente las clases correspondientes de la biblioteca estándar, puesto que tienen una funcionalidad ligeramente mayor.
Las Clases CTreeBase y 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 };
La arquitectura acaba con dos clases: CTraceCtrl - su única instancia se usa directamente para rastrear; contiene tres instancias de la clase CTreeCtrl para la creación de la estructura requerida de funciones y un contenedor temporal, la clase CIn. Esta es simplemente una clase auxiliar usada para añadir nuevos nodos a CTraceCtrl.
Las Clases CTraceCtrl y 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 };
Modelo de Operación de la Clase CIn
Esta clase se encarga de la creación del árbol de series.
La formación del gráfico se lleva a cabo por pasos en dos fases usando dos funciones CTraceCtrl:
void In(string afile, int aline, string aname, int aid); // entering a specified node void Out(int aid); // exit before a specified node
En otras palabras, para formar un árbol se llevan a cabo llamadas continuas a In-Out-In-Out-In-In-Out-Out, etc.
El par In-Out funciona así:
1. Introducir un bloque (función, ciclo, condición, etc.), es decir, justo después del signo "{".
Al introducir el bloque se crea una nueva estancia CIn, y obtiene el CTraceCtrl actual que ya se inició con algunos nodos anteriores. La función CTraceCtrl::In se llama en CIn, y crea un nuevo nodo en la serie. El nodo se crea bajo el nodo actual CTraceCtrl::m_cur. Toda la información actual sobre la introducción se escribe en él: nombre de archivo, número de fila, nombre de clase, funciones, hora actual, etc.
2. Salir del bloque al encontrar un signo "}".
Al salir del bloque, MQL llama automáticamente al destructor CIn::~CIn. En el destructor se llama a CTraceCtrl::Out. El puntero del nodo actual CTraceCtrl::m_cur se sube a un nivel más alto en el árbol. Si el destructor no se llama para el nuevo nodo, el nodo se mantiene en el árbol.
Esquema de Formación de Serie
La formación de la serie de llamadas en forma de árbol llenando toda la información sobre una llamada se lleva a cabo usando el contenedor CIn.
Macros para Hacer las Llamadas más Fáciles
Para evitar reescribir las largas líneas de código para crear el objeto CIn e introducir un nodo en su código, es conveniente sustituirlo con la llamada al macro:#define _IN CIn _in; _in.In(__FILE__, __LINE__, __FUNCTION__)
Como puede ver, se crea el objeto CIn y después introducimos el código.
Puesto que MQL da una advertencia en caso de que los nombres de variables locales coincidan con las variables globales, es mejor (más preciso y claro) crear 3-4 definiciones análogas con los otros nombres de variables en la siguiente 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__)Conforme entre más profundamente en los sub-bloques, use los siguientes 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());
Con la aparición de macros en la versión 411, puede usar libremente la transferencia de parámetros usando #define.
Esta es la razón por la que en la clase CTraceCtrl encontrará la siguiente definición de macro:
#define NIL(p) (CheckPointer(p)==POINTER_INVALID)
Permite abreviar la comprobación de validez del puntero.
Por ejemplo, la línea:
if (CheckPointer(m_tree))==POINTER_INVALID || CheckPointer(m_cur))==POINTER_INVALID) return;
se sustituye con la variante más corta:
if (NIL(m_tree) || NIL(m_cur)) return;
Preparar sus Archivos para el Rastreo
Para controlar y obtener la serie, debe seguir tres pasos:
1. Añadir los archivos requeridos#include <Trace.mqh>
La biblioteca estándar entera se basa en la clase CObject por ahora. Por tanto, se usa como clase base en sus archivos, y es suficiente usar Trace.mqh solo a Object.mqh.
2. Coloque los macros _IN para los bloques requeridos (puede usar search/replace (buscar/sustituir))
Ejemplo de uso del 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. En las funciones OnInit, OnTime, y OnDeinit que suponen el módulo principal del programa, añada la creación, modificación y eliminación del objeto global CTraceCtrl respectivamente. Debajo puede encontrar el código ya preparado para su inserción:
Incrustar el rastreador en el código 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… }
Clases de Visualización de Rastreo
Así, hemos organizado la serie. Ahora consideremos la visualización de la información obtenida.
Para ello, debemos crear dos clases. CTreeView – para mostrar el árbol, y CTraceView – para controlar la visualización de árboles e información adicional sobre series. Ambas clases son derivadas de la clase baseCView.
Las Clases CTreeView y 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 };
Hemos elegido mostrar la serie en una subventana separada como variante óptima.
En otras palabras, cuando la clase CTraceView se crea en la función CTraceView::Create, la ventana del gráfico se crea y todos los objetos se dibujan en ella, a pesar del hecho de que CTraceView se crea y funciona en Asesor Experto (EA, por sus siglas en inglés) en otra ventana. Se hace así para evitar la obstaculización de operaciones del código fuente del programa rastreado y la visualización de su propia información en el gráfico a causa de la enorme cantidad de información.
Pero para hacer la interacción entre dos ventanas posible, necesitamos añadir un indicador a la ventana que enviará todos los eventos del usuario a la ventana base con el programa rastreado.
El indicador se crea con la misma función CTraceView::Create. Solo tiene un parámetro externo: el identificador del gráfico al que se deben enviar todos los eventos.
El Indicador 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); }
Como resultado tendremos una representación bastante estructurada de la serie.
En el árbol TRACE que aparece a la izquierda se mostrará la serie inicial.
Debajo de él se encuentra la ventana INFO con información detallada sobre el nodo seleccionado (CTraceView::OnChartEvent en este ejemplo). Dos ventanas adyacentes que contienen árboles muestran la misma serie, pero se agrupan por clases (el árbol CLASS en el medio) y por archivos (el árbol FILE a la derecha).
Los árboles de clases y archivos tienen el mecanismo incluido de sincronización con el árbol principal de la serie, así como medios de control convenientes. Por ejemplo, cuando hace click en un nombre de clase en el árbol de clases, se seleccionan todas las funciones de esta clase en el árbol de series y en el árbol de archivos. Y de la misma forma, cuando hace click en un nombre de archivo, se seleccionan todas las funciones y clases de ese archivo.
Prestaciones de Trabajo con la Serie
- Añadir Parámetros Watch
Como ya ha notado, los parámetros del nodo CNode incluyen el array de estructuras tagWatch. Se crea así simplemente por la conveniencia de representación de la información. Contiene un valor nombrado de una variable o expresión.
Estructura de un Valor Watch
//------------------------------------------------------------------ struct tagWatch struct tagWatch { string m_name; // name string m_val; // value };
Para añadir un nuevo valor Watch al nodo actual, debe llamar a la función CTrace::AddWatch y usar el macro _WATCH.
#define _WATCH(w, v) if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, string(v));
La limitación especial de valores añadidos (al igual que con los nodos) controla que los nombres sean únicos. Esto significa que que comprueba el nombre de un valor Watch para ver si está repetido antes de añadirlo al array CNode::m_watch[]. Si el array contiene un valor con el mismo nombre, el nuevo no se añadirá, pero el valor del ya existente se actualizará.
Todos los valores Watch se muestran en la ventana de información.
- Ejecución por pasos del código.
Otra conveniente prestación de MQL5 es la organización de una pausa forzada en el código durante su ejecución.
La pausa se implementa usando un bucle infinito simple durante (true). Lo más conveniente de MQL5 en este caso es la gestión del evento de salida de este bucle: haciendo click en el botón rojo de control. Para crear un punto de pausa durante la ejecución, use la función CTrace::Break.
La Función para la Implementación de Puntos de Pausa
//------------------------------------------------------------------ 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 }
Al encontrarse con un punto de pausa, los árboles de series están sincronizados para mostrar la función que llamó a este macro. Si se cierra un nodo, el nodo progenitor se expandirá para mostrarlo. Y si es necesario, el árbol se extenderá o retraerá para traer el nodo al área visible.
Para salir de CTraceCtrl::Break, haga click en el botón rojo localizado cerca del nombre del nodo.
Conclusión
Bueno, ahora tenemos un interesante "juguete". Al escribir este artículo, intenté diferentes variantes para trabajar con CTraceCtrl y me aseguré de que MQL5 tiene perspectivas únicas de controlar Asesores Expertos y organizar su cooperación. Ninguna de las prestaciones usadas para el desarrollo del rastreador están disponibles en MQL4, lo que una vez más prueba las ventajas de MQL5 y sus amplias posibilidades.
En el código adjunto puede encontrar todas las clases descritas en el artículo junto con las bibliotecas de servicio (el número mínimo requerido de ellas, puesto que no son el objetivo). Además, he adjuntado un ejemplo ya preparado - archivos actualizados de la biblioteca estándar donde se colocan los macros_IN. Todos los experimentos hechos con el Asesor Experto están incluidos en el archivo estándar de MetaTrader 5 - MACD Sample.mq5.
Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/272
- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso