Rastreamento, Depuração e Análise Estrutural de Código Fonte
Introdução
Esse artigo fala sobre um dos métodos de criação de uma pilha de chamadas durante a execução. As seguintes características estão descritas neste artigo:
- Fazer a estrutura das classes usadas, funções e pastas.
- Fazer a pilha de ligação mantendo todas as pilhas anteriores. Sequência de chamada.
- Visualizar o estado dos parâmetros de Observação durante a execução.
- Execução do código passo a passo.
- Agrupar e selecionar pilhas obtidas, recebendo "extrema" informação.
Principais Princípios de Desenvolvimento
Uma abordagem comum é escolhida como método de representação de estrutura - exibida na forma de uma árvore. Para esta finalidade, nós precisaremos de duas classes informacionais. CNode - um "nó" usado para escrever toda informação sobre uma pilha. CTreeCtrl - uma "árvore" que processa todos os nós. E o próprio rastreador - CTraceCtrl, usado para processar árvores.
As classes CNodeBase e CTreeBase descrevem propriedades e métodos básicos de trabalho com nós e árvores.
A classe herdada CNode estende a funcionalidade básica da CNodeBase, e a classe CTreeBase trabalha com a classe derivada CNode. Isso é feito devido à classe CNodeBase ser o pai de outros nós padrão, e é isolado como uma classe independente por conveniência da hierarquia e herança.
Ao contrário CTreeNode da biblioteca padrão, a classe CNodeBase contém um arranjo de indicadores para nós, deste modo o numero de "ramos" que saem deste nó é ilimitado.
As classes 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); };
Você pode encontrar a implementação de todas as classes nas pastas anexadas. Nesse artigos, somente vamos mostrar seus cabeçalhos e importantes funções.
De acordo com a classificação aceitada, CTreeBase representa e orienta o gráfico acíclico. A classe derivada CTreeCtrl usa CNode e serve toda sua funcionalidade: adicionando alterando e excluindo os nós CNode.
CTreeCtrl e CNode podem substituir as correspondentes classes da biblioteca padrão com sucesso, já que eles tem uma funcionalidade um pouco mais ampla.
As classes 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 };
A arquitetura termina com duas classes: CTraceCtrl - sua única instância é usada para rastreamento, a mesma contém três instâncias da classe CTreeCtrl para criação da estrutura requirida das funções; e um container temporário - a classe CIn. Essa é só uma classe auxiliar que é usada para acrescentar novos nós a CTraceCtrl.
As classes 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 };
Modelo de Operação da Classe Cln
Essa classe é responsável pela criação da árvore de pilha.
Formação de gráfico é executada passo-a-passo em duas etapas usando duas funções CTraceCtrl:
void In(string afile, int aline, string aname, int aid); // entering a specified node void Out(int aid); // exit before a specified node
Em outras palavras, para formar uma árvore, contínuas chamadas In-Out-In-Out-In-In-Out-Out, etc. são realizadas.
O par In-Out funciona da seguinte maneira:
1. Introduzindo um bloco (função, ciclo, condição, etc.), i.e. logo após o colchete"{".
Quando introduzir o bloco, uma nova instância do CIn é criada, ele obtém o CTraceCtrl atual que já foi iniciado com alguns nós anteriores. A função CTraceCtrl::In é chamada CIn, a qual cria um novo nó na pilha. O nó é criado sob o atual nó CTraceCtrl::m_cur. Toda a atual informação sobre introduzir esta escrito aqui: nome do arquivo, número da fileira, nome da classe, funções, tempo atual, etc.
2. Sair do bloco quando encontrar um "}" colchete.
Quando sair de um bloco, o MQL automaticamente chama o destruidor CIn::~CIn. CTraceCtrl::Out é chamado no destruidor. O indicador no nó atual CTraceCtrl::m_cur é elevado a um nível acima na árvore. Neste ponto o destruidor não é chamado pelo novo nó, o nó continua na árvore.
Modelo de Criação de uma Pilha
Formação de uma pilha de chamada na forma de uma árvore com o preenchimento de toda informação sobre uma chamada é realizada usando o container CIn.
Macros para Fazer Chamadas Mais Fácil
Para evitar reescrever as longas linhas do código de criação do objeto CIn e introduzir um nó no seu código, é conveniente substitui-lo com a chamada do macro:#define _IN CIn _in; _in.In(__FILE__, __LINE__, __FUNCTION__)
Como você vê, o objeto CIn está criado e então nós introduzimos o nó.
Já que MQL dá um aviso no caso de nomes de variáveis locais ser as mesmas das variáveis globais, é melhor (mais preciso e claro) criar 3-4 definições análogas com outros nomes das variáveis da seguinte maneira:
#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 você vai mais fundo até os sub-blocos, utilize os próximos 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());
Com o aparecimento de macros na versão 411, você pode usar totalmente a passagem de parâmetros utilizando o #define.
Por isso que na classe CTraceCtrl você encontrará a seguinte definição macro:
#define NIL(p) (CheckPointer(p)==POINTER_INVALID)
Ele permite encurtar a verificação de validade do indicador.
Por exemplo, a linha:
if (CheckPointer(m_tree))==POINTER_INVALID || CheckPointer(m_cur))==POINTER_INVALID) return;
é substituído com a menor variante:
if (NIL(m_tree) || NIL(m_cur)) return;
Preparando Suas Pastas Para Rastreamento
Para controlar e obter a pilha, você precisa seguir três passos.
1. Adicionar as pastas requiridas#include <Trace.mqh>
Toda a biblioteca padrão é baseada na classe CObject no momento. Assim, se isso também é usado como uma classe base nas suas pastas, é o suficiente para adicionar Trace.mqh apenas a Object.mqh.
2. Coloque o _IN macros nos blocos requeridos (você pode usar procurar/substituir)
O exemplo de utilização do _IN macro: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. Nas funções OnInit, OnTime, e OnDeinit consistem o módulo principal do programa, adicionar a criação, modificar e deletar do objeto global CTraceCtrl respectivamente. Abaixo você pode encontrar o código já pronto para inserção:
Incorporando o rastreador no 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… }
Exibindo Classes de Rastreamento
Então, a pilha tem sido organizada. Agora vamos considerar a exibição de informações obtidas.
Para esta finalidade, nós devemos criar duas classes. CTreeView – para exibir a árvore, e CTraceView – para controlar a exibição das árvores e informação adicional sobre pilha. Ambas classes são derivadas da classe base CView.
As classes 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 };
Nós escolhemos exibir a pilha em uma sub janela separada como uma variante otimizada.
Em outras palavras, quando a classe CTraceView é criada na funçãoCTraceView::Create, a janela da tabela é criada e todos os objetos são desenhados nela, apesar do fato que o CTraceView está criado e funciona com o consultor especialista em outra janela. é feito para evitar a operação iminente do código fonte do programa rastreado e a exibição de sua própria informação no gráfico pela grande quantidade de informação.
Mas, para fazer a interação entre duas janelas possíveis, nós precisamos adicionar um indicador para a janela, o qual irá enviar todos os eventos do usuário para a janela base com o programa rastreado.
O indicador é criado na mesma função CTraceView::Create. Tendo apenas um parâmetro externo - o ID da tabela para o qual deve-se enviar todos os eventos.
O 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); }
Em resultado, temos uma representação bem estruturada da pilha.
Na árvore TRACE mostrada à esquerda, a pilha inicial é exibida.
Abaixo dele, está a janela INFO contendo informações detalhadas sobre o nó selecionado (CTraceView::OnChartEvent nesse exemplo). Duas janelas adjacentes contendo árvores exibe a mesma pilha, mas é agrupado por classes (a árvore CLASS no meio) e por arquivos (a árvore FILE para a direita).
As árvores de classes e arquivos tem o mecanismo incorporado de sincronização com a árvore principal da pilha, assim como meios convenientes de controle. Por exemplo, quando você clica no nome de uma classe na árvore de classes, todas as funções dessa classe são selecionadas na árvore de pilha e na árvore de arquivos. Da mesma maneira, quando você clica no nome de um arquivo, todas as funções e classes daquele arquivo são selecionadas.
Características de Trabalhar com a Pilha
- Adicionando Parâmetros de Observação
Como você já tem notado, os parâmetros do nó CNode incluem o arranjo de estruturas tagWatch. é criado somente para a conveniência de representação de informação. O mesmo contém um valor nomeado de uma variável ou expressão.
Estrutura de um Valor de Observação
//------------------------------------------------------------------ struct tagWatch struct tagWatch { string m_name; // name string m_val; // value };
Para adicionar um valor de Observação para o nó atual, você precisa chamar a função CTrace::AddWatch e usar _WATCH macro.
#define _WATCH(w, v) if (!NIL(m_trace) && !NIL(m_trace.m_cur)) m_trace.m_cur.AddWatch(w, string(v));
A particular limitação nos valores acrescentados (o mesmo que com os nós) é o controle da singularidade de nomes. Isso significa que o nome de um valor de Observação é verificado se é singular antes de ser adicionado ao arranjo CNode::m_watch[]. Se o arranjo contém um valor com o mesmo nome, o novo não será adicionado, mas o valor do existente será atualizado.
Todos os valores de Observação rastreados estão exibidos na janela de informação.
- Execução do código passo a passo.
Outro recurso conveniente dado pelo MQL5 é a organização de uma pausa forçada no código durante sua execução.
A pausa é implementada usando um ciclo infinito simples enquanto (verdadeiro). A conveniência do MQL5 aqui é como lidar com o evento de saída deste ciclo - clicando no botão vermelho de controle. Para criar um ponto de parada durante a execução, use a função CTrace::Break.
A Função de Implementação de Pontos de Parada
//------------------------------------------------------------------ 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 encontra tal ponto de parada, as árvores de pilhas são sincronizadas para exibir a função que chama esse macro. Se um nó é fechado o nó pai será expandido para exibí-lo. E, se necessário, a árvore é deslocada para cima ou para baixo para trazer o nó para uma área visível.
Para sair do CTraceCtrl::Break, clique no botão vermelho localizado perto do nome do nó.
Conclusão
Bom, nós temos um "brinquedo" interessante agora. Enquanto escrevi esse artigo, eu tentei muitas variantes de trabalho com CTraceCtrl e me certifiquei que MQL5 tem perspectivas únicas de controlar consultores especialistas e organizar sua operação. Todos os recursos usados para desenvolver o rastreador não estão disponíveis no MQL4, oque prova as vantagens do MQL5 e suas amplas possibilidades mais uma vez.
No código anexado, você pode encontrar todas as classes descritas nesse artigo junto com as bibliotecas de serviço (conjunto mínimo necessário deles, já que não são o conjunto alvo deles). Além disso, eu anexei o exemplo já pronto - arquivos atualizados da biblioteca padrão onde o _IN macros são colocados. Todos os experimentos foram conduzidos com o consultor especialista incluso na distribuição padrão de MetaTrader 5 - MACD Sample.mq5.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/272
- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Novo artigo Rastreamento, Depuração e Análise Estrutural de Código Fonte foi publicado:
Autor: o_Omp.5