preview
Desenvolvendo um sistema de Replay (Parte 64): Dando play no serviço (V)

Desenvolvendo um sistema de Replay (Parte 64): Dando play no serviço (V)

MetaTrader 5Exemplos | 11 setembro 2024, 09:32
26 0
Daniel Jose
Daniel Jose

Introdução

No artigo anterior Desenvolvendo um sistema de Replay (Parte 63): Dando play no serviço (IV), foi feito a montagem e implementação de um mecanismo, que torna possível ao usuário controlar, por assim dizer, a quantidade máxima de ticks que deverão ser utilizados para plotagem da barra no gráfico. Apesar de este controle ter como finalidade permitir que a barra seja plotada sem atrapalhar outros pontos críticos da aplicação de replay/simulador. O mesmo não influencia, ou distorce os valores de volume que deverão ser de fato apresentados.

Mas se você se atentou ao vídeo presente naquele artigo, ou mesmo veio a compilar e testar a aplicação de replay/simulador, deve ter percebido, que de tempo em tempos, o sistema entrava em modo pausado. Estranhamente isto acontecia sem que o indicador de controle viesse a ter a sua indicação alterada. Mostrando desta forma, que teríamos de alguma forma, saído do modo play, para o modo pause. Ok, devo concordar que isto de fato é bastante estranho e suspeito. Você muito provavelmente, deve estar imaginando como isto poderia acontecer. De fato, me fiz o mesmo questionamento. Tentando entender por que razão o sistema entrava em modo pausado. E sempre em determinados pontos. Pois muito bem, explicar qual foi a solução que criei e como o problema pode ser entendido, é o tema do próximo tópico.


Entendendo e solucionando o modo pause automático

A grande questão e o maior aborrecimento aqui, não é de fato e nem de longe entender por que o replay/simulador estava entrando no modo pause de forma automática. O verdadeiro aborrecimento aqui, se dá pelo fato de ter entendido uma questão que muito provavelmente irá ser corrigida em breve pelos desenvolvedores do MetaTrader 5, mas que no momento em que escrevo este artigo, torna o uso de alguns testes e conceitos de utilização de alguns objetos gráficos uma tremenda de uma chateação. Isto pelo motivo de que os mesmos, podem vim algum de seus status alterados sem que você compreenda o real motivo.

Talvez eu esteja pegando um pouco pesado. Mas vamos relembrar como os objetos são importantes para nossa aplicação de replay/simulador e como os utilizamos de maneira a controlar as coisas. Isto para quem não está acompanhando esta sequência de artigos sobre o replay/simulador.

Quando você inicializa o serviço de replay/simulador. Este por sua vez abrirá um gráfico. Carregar os ticks presentes em um arquivo. Criar um ativo customizado e no final colocará no gráfico um indicador. Este indicador é o nosso indicador de controle, que serve justamente para isto: Controlar o que o replay/simulador estará de fato fazendo.

Neste momento, não fazemos mais uso de variáveis globais de terminal para acessar ou transferir informações entre o indicador de controle e o serviço de replay/simulador. Estamos fazendo uso de uma outra técnica, onde as informações fluem, entre os dois programas, de maneira que o usuário, não possa interferir nas informações que estão sendo transferidas.

Basicamente o serviço faz uso de eventos customizados a fim de transferir informações para dentro do indicador de controle. Já o indicador de controle, repassa parte das informações via buffer para dentro do serviço. Estou dizendo parte das informações, por conta que existe uma informação que é repassada de outra forma, a fim de não fazer o serviço ficar o tempo todo lendo o buffer. Já que a leitura do buffer, implica em transferir uma quantidade maior de informações do que realmente de fato precisamos. Por conta disto, fazemos com que o serviço acesse diretamente, mas sem modificar um objeto que é mantido pelo indicador de controle. Tal objeto é o botão que representa o status atual de execução, ou seja, o botão no qual o usuário informa se devemos estar em modo play ou modo pause.

Você pode ver isto, ao observar o código do arquivo C_Replay.mqh. Porém para melhor explicar isto, veja o fragmento abaixo, que pertence justamente a classe C_Replay.mqh.
35. //+------------------------------------------------------------------+
36. inline void UpdateIndicatorControl(void)
37.          {
38.             static bool bTest = false;
39.             double Buff[];
40.                                  
41.             if (m_IndControl.Handle == INVALID_HANDLE) return;
42.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
43.             {
44.                if (bTest)
45.                   m_IndControl.Mode = (ObjectGetInteger(m_Infos.IdReplay, def_ObjectCtrlName((C_Controls::eObjectControl)C_Controls::ePlay), OBJPROP_STATE) == 1  ? C_Controls::ePause : C_Controls::ePlay);
46.                else
47.                {
48.                   if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
49.                      m_IndControl.Memory.dValue = Buff[0];
50.                   if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
51.                      if (bTest = ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay))
52.                         m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
53.                }
54.             }else
55.             {
56.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
57.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
58.                m_IndControl.Memory._8b[7] = 'D';
59.                m_IndControl.Memory._8b[6] = 'M';
60.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
61.                bTest = false;
62.             }
63.          }
64. //+------------------------------------------------------------------+

Fragmento original do código fonte em C_Replay.mqh

Observe que na linha 38 temos uma variável estática, que serve justamente para informar ao procedimento, se podemos ou não ler o objeto diretamente. Ou se devemos ler buffer do indicador de controle. Quando na linha 60 enviamos um evento customizado, forçamos na linha 61, que a próxima chamada irá ler o buffer do indicador de controle. Assim esta leitura não é feita a todo momento, mas apenas de tempos em tempos, de forma bastante espaçada não afetando fortemente o serviço de replay/simulador. Mas o grande macete de fato, é que na linha 51 dizemos que as próximas leituras não serão via buffer e sim via acesso direto ao objeto gráfico. Porém isto somente ocorrerá se estivermos em modo play. Caso estejamos em modo pausado o tempo de resposta não é algo assim preocupante e crítico.

Então se estivermos em modo play, a partir da terceira chamada, executaremos a linha 45. Isto até que o modo pause seja de fato declarado pelo usuário. Quando isto ocorrer a função de LoopEventOnTime, que se encontra presente na classe C_Replay, irá ser encerrada. Mas como o usuário apenas informou que o serviço deverá entrar em modo pause, a função LoopEventOnTime será novamente chamada, fazendo com que o fragmento acima volte a observar o indicador de controle. Porém, ainda ficaremos observando o indicador pelo objeto gráfico. Aqui existe um problema que nos dificulta a fazer uma outra coisa. Mas não é isto que causa o modo pause automático. O modo pause não está sendo disparado aqui no serviço. Ele está sendo disparado no indicador de controle. E o motivo é exatamente o evento customizado presente na linha 60 do fragmento acima. Agora a coisa complicou de vez. Como algo, que estamos usando para disparar um evento customizado, está fazendo com que o serviço seja erroneamente informado pelo indicador de controle que o usuário acionou o modo pause ? Caramba, mas que coisa maluca. De fato, é algo muito bizarro. Mas de alguma forma, o evento customizado está fazendo com que o indicador mude o valor da propriedade OBJPROP_STATE do objeto que estamos observando no serviço. Quando esta propriedade mudar, a linha 45 no fragmento acima, fará como que o serviço seja falsamente informado que o indicador entrou em modo pause. Isto disparará a saída da função LoopEventOnTime, fazendo com que ela seja reinicializada. Porém quando esta função LoopEventOnTime, for verificar o valor da propriedade OBJPROP_STATE, ela irá ver um valor incorreto. Isto fará com que o serviço entre em modo pause, enquanto o indicador permanece mostrando ao usuário que o sistema está em execução normal, ou seja em modo play.

Muito bem. Se você entendeu de fato a falha, deve estar pensando que todo o problema se deve ao fato de que estamos observando um objeto presente no gráfico, em vez de olhar o conteúdo do buffer do indicador. Sim, concordo. Toda a falha se deve ao fato, de estarmos fazendo o serviço olhar o valor da propriedade OBJPROP_STATE de um objeto presente no gráfico, no qual o serviço de fato não deveria acessar. Mais uma vez e novamente concordo, mas isto não justifica o fato de que a propriedade OBJPROP_STATE, seja modificada por conta que um evento customizado foi disparado. Um erro não justifica o outro. De qualquer forma existem duas soluções mais diretas para este mesmo problema. Uma seria utilizar uma outra propriedade a fim de permitir que o serviço olhe, e apenas olhe o que um dado objeto no gráfico esteja fazendo. Apesar desta solução resolver o problema, não irei de fato fazer uso desta. O motivo é um outro problema que temos que corrigir, ou melhor dizendo algo que ainda precisamos implementar.

Mesmo que isto venha a nos custar um tempo um pouco maior para acessar o buffer do indicador, a olhar um objeto no gráfico. Irei de fato, olhar o buffer do indicador, isto por que implementarei algo que não se encontra atualmente implementado, mas já esteve em versões passadas deste serviço de replay/simulador. Tal recurso trata-se na verdade do avanço rápido. Assim depois de fazer a modificações e remover a variável estática da rotina a mesma ficou como pode ser visto no fragmento logo abaixo.

35. //+------------------------------------------------------------------+
36. inline void UpdateIndicatorControl(void)
37.          {
38.             double Buff[];
39.                                  
40.             if (m_IndControl.Handle == INVALID_HANDLE) return;
41.             if (m_IndControl.Memory._16b[C_Controls::eCtrlPosition] == m_IndControl.Position)
42.             {
43.                if (CopyBuffer(m_IndControl.Handle, 0, 0, 1, Buff) == 1)
44.                   m_IndControl.Memory.dValue = Buff[0];
45.                if ((C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus] != C_Controls::eTriState)
46.                   if ((m_IndControl.Mode = (C_Controls::eObjectControl)m_IndControl.Memory._16b[C_Controls::eCtrlStatus]) == C_Controls::ePlay)
47.                      m_IndControl.Position = m_IndControl.Memory._16b[C_Controls::eCtrlPosition];
48.             }else
49.             {
50.                m_IndControl.Memory._16b[C_Controls::eCtrlPosition] = m_IndControl.Position;
51.                m_IndControl.Memory._16b[C_Controls::eCtrlStatus] = (ushort)m_IndControl.Mode;
52.                m_IndControl.Memory._8b[7] = 'D';
53.                m_IndControl.Memory._8b[6] = 'M';
54.                EventChartCustom(m_Infos.IdReplay, evCtrlReplayInit, 0, m_IndControl.Memory.dValue, "");
55.             }
56.          }
57. //+------------------------------------------------------------------+

Fragmento modificado do código fonte em C_Replay.mqh

Apesar de todos os aborrecimentos, no final está será a melhor solução para nós. Mesmo que isto signifique que existe algo estranho e que algumas propriedades de determinados objetos não são de fato totalmente confiáveis. Pelo menos não até o momento em que escrevo este artigo. No entanto, se você ainda assim por qualquer motivo precisar fazer com que um serviço leia dados em algum objeto presente no gráfico. Aconselho você a usar a propriedade OBJPROP_TOOLTIP. Apesar de ser do tipo string, não encontrei problemas ao usá-la nos testes de transferência de informações, a fim de saber se o indicador de controle estaria no modo pause ou no modo play. Mas como foi dito a pouco, apesar de esta solução a fim de acessar o objeto fosse adequada, ela não iria nos permitir de fato implementar o avanço rápido. Seria necessário fazer diversas outras mudanças no código e no final teríamos que modificar o procedimento UpdateIndicadorControl de qualquer forma, a fim de que ele ficasse como mostrado.

Muito bem, agora que já resolvemos este percalço, vamos resolver um outro, igualmente bastante chato, e que foi mostrado em vídeo no artigo Desenvolvendo um sistema de Replay (Parte 62): Dando play no serviço (III). Mas para explicar como foi feita a solução daquele problema vamos para um novo tópico.


Resolvendo a falha de despejo de memória

Muito provavelmente muitos de você devem ter ficado espantados ao ver que a aplicação, de uma hora para outra, simplesmente estava falhando. Isto no momento que ela estava sendo encerrada por conta que o gráfico estava sendo fechado, ou já havia sido fechado. Um programador menos experiente, logo de cara começaria a dizer que a falha se deve por algo no MetaTrader 5, ou no MQL5. De fato, é mais simples jogar a culpa em outro, do que assumir o fardo e a responsabilidade de que o SEU programa está tendo um mal comportamento, ou no mínimo contém erros nos quais você ainda não se deu ao trabalho de corrigir ou verificar. Seja por não ter conhecimento e experiência suficiente para corrigir as falhas. Ou seja, pelo motivo que está focado em desenvolver algo específico antes de corrigir os erros. Muito bem, esperar que a plataforma faça tudo para você, é assinar um atestado de ignorância ou de inocência. Na verdade, ela, irá apenas fazer aquilo que ela deve realmente fazer. Depende de você, como programador, fazer todo o trabalho restante. Corrigindo falhas e fazer com que a sua aplicação funcione adequadamente.

Durante um bom tempo, eu estava negligenciando e postergando resolver alguns problemas e melhorar parte do código. Isto por estar envolvido no desenvolvimento de outras coisas. Não sabia se realmente seria possível chegar em um determinado ponto. Então você simplesmente não se preocupa em corrigir falhas, ou promover melhorias no código. O que você deseja é que ele funcione, ou tenha determinados tipos de recursos. Perder tempo resolvendo falhas, para no final ter que jogar todo o código corrigido fora, por que algo se mostrou inviável de ser feito ou mantido. Faz com que venhamos a ficar desmotivados. Então as correções e melhorias, somente acontecem quando o código se mostrou adequado, para poder receber a devida atenção.

Muitos talvez, estejam achando que estou fazendo rodeios em torno de algo. Eu poderia simplesmente mostrar um código muito mais avançado logo de início. Mas a verdade é que isto criaria uma falsa ilusão a quem está começando, de que o código simplesmente nasce pronto. Ou pior: Que ele deveria nascer já com todas as funcionalidades e funções. Quando na verdade, quem programa a anos, sabe que não é assim que as coisas acontecem. Quero mostrar como realmente o código se desenvolve e como vamos lidando, como programadores aos problemas que vão surgindo.

De qualquer forma, chegou a hora de mostrar como corrigir a falha, que se encontra presente no código a um bom tempo. Isto desde que começamos a fazer com que ele, trabalha-se de uma forma diferente. Se você não olhou o código tentando entender por que a falha está acontecendo, deve estar imaginando que ela esteja sendo causada por diversos defeitos. Mas não. Sinto lhe informar, que se você pensou assim, você de fato não está procurando estudar e entender os artigos. Está na verdade querendo apenas um código pronto e não está interessado em aprender a programar. E este é justamente o intuito de tais artigos, ensinar você caro leitor, algo que outro programador tenha testado ou esteja fazendo. A fim de que você aprenda novas técnicas ou venha a conhecer outras formas de se obter determinados resultados.

Mas chega de devaneios. Vamos então ver a solução do problema de despejo. A primeira coisa que você deve observar é o conteúdo das mensagens informadas pelo MetaTrader 5. Para facilitar, você pode ver um destes conteúdos na imagem logo abaixo.

Image 01

Imagem 01 - Visualizando as falhas indicadas pelo MetaTrader 5

Note que nesta imagem, existem duas linhas que foram destacadas. As demais linhas estão relacionadas a estas duas. Olhando com calma, aqui você pode ver do que se trata a tal falha. Ela está acontecendo por que alguns objetos não estão sendo removidos do gráfico. Aí você pode estar pensando: Mas como assim? Como os objetos não estão sendo removidos do gráfico? Por um acaso eu me esqueci de os remover? NÃO. Eles de fato estão sendo removidos e tal remoção acontece no destructor da classe. Você pode observar isto ao olhar o código do indicador de controle. Pois a falha está acontecendo justamente nele, como você pode ver na imagem acima.

Mas se os objetos estão sendo removidos. Por que o MetaTrader 5 está nos alertando sobre o fato de que existem objetos que não estão sendo removidos. E pior, os objetos fazem parte da classe C_DrawImage.

Ao ver as coisas assim, você aspirante a programador, pode ficar completamente sem saber o que fazer. Isto por conta do fato de que a classe C_DrawImage, não é de fato acessada diretamente, mas sim indiretamente. Para explicar isto melhor, vamos ver o código da classe C_Controls, que faz todo o trabalho de gerenciamento dos objetos. Um destes trabalhos é justamente o de acessar a classe C_DrawImage. Lembre-se: O MetaTrader 5 está nos alertando para uma falha, onde objetos do tipo C_DrawImage não estão sendo removido. Então a fonte do problema não está na classe C_DrawImagem, mas no código que faz uso da classe C_DrawImage, ou seja, a classe C_Controls.

Já que não é preciso ver o código completo, para entender o problema e compreender a solução. Abaixo você tem acesso aos trechos realmente importantes. Só que existe, um pequeno, detalhe: Neste código que você pode ver abaixo, já temos a solução do problema de despejo já implementado.

001. //+------------------------------------------------------------------+
002. #property copyright "Daniel Jose"
003. //+------------------------------------------------------------------+
004. #include "..\Auxiliar\C_DrawImage.mqh"
005. #include "..\Defines.mqh"
006. //+------------------------------------------------------------------+
007. #define def_PathBMP            "Images\\Market Replay\\Control\\"
008. #define def_ButtonPlay         def_PathBMP + "Play.bmp"
009. #define def_ButtonPause        def_PathBMP + "Pause.bmp"
010. #define def_ButtonLeft         def_PathBMP + "Left.bmp"
011. #define def_ButtonLeftBlock    def_PathBMP + "Left_Block.bmp"
012. #define def_ButtonRight        def_PathBMP + "Right.bmp"
013. #define def_ButtonRightBlock   def_PathBMP + "Right_Block.bmp"
014. #define def_ButtonPin          def_PathBMP + "Pin.bmp"
015. #resource "\\" + def_ButtonPlay
016. #resource "\\" + def_ButtonPause
017. #resource "\\" + def_ButtonLeft
018. #resource "\\" + def_ButtonLeftBlock
019. #resource "\\" + def_ButtonRight
020. #resource "\\" + def_ButtonRightBlock
021. #resource "\\" + def_ButtonPin
022. //+------------------------------------------------------------------+
023. #define def_ObjectCtrlName(A) "MarketReplayCTRL_" + (typename(A) == "enum eObjectControl" ? EnumToString((C_Controls::eObjectControl)(A)) : (string)(A))
024. #define def_PosXObjects         120
025. //+------------------------------------------------------------------+
026. #define def_SizeButtons         32
027. #define def_ColorFilter         0xFF00FF
028. //+------------------------------------------------------------------+
029. #include "..\Auxiliar\C_Mouse.mqh"
030. //+------------------------------------------------------------------+
031. class C_Controls : private C_Terminal
032. {
033.    protected:
034.    private   :
035. //+------------------------------------------------------------------+
036.       enum eMatrixControl {eCtrlPosition, eCtrlStatus};
037.       enum eObjectControl {ePause, ePlay, eLeft, eRight, ePin, eNull, eTriState = (def_MaxPosSlider + 1)};
038. //+------------------------------------------------------------------+
039.       struct st_00
040.       {
041.          string   szBarSlider,
042.                   szBarSliderBlock;
043.          ushort   Minimal;
044.       }m_Slider;
045.       struct st_01
046.       {
047.          C_DrawImage *Btn;
048.          bool         state;
049.          short        x, y, w, h;
050.       }m_Section[eObjectControl::eNull];
051.       C_Mouse   *m_MousePtr;
052. //+------------------------------------------------------------------+

              ...

071. //+------------------------------------------------------------------+
072.       void SetPlay(bool state)
073.          {
074.             if (m_Section[ePlay].Btn == NULL)
075.                m_Section[ePlay].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePlay), def_ColorFilter, "::" + def_ButtonPause, "::" + def_ButtonPlay);
076.             m_Section[ePlay].Btn.Paint(m_Section[ePlay].x, m_Section[ePlay].y, m_Section[ePlay].w, m_Section[ePlay].h, 20, (m_Section[ePlay].state = state) ? 1 : 0);
077.             if (!state) CreateCtrlSlider();
078.          }
079. //+------------------------------------------------------------------+
080.       void CreateCtrlSlider(void)
081.          {
082.             if (m_Section[ePin].Btn != NULL) return;
083.             CreteBarSlider(77, 436);
084.             m_Section[eLeft].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eLeft), def_ColorFilter, "::" + def_ButtonLeft, "::" + def_ButtonLeftBlock);
085.             m_Section[eRight].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(eRight), def_ColorFilter, "::" + def_ButtonRight, "::" + def_ButtonRightBlock);
086.             m_Section[ePin].Btn = new C_DrawImage(GetInfoTerminal().ID, 0, def_ObjectCtrlName(ePin), def_ColorFilter, "::" + def_ButtonPin);
087.             PositionPinSlider(m_Slider.Minimal);
088.          }
089. //+------------------------------------------------------------------+
090. inline void RemoveCtrlSlider(void)
091.          {         
092.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
093.             for (eObjectControl c0 = ePlay + 1; c0 < eNull; c0++)
094.             {
095.                delete m_Section[c0].Btn;
096.                m_Section[c0].Btn = NULL;
097.             }
098.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName("B"));
099.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
100.          }
101. //+------------------------------------------------------------------+

              ...

132. //+------------------------------------------------------------------+
133.    public   :
134. //+------------------------------------------------------------------+
135.       C_Controls(const long Arg0, const string szShortName, C_Mouse *MousePtr)
136.          :C_Terminal(Arg0),
137.           m_MousePtr(MousePtr)
138.          {
139.             if ((!IndicatorCheckPass(szShortName)) || (CheckPointer(m_MousePtr) == POINTER_INVALID)) SetUserError(C_Terminal::ERR_Unknown);
140.             if (_LastError != ERR_SUCCESS) return;
141.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, false);
142.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
143.             ChartSetInteger(GetInfoTerminal().ID, CHART_EVENT_OBJECT_DELETE, true);
144.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++)
145.             {
146.                m_Section[c0].h = m_Section[c0].w = def_SizeButtons;
147.                m_Section[c0].y = 25;
148.                m_Section[c0].Btn = NULL;
149.             }
150.             m_Section[ePlay].x = def_PosXObjects;
151.             m_Section[eLeft].x = m_Section[ePlay].x + 47;
152.             m_Section[eRight].x = m_Section[ePlay].x + 511;
153.             m_Slider.Minimal = eTriState;
154.          }
155. //+------------------------------------------------------------------+
156.       ~C_Controls()
157.          {
158.             for (eObjectControl c0 = ePlay; c0 < eNull; c0++) delete m_Section[c0].Btn;
159.             ObjectsDeleteAll(GetInfoTerminal().ID, def_ObjectCtrlName(""));
160.             delete m_MousePtr;
161.          }
162. //+------------------------------------------------------------------+

              ...

172. //+------------------------------------------------------------------+
173.       void DispatchMessage(const int id, const long &lparam, const double &dparam, const string &sparam)
174.          {
175.             short  x, y;
176.             static ushort iPinPosX = 0;
177.             static short six = -1, sps;
178.             uCast_Double info;
179.             
180.             switch (id)
181.             {
182.                case (CHARTEVENT_CUSTOM + evCtrlReplayInit):
183.                   info.dValue = dparam;
184.                   if ((info._8b[7] != 'D') || (info._8b[6] != 'M')) break;
185.                   iPinPosX = m_Slider.Minimal = (info._16b[eCtrlPosition] > def_MaxPosSlider ? def_MaxPosSlider : (info._16b[eCtrlPosition] < iPinPosX ? iPinPosX : info._16b[eCtrlPosition]));
186.                   SetPlay((eObjectControl)(info._16b[eCtrlStatus]) == ePlay);
187.                   break;
188.                case CHARTEVENT_OBJECT_DELETE:
189.                   if (StringSubstr(sparam, 0, StringLen(def_ObjectCtrlName(""))) == def_ObjectCtrlName(""))
190.                   {
191.                      if (sparam == def_ObjectCtrlName(ePlay))
192.                      {
193.                         delete m_Section[ePlay].Btn;
194.                         m_Section[ePlay].Btn = NULL;
195.                         SetPlay(m_Section[ePlay].state);
196.                      }else
197.                      {
198.                         RemoveCtrlSlider();
199.                         CreateCtrlSlider();
200.                      }
201.                   }
202.                   break;
203.                case CHARTEVENT_MOUSE_MOVE:
204.                   if ((*m_MousePtr).CheckClick(C_Mouse::eClickLeft))   switch (CheckPositionMouseClick(x, y))
205.                   {
206.                      case ePlay:
207.                         SetPlay(!m_Section[ePlay].state);
208.                         if (m_Section[ePlay].state)
209.                         {
210.                            RemoveCtrlSlider();
211.                            m_Slider.Minimal = iPinPosX;
212.                         }else CreateCtrlSlider();
213.                         break;

              ...

249. //+------------------------------------------------------------------+

Trechos do código fonte de C_Controls.mqh

Como falei, anteriormente, se você está começando no ramo de programação não irá de fato conseguir entender como a solução foi conseguida. Já que se você olhar rapidamente o código não conseguirá notar nenhuma diferença no mesmo. Isto por que a solução não é um código extenso, ou uma grande mudança no código da classe. A correção se deu ao adicionar uma única linha no código da classe. Isto mesmo que você acabou de ler. Para resolver o problema do despejo, foi preciso adicionar um código extremamente complexo e difícil de imaginar por quem esteja começando. Mas todo ele é composto de uma única linha, que qualquer um ao olhar a mesma conseguirá entender. Pois se trata de algo banal, mas se não estiver presente produzirá a tal falha de despejo. Informando que objetos pertencentes a classe C_DrawImage não estão sendo removidos da memória.

Então vamos começar entendendo, por que a falha está na classe C_Controls e não na classe C_DrawImage. Como falei acima, a classe C_DrawImage não é acessada diretamente, mas sim indiretamente. E isto por que o acesso, a classe C_DrawImage, não é feito por nada mais e nada menos do que um ponteiro. Este é declarado na linha 47. Então atenção: A classe C_DrawImage está sendo referenciada por um ponteiro. Mas como aqui no MQL5, ponteiros são ligeiramente diferentes do C / C++, você pode considerar a linha 47 como sendo uma variável. No entanto, ela é um ponteiro. E o motivo da falha não está em declarar a variável como ponteiro. Isto não é o motivo. Mas sim como estamos trabalhando com este ponteiro.

O grande problema de ponteiros e talvez este seja o motivo que aqui no MQL5, eles não são como no C / C++, é que uma falha contida neles pode muitas vezes serem muito difíceis de serem resolvidas. Algumas destas falhas são tão complicadas, que somente acontecem quando algumas interações acontecem, o que torna um pesadelo corrigir o problema. Já que você pode testar o programa centenas de vezes, mas em uma a interação acontece e muitas das vezes é justamente quando você não está debugando o programa. E isto é a pior coisa que pode acontecer.

Pois bem, como boa prática de programação, ao declaramos algo como sendo um ponteiro, ou que vá trabalhar como um. Devemos o quanto antes inicializar o mesmo. Mas isto também serve para variáveis no geral. Já que nosso código é uma classe, a inicialização deverá ser feita no constructor da classe. Isto de fato já estava acontecendo, e você pode ver isto olhando as linhas 144 a 149, mas para ser exato, o ponteiro é inicializado na linha 148. Note que o inicializamos com um valor NULL.

Agora vem a questão. O constructor da classe pode ser chamado de duas formas diferentes. Uma onde a classe está sendo referenciada como sendo uma variável normal. Neste caso não temos grandes problemas, pois o próprio compilador faz a reserva de memória a fim de que a classe trabalhe tranquilamente e de forma serena. A segunda forma, é quando a classe estará de alguma forma, sendo referenciada via ponteiros. Neste caso, você programador deverá chamar o constructor da classe via operador NEW e chamar o destructor da classe via operador DELETE.

Muito bem. Na linha 156 temos o destructor da classe C_Controls sendo implementado. Agora preste muita, mas muita atenção ao seguinte fato, pois, isto é, de suma importância para entender a falha. Quando o destructor, na linha 156 executa a linha 158 ele chamará o destructor da classe C_DrawImage, este poderá ou não existir. Mas o fato é que: Quando esta linha é executada, ela também liberará a memória que foi alocada pelo ponteiro. Se isto estiver ocorrendo, o MetaTrader 5 não gerará nenhum tipo de alerta por conta de falha no despejo da memória. Ou seja, a memória terá sido liberada sem falhas. No entanto, não é isto que está acontecendo. Então a pergunta é: POR QUE?

O motivo é que em algum ponto deste código visto acima, tem algo que está interferindo no trabalho do operador DELETE e a única coisa capaz de fazer isto é o operador NEW. Então por consequência a falha é justamente no operador NEW. Mas por que a falha se encontra nele? O motivo é que o operador NEW aqui no MQL5 deve estar trabalhando da mesma forma como faz no C / C++. Algumas linguagens de programação tem o operador NEW trabalhando de uma forma diferente. Nas não aqui no MQL5. Então vamos entender o que de fato está acontecendo.

Quando os objetos que serão sustentados pela classe C_DrawImage, precisam ser criados, você faz isto usando o operador NEW. Este tipo de coisa pode ser visto nas seguintes linhas:

  • Na linha 75, criamos o botão que representa a imagem de play e pause.
  • Nas linhas 84 a 86 os botões que serão usados no controle deslizante.

No entanto, em apenas dois pontos temos de fato a liberação da classe C_DrawImage. Estes pontos são: No destructor da classe C_Controls e na linha 95. Agora quero chamar a sua atenção ao seguinte fato: Na linha 96, imediatamente após liberar a memória via operador DELETE, temos a atribuição do valor NULL novamente ao ponteiro. Por que isto estava sendo feito? O motivo é que se você tentar referenciar uma região da memória sem que o ponteiro de fato esteja apontando para algo válido, poderá ler lixo, escrever em algo importante ou mesmo executar código malicioso. Por isto é uma boa prática em programação SEMPRE fazer com que ponteiros inválidos tenham um valor NULL. SEMPRE. No entanto, este código já existia e mesmo assim a falha acontecia.

Voltemos então aos pontos onde os operadores NEW estavam sendo acessados. A linha 74, já existia desde o começo da codificação desta classe. Isto faz com que somente quando o ponteiro não estiver em uso, venhamos a fazer com que ele aponte para algo. Então definitivamente a falha não está aqui. Mas, porém, toda via e, entretanto, a linha 82 NÃO EXISTIA ORIGINALMENTE. E por que esta linha não existia? Este é um motivo do qual eu não saberia explicar. Talvez por esquecimento. Talvez por não imaginar que ela de fato faria alguma diferença. Sinceramente não sei explicar. Mas é justamente a falta desta linha 82 é que fazia com que o MetaTrader 5 nos alerta-se para uma falha.

Não sei se você consegue de fato perceber o quanto, algo banal, um simples teste a fim de verificar se o ponteiro estava ou não ocupado pode fazer diferença. Talvez o motivo não tenha nem sido uma falha da minha parte, já que o procedimento CreateCtrlSlider somente iria ser chamado se RemoveCtrlSlider tivesse sido chamado antes. Porém existe um ponto e apenas e somente um único ponto onde isto não acontece. E este é justamente na linha 212. Esta linha é o nosso real problema, pois ela fará com que o operador NEW execute novamente chamadas ao constructor da classe C_DrawImage, alocando mais memória e duplicando as coisas. Mas a memória alocada anteriormente não é liberada e fica ali acumulando e tomando cada vez mais e mais espaço na memória. Até que você venha a encerrar a aplicação. Neste momento o MetaTrader 5 informará que existia uma falha na aplicação.


Conclusão

Como eu disse, algumas linguagens, tem um bloqueio que impede de você fazer um ponteiro já em uso apontar para outra coisa. Mas não é o caso, até o presente momento do MQL5 pelo que pude notar. O que estou fazendo aqui não mostrando uma brecha no MQL5. Mas sim, algo com o qual você deve ficar atento e tomar o devido cuidado ao programar. Mesmo que muitos dizem, afirmam e insistem em dizer que o MQL5 não contem ponteiros. Você pode ver que isto não é de todo verdade. E que sim, se você não tomar os devidos cuidados acabará, tendo sérios problemas, mesmo quando o programa é simples e aparentemente sem muitas dificuldades em termos de implementação.

Durante anos lidei com falhas como estas, onde um ponteiro simplesmente começava a ficar rebelde. E mesmo depois de tanto tempo e com a experiência adquirida por anos a fio. Ainda assim cai frente a uma falha de ponteiro. Justamente por conta de não fazer o teste que é visto na linha 82 que garante que não usaremos um ponteiro já ocupado. E de não ter prestado a devida atenção a chamada presente na linha 212, que por estar dentro de um evento do mouse irá ser disparada a cada movimento do mesmo quando as condições a permitirem fazer isto.

Então espero que este conhecimento lhe ajude e que lhe sirva de alerta. Nunca e jamais, subestime os ponteiros. Eles são muito úteis, mas podem nos dar muita dor de cabeça. No vídeo abaixo, você pode ver como o sistema ficou sem a falha.

Vídeo de demonstração

Arquivos anexados |
Anexo.zip (420.65 KB)
Redes neurais de maneira fácil (Parte 86): Transformador em forma de U Redes neurais de maneira fácil (Parte 86): Transformador em forma de U
Continuamos a analisar algoritmos de previsão de séries temporais. E neste artigo, proponho que você conheça o método U-shaped Transformer.
Desenvolvendo um EA multimoeda (Parte 8): Realizando testes de carga e processando um novo candle Desenvolvendo um EA multimoeda (Parte 8): Realizando testes de carga e processando um novo candle
À medida que avançamos, utilizamos cada vez mais instâncias simultâneas de estratégias de negociação em um único EA. Vamos descobrir até quantas instâncias podemos utilizar antes de nos depararmos com limitações de recursos.
Desenvolvendo um EA multimoeda (Parte 9): Coleta dos resultados de otimização de instâncias individuais da estratégia de trading Desenvolvendo um EA multimoeda (Parte 9): Coleta dos resultados de otimização de instâncias individuais da estratégia de trading
Vamos delinear as principais etapas para o desenvolvimento do nosso EA. Uma das primeiras será realizar a otimização de uma instância individual da estratégia de trading desenvolvida. Tentaremos reunir em um único lugar todas as informações necessárias sobre as execuções do testador durante a otimização.
Redes neurais de maneira fácil (Parte 85): previsão multidimensional de séries temporais Redes neurais de maneira fácil (Parte 85): previsão multidimensional de séries temporais
Neste artigo, quero apresentar a vocês um novo método abrangente de previsão de séries temporais, que combina harmoniosamente as vantagens dos modelos lineares e dos transformers.