Enviar pedidos para o MT4 a partir de Java via IP - página 3

 
jjc:

... afirmando o óbvio, se você quisesse fazer o acima descrito em um EA em vez de um roteiro [...]

Versão revisada, que pode ser usada para criar um EA em vez de um script simplesmente des-comentando o #define COMPILE_AS_EA. O código então tem OnTimer (e OnTick) em vez de OnStart.

A única outra mudança é que o código agora coloca os soquetes no modo sem bloqueios. Ele ainda pesquisa a disponibilidade de novos eventos de soquetes usando select(), mas agora evita o perigo de entrar em um estado de bloqueio se o select() estiver de alguma forma errado.

#property strict 

// ---------------------------------------------------------------------
// This code can either create an EA or a script. To create an EA,
// remove the commenting-out of the line below. The code will then
// have OnTimer() and OnTick() instead of OnStart()
// ---------------------------------------------------------------------

// #define COMPILE_AS_EA


// ---------------------------------------------------------------------
// If we are compiling as a script, then we want the script
// to display the inputs page
// ---------------------------------------------------------------------

#ifndef  COMPILE_AS_EA
#property  show_inputs
#endif


// ---------------------------------------------------------------------
// User-configurable parameters
// ---------------------------------------------------------------------

input int PortNumber = 51234; // TCP/IP port number

// ---------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------

// Size of temporary buffer used to read from client sockets
#define  SOCKET_READ_BUFFER_SIZE  10000

// Defines the frequency of the main-loop processing, in milliseconds.
// This is either the duration of the sleep in OnStart(), if compiled
// as a script, or the frequency which is given to EventSetMillisecondTimer()
// if compiled as an EA
#define  SLEEP_MILLISECONDS       10


// ---------------------------------------------------------------------
// Forward definitions of classes
// ---------------------------------------------------------------------

// Wrapper around a connected client socket
class Connection;

// ---------------------------------------------------------------------
// Winsock structure definitions and DLL imports
// ---------------------------------------------------------------------

struct sockaddr_in {
   short af_family;
   short port;
   int addr;
   int dummy1;
   int dummy2;
};

struct timeval {
   int secs;
   int usecs;
};

struct fd_set {
   int count;
   int single_socket;
   int dummy[63];
};

#import "Ws2_32.dll"
   int socket(int, int, int);   
   int bind(int, sockaddr_in&, int);
   int htons(int);
   int listen(int, int);
   int accept(int, int, int);
   int closesocket(int);
   int select(int, fd_set&, int, int, timeval&);
   int recv(int, uchar&[], int, int);
   int ioctlsocket(int, uint, uint&);
   int WSAGetLastError();
#import   


// ---------------------------------------------------------------------
// Global variables
// ---------------------------------------------------------------------

// Flags whether OnInit was successful
bool SuccessfulInit = false;

// Handle of main listening server socket
int ServerSocket;

// List of currently connected clients
Connection * Clients[];

// For EA compilation only, we track whether we have 
// successfully created a timer
#ifdef  COMPILE_AS_EA
bool CreatedTimer = false;   
#endif

// ---------------------------------------------------------------------
// Initialisation - create listening socket
// ---------------------------------------------------------------------

void OnInit()
{
   SuccessfulInit = false;
   
   if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Print("Requires \'Allow DLL imports\'");return;}

   // (Don't need to call WSAStartup because MT4 must have done this)

   // Create the main server socket
   ServerSocket = socket(2 /* AF_INET */, 1 /* SOCK_STREAM */, 6 /* IPPROTO_TCP */);
   if (ServerSocket == -1) {Print("ERROR " , WSAGetLastError() , " in socket creation");return;}
   
   // Put the socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(ServerSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on server socket");return;}
   
   // Bind the socket to the specified port number. In this example,
   // we only accept connections from localhost
   sockaddr_in service;
   service.af_family = 2 /* AF_INET */;
   service.addr = 0x100007F; // equivalent to inet_addr("127.0.0.1")
   service.port = (short)htons(PortNumber);
   if (bind(ServerSocket, service, 16 /* sizeof(service) */) == -1) {Print("ERROR " , WSAGetLastError() , " in socket bind");return;}

   // Put the socket into listening mode
   if (listen(ServerSocket, 10) == -1) {Print("ERROR " , WSAGetLastError() , " in socket listen");return;}

   // Flag that we've successfully initialised
   SuccessfulInit = true;

   // If we're operating as an EA rather than a script, then try setting up a timer
   #ifdef  COMPILE_AS_EA
   CreateTimer();
   #endif   
}


// ---------------------------------------------------------------------
// Variation between EA and script, depending on COMPILE_AS_EA above.
// Both versions simply call MainLoop() to handle the socket activity.
// The script version simply does so from a loop in OnStart().
// The EA version sets up a timer, and then calls MainLoop from OnTimer()
// ---------------------------------------------------------------------

#ifdef  COMPILE_AS_EA
   // .........................................................
   // EA version. Simply calls MainLoop() from a timer.
   // The timer has previously been created in OnInit(), 
   // though we also check this in OnTick()...
   void OnTimer()
   {
      MainLoop();
   }
   
   // The function which creates the timer if it hasn't yet been set up
   void CreateTimer()
   {
      if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer(SLEEP_MILLISECONDS);
   }

   // Timer creation can sometimes fail in OnInit(), at least
   // up until MT4 build 970, if MT4 is starting up with the EA already
   // attached to a chart. Therefore, as a fallback, we also 
   // handle OnTick(), and thus eventually set up the timer in
   // the EA's first tick if we failed to set it up in OnInit()
   void OnTick()
   {
      CreateTimer();
   }

#else

   // .........................................................
   // Script version. Simply calls MainLoop() repeatedly
   // until the script is removed, with a small sleep between calls
   void OnStart()
   {
      while (!IsStopped()) {
         MainLoop();
         Sleep(SLEEP_MILLISECONDS);
      }
   }
#endif


// ---------------------------------------------------------------------
// Main processing loop which handles new incoming connections, and reads
// data from existing connections.
// Can either be called from OnStart() in a script, on a continuous loop 
// until IsStopped() is true; or from OnTimer() in an EA.
// ---------------------------------------------------------------------

void MainLoop()
{
   // Do nothing if init was unsuccessful
   if (!SuccessfulInit) return;
   
   // .........................................................
   // Do we have a new pending connection on the server socket?
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;
   
   fd_set PollServerSocket;
   PollServerSocket.count = 1;
   PollServerSocket.single_socket = ServerSocket;

   int selres = select(0, PollServerSocket, 0, 0, waitfor);
   if (selres > 0) {
   
      Print("New incoming connection...");
      int NewClientSocket = accept(ServerSocket, 0, 0);
      if (NewClientSocket == -1) {
         if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
            // Blocking warning; ignore
         } else {
            Print("ERROR " , WSAGetLastError() , " in socket accept");
         }

      } else {
         Print("...accepted");

         int ctarr = ArraySize(Clients);
         ArrayResize(Clients, ctarr + 1);
         Clients[ctarr] = new Connection(NewClientSocket);               
         Print("Got connection to client #", Clients[ctarr].GetID());
      }
   }

   // .........................................................
   // Process any incoming data from client connections
   // (including any which have just been accepted, above)
   int ctarr = ArraySize(Clients);
   for (int i = ctarr - 1; i >= 0; i--) {
      // Return value from ReadAnyPendingData() is true
      // if the socket still seems to be alive; false if 
      // the connection seems to have been closed, and should be discarded
      if (Clients[i].ReadAnyPendingData()) {
         // Socket still seems to be alive
         
      } else {
         // Socket appears to be dead. Delete, and remove from list
         Print("Lost connection to client #", Clients[i].GetID());
         
         delete Clients[i];
         for (int j = i + 1; j < ctarr; j++) {
            Clients[j - 1] = Clients[j];
         }
         ctarr--;
         ArrayResize(Clients, ctarr);           
      }
   }
}

// ---------------------------------------------------------------------
// Termination - clean up sockets
// ---------------------------------------------------------------------

void OnDeinit(const int reason)
{
   closesocket(ServerSocket);
   
   for (int i = 0; i < ArraySize(Clients); i++) {
      delete Clients[i];
   }
   ArrayResize(Clients, 0);
   
   #ifdef  COMPILE_AS_EA
   EventKillTimer();
   CreatedTimer = false;
   #endif
}


// ---------------------------------------------------------------------
// Simple wrapper around each connected client socket
// ---------------------------------------------------------------------

class Connection {
private:
   // Client socket handle
   int mSocket;

   // Temporary buffer used to handle incoming data
   uchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
   
   // Stored-up data, waiting for a \r character 
   string mPendingData;
   
public:
   Connection(int ClientSocket);
   ~Connection();
   string GetID() {return IntegerToString(mSocket);}
   
   bool ReadAnyPendingData();
   void ProcessIncomingMessage(string strMessage);
};

// Constructor, called with the handle of a newly accepted socket
Connection::Connection(int ClientSocket)
{
   mPendingData = "";
   mSocket = ClientSocket; 
   
   // Put the client socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(mSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on client socket");}
}

// Destructor. Simply close the client socket
Connection::~Connection()
{
   closesocket(mSocket);
}

// Called repeatedly on a timer, to check whether any
// data is available on this client connection. Returns true if the 
// client still seems to be connected (*not* if there's new data); 
// returns false if the connection seems to be dead. 
bool Connection::ReadAnyPendingData()
{
   // Check the client socket for data-readability
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;

   fd_set PollClientSocket;
   PollClientSocket.count = 1;
   PollClientSocket.single_socket = mSocket;

   int selres = select(0, PollClientSocket, 0, 0, waitfor);
   if (selres > 0) {
      
      // Winsock says that there is data waiting to be read on this socket
      int res = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0);
      if (res > 0) {
         // Convert the buffer to a string, and add it to any pending
         // data which we already have on this connection
         string strIncoming = CharArrayToString(mTempBuffer, 0, res);
         mPendingData += strIncoming;
         
         // Do we have a complete message (or more than one) ending in \r?
         int idxTerm = StringFind(mPendingData, "\r");
         while (idxTerm >= 0) {
            if (idxTerm > 0) {
               string strMsg = StringSubstr(mPendingData, 0, idxTerm);         
               
               // Strip out any \n characters lingering from \r\n combos
               StringReplace(strMsg, "\n", "");
               
               // Print the \r-terminated message in the log
               ProcessIncomingMessage(strMsg);
            }               
         
            // Keep looping until we have extracted all the \r delimited 
            // messages, and leave any residue in the pending data 
            mPendingData = StringSubstr(mPendingData, idxTerm + 1);
            idxTerm = StringFind(mPendingData, "\r");
         }
         
         return true;
      
      } else if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
         // Would block; not an error
         return true;
         
      } else {
         // recv() failed. Assume socket is dead
         return false;
      }
   
   } else if (selres == -1) {
      // Assume socket is dead
      return false;
      
   } else {
      // No pending data
      return true;
   }
}

// Can override this with whatever you want to do with incoming messages:
// handling trading commands, send data back down the socket etc etc etc
void Connection::ProcessIncomingMessage(string strMessage)
{
   Print("#" , GetID() , ": " , strMessage);
}
 
jjc:

Versão revisada que [...]

Estou certo de que todos estão ansiosos por uma atualização sobre este [...

No contexto de uma EA - que agora é o padrão no código - acontece que é possível usar o WSAAsyncSelect() para fazer o manuseio da atividade do soquete por eventos. Isto pode muitas vezes empurrar a latência entre o envio e a recepção de mensagens abaixo do nível de milissegundos. A velocidade exata varia dependendo do que mais o MT4 está fazendo, mas é em média muito mais rápido, e nunca pior, do que apenas usar o timer.

Não é possível conseguir que a WSAAsyncSelect() acione OnTimer() ou OnTick() no EA. Por exemplo, WSAAsyncSelect() pode ser dito para disparar mensagens WM_TIMER, mas MT4 as ignora porque o ID do timer wparam do WSAAsyncSelect() não corresponde ao ID do timer que espera do EventSetMillisecondTimer().

No entanto, dizer à WSAAsyncSelect() para enviar WM_KEYDOWN faz disparar com sucesso OnChartEvent() no EA. Veja os comentários no código para detalhes completos de como esta adição funciona.

#property strict 

// ---------------------------------------------------------------------
// This code can either create an EA or a script. To create a script,
// comment out the line below. The code will then have OnTimer() 
// and OnTick() instead of OnStart()
// ---------------------------------------------------------------------

#define  COMPILE_AS_EA


// ---------------------------------------------------------------------
// If we are compiling as a script, then we want the script
// to display the inputs page
// ---------------------------------------------------------------------

#ifndef  COMPILE_AS_EA
#property  show_inputs
#endif


// ---------------------------------------------------------------------
// User-configurable parameters
// ---------------------------------------------------------------------

input int PortNumber = 51234; // TCP/IP port number

// ---------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------

// Size of temporary buffer used to read from client sockets
#define  SOCKET_READ_BUFFER_SIZE  10000

// Defines the frequency of the main-loop processing, in milliseconds.
// This is either the duration of the sleep in OnStart(), if compiled
// as a script, or the frequency which is given to EventSetMillisecondTimer()
// if compiled as an EA
#define  SLEEP_MILLISECONDS       10


// ---------------------------------------------------------------------
// Forward definitions of classes
// ---------------------------------------------------------------------

// Wrapper around a connected client socket
class Connection;

// ---------------------------------------------------------------------
// Winsock structure definitions and DLL imports
// ---------------------------------------------------------------------

struct sockaddr_in {
   short af_family;
   short port;
   int addr;
   int dummy1;
   int dummy2;
};

struct timeval {
   int secs;
   int usecs;
};

struct fd_set {
   int count;
   int single_socket;
   int dummy[63];
};

#import "Ws2_32.dll"
   int socket(int, int, int);   
   int bind(int, sockaddr_in&, int);
   int htons(int);
   int listen(int, int);
   int accept(int, int, int);
   int closesocket(int);
   int select(int, fd_set&, int, int, timeval&);
   int recv(int, uchar&[], int, int);
   int ioctlsocket(int, uint, uint&);
   int WSAGetLastError();
   int WSAAsyncSelect(int, int, uint, int);
#import   


// ---------------------------------------------------------------------
// Global variables
// ---------------------------------------------------------------------

// Flags whether OnInit was successful
bool SuccessfulInit = false;

// Handle of main listening server socket
int ServerSocket;

// List of currently connected clients
Connection * Clients[];

// For EA compilation only, we track whether we have 
// successfully created a timer
#ifdef  COMPILE_AS_EA
bool CreatedTimer = false;   
#endif

// ---------------------------------------------------------------------
// Initialisation - create listening socket
// ---------------------------------------------------------------------

void OnInit()
{
   SuccessfulInit = false;
   
   if (!TerminalInfoInteger(TERMINAL_DLLS_ALLOWED)) {Print("Requires \'Allow DLL imports\'");return;}
   
   // (Don't need to call WSAStartup because MT4 must have done this)

   // Create the main server socket
   ServerSocket = socket(2 /* AF_INET */, 1 /* SOCK_STREAM */, 6 /* IPPROTO_TCP */);
   if (ServerSocket == -1) {Print("ERROR " , WSAGetLastError() , " in socket creation");return;}
   
   // Put the socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(ServerSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on server socket");return;}
   
   // Bind the socket to the specified port number. In this example,
   // we only accept connections from localhost
   sockaddr_in service;
   service.af_family = 2 /* AF_INET */;
   service.addr = 0x100007F; // equivalent to inet_addr("127.0.0.1")
   service.port = (short)htons(PortNumber);
   if (bind(ServerSocket, service, 16 /* sizeof(service) */) == -1) {Print("ERROR " , WSAGetLastError() , " in socket bind");return;}

   // Put the socket into listening mode
   if (listen(ServerSocket, 10) == -1) {Print("ERROR " , WSAGetLastError() , " in socket listen");return;}

   #ifdef  COMPILE_AS_EA
   // In the context of an EA, we can improve the latency even further by making the 
   // processing event-driven, rather than just on a timer. If we use
   // WSAAsyncSelect() to tell Windows to send a WM_KEYDOWN whenever there's socket
   // activity, then this will fire the EA's OnChartEvent. We can thus collect
   // socket events even faster than via the timed check, which becomes a back-up.
   // Note that WSAAsyncSelect() on a server socket also automatically applies 
   // to all client sockets created from it by accept(), and also that
   // WSAAsyncSelect() puts the socket into non-blocking mode, duplicating what
   // we have already done above using ioctlsocket()
   if (WSAAsyncSelect(ServerSocket, (int)ChartGetInteger(0, CHART_WINDOW_HANDLE), 0x100 /* WM_KEYDOWN */, 0xFF /* All events */) != 0) {
      Print("ERROR " , WSAGetLastError() , " in async select");
   }
   #endif

   // Flag that we've successfully initialised
   SuccessfulInit = true;

   // If we're operating as an EA rather than a script, then try setting up a timer
   #ifdef  COMPILE_AS_EA
   CreateTimer();
   #endif   
}


// ---------------------------------------------------------------------
// Variation between EA and script, depending on COMPILE_AS_EA above.
// Both versions simply call MainLoop() to handle the socket activity.
// The script version simply does so from a loop in OnStart().
// The EA version sets up a timer, and then calls MainLoop from OnTimer().
// It also has event-driven handling of socket activity by getting
// Windows to simulate keystrokes whenever a socket event happens.
// This is even faster than relying on the timer.
// ---------------------------------------------------------------------

#ifdef  COMPILE_AS_EA
   // .........................................................
   // EA version. Simply calls MainLoop() from a timer.
   // The timer has previously been created in OnInit(), 
   // though we also check this in OnTick()...
   void OnTimer()
   {
      MainLoop();
   }
   
   // The function which creates the timer if it hasn't yet been set up
   void CreateTimer()
   {
      if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer(SLEEP_MILLISECONDS);
   }

   // Timer creation can sometimes fail in OnInit(), at least
   // up until MT4 build 970, if MT4 is starting up with the EA already
   // attached to a chart. Therefore, as a fallback, we also 
   // handle OnTick(), and thus eventually set up the timer in
   // the EA's first tick if we failed to set it up in OnInit()
   void OnTick()
   {
      CreateTimer();
   }

   // In an EA, the use of WSAAsyncSelect() above means that Windows will fire a key-down 
   // message whenever there is socket activity. We can then respond to KEYDOWN events
   // in MQL4 as a way of knowing that there is socket activity to be dealt with.
   void OnChartEvent(const int id, const long& lparam, const double& dparam, const string& sparam)
   {
      if (id == CHARTEVENT_KEYDOWN) {
         // The lparam will be the socket handle, and the dparam will be the type
         // of socket event (e.g. dparam = 1 = FD_READ). The fastest option would be
         // to use the socket handle in lparam to identify the specific socket
         // which requires action, but we'll take the slightly less efficient but simpler
         // route where we just do all our full socket handling in response to an event.
         // Similarly, we won't bother to distinguish between real keypresses and 
         // the pseudo-keypresses from WSAAyncSelect().
         MainLoop();   
      }
   }
  
#else

   // .........................................................
   // Script version. Simply calls MainLoop() repeatedly
   // until the script is removed, with a small sleep between calls
   void OnStart()
   {
      while (!IsStopped()) {
         MainLoop();
         Sleep(SLEEP_MILLISECONDS);
      }
   }
#endif


// ---------------------------------------------------------------------
// Main processing loop which handles new incoming connections, and reads
// data from existing connections.
// Can either be called from OnStart() in a script, on a continuous loop 
// until IsStopped() is true; or from OnTimer() in an EA.
// ---------------------------------------------------------------------

void MainLoop()
{
   // Do nothing if init was unsuccessful
   if (!SuccessfulInit) return;
   
   // .........................................................
   // Do we have a new pending connection on the server socket?
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;
   
   fd_set PollServerSocket;
   PollServerSocket.count = 1;
   PollServerSocket.single_socket = ServerSocket;

   int selres = select(0, PollServerSocket, 0, 0, waitfor);
   if (selres > 0) {
   
      Print("New incoming connection...");
      int NewClientSocket = accept(ServerSocket, 0, 0);
      if (NewClientSocket == -1) {
         if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
            // Blocking warning; ignore
         } else {
            Print("ERROR " , WSAGetLastError() , " in socket accept");
         }

      } else {
         Print("...accepted");

         int ctarr = ArraySize(Clients);
         ArrayResize(Clients, ctarr + 1);
         Clients[ctarr] = new Connection(NewClientSocket);               
         Print("Got connection to client #", Clients[ctarr].GetID());
      }
   }

   // .........................................................
   // Process any incoming data from client connections
   // (including any which have just been accepted, above)
   int ctarr = ArraySize(Clients);
   for (int i = ctarr - 1; i >= 0; i--) {
      // Return value from ReadAnyPendingData() is true
      // if the socket still seems to be alive; false if 
      // the connection seems to have been closed, and should be discarded
      if (Clients[i].ReadAnyPendingData()) {
         // Socket still seems to be alive
         
      } else {
         // Socket appears to be dead. Delete, and remove from list
         Print("Lost connection to client #", Clients[i].GetID());
         
         delete Clients[i];
         for (int j = i + 1; j < ctarr; j++) {
            Clients[j - 1] = Clients[j];
         }
         ctarr--;
         ArrayResize(Clients, ctarr);           
      }
   }
}

// ---------------------------------------------------------------------
// Termination - clean up sockets
// ---------------------------------------------------------------------

void OnDeinit(const int reason)
{
   closesocket(ServerSocket);
   
   for (int i = 0; i < ArraySize(Clients); i++) {
      delete Clients[i];
   }
   ArrayResize(Clients, 0);
   
   #ifdef  COMPILE_AS_EA
   EventKillTimer();
   CreatedTimer = false;
   #endif
}


// ---------------------------------------------------------------------
// Simple wrapper around each connected client socket
// ---------------------------------------------------------------------

class Connection {
private:
   // Client socket handle
   int mSocket;

   // Temporary buffer used to handle incoming data
   uchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
   
   // Stored-up data, waiting for a \r character 
   string mPendingData;
   
public:
   Connection(int ClientSocket);
   ~Connection();
   string GetID() {return IntegerToString(mSocket);}
   
   bool ReadAnyPendingData();
   void ProcessIncomingMessage(string strMessage);
};

// Constructor, called with the handle of a newly accepted socket
Connection::Connection(int ClientSocket)
{
   mPendingData = "";
   mSocket = ClientSocket; 
   
   // Put the client socket into non-blocking mode
   uint nbmode = 1;
   if (ioctlsocket(mSocket, 0x8004667E /* FIONBIO */, nbmode) != 0) {Print("ERROR in setting non-blocking mode on client socket");}
}

// Destructor. Simply close the client socket
Connection::~Connection()
{
   closesocket(mSocket);
}

// Called repeatedly on a timer, to check whether any
// data is available on this client connection. Returns true if the 
// client still seems to be connected (*not* if there's new data); 
// returns false if the connection seems to be dead. 
bool Connection::ReadAnyPendingData()
{
   // Check the client socket for data-readability
   timeval waitfor;
   waitfor.secs = 0;
   waitfor.usecs = 0;

   fd_set PollClientSocket;
   PollClientSocket.count = 1;
   PollClientSocket.single_socket = mSocket;

   int selres = select(0, PollClientSocket, 0, 0, waitfor);
   if (selres > 0) {
      
      // Winsock says that there is data waiting to be read on this socket
      int res = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0);
      if (res > 0) {
         // Convert the buffer to a string, and add it to any pending
         // data which we already have on this connection
         string strIncoming = CharArrayToString(mTempBuffer, 0, res);
         mPendingData += strIncoming;
         
         // Do we have a complete message (or more than one) ending in \r?
         int idxTerm = StringFind(mPendingData, "\r");
         while (idxTerm >= 0) {
            if (idxTerm > 0) {
               string strMsg = StringSubstr(mPendingData, 0, idxTerm);         
               
               // Strip out any \n characters lingering from \r\n combos
               StringReplace(strMsg, "\n", "");
               
               // Print the \r-terminated message in the log
               ProcessIncomingMessage(strMsg);
            }               
         
            // Keep looping until we have extracted all the \r delimited 
            // messages, and leave any residue in the pending data 
            mPendingData = StringSubstr(mPendingData, idxTerm + 1);
            idxTerm = StringFind(mPendingData, "\r");
         }
         
         return true;
      
      } else if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */) {
         // Would block; not an error
         return true;
         
      } else {
         // recv() failed. Assume socket is dead
         return false;
      }
   
   } else if (selres == -1) {
      // Assume socket is dead
      return false;
      
   } else {
      // No pending data
      return true;
   }
}

// Can override this with whatever you want to do with incoming messages:
// handling trading commands, send data back down the socket etc etc etc
void Connection::ProcessIncomingMessage(string strMessage)
{
   Print("#" , GetID() , ": " , strMessage);
}
 
jjc:

Para um pouco de diversão...


Lol. Incrível. Parabéns. Eu não pensei que fosse possível codificar estas funcionalidades no MQL.

 
Mariop:

Lol. Incrível. Parabéns. Eu não pensei que fosse possível codificar estas funcionalidades no MQL.

Meu instinto é sempre de fazer soquetes de servidor usando multi-tarefas, mas não consigo pensar em uma razão pela qual este código não seria viável e seguro para uso na vida real.

Há uma coisa na versão mais recente, acima, que não é a ideal. Em vez de colocar mais um grande bloco de código, vou descrevê-lo em seu lugar:

  • O uso do WSASyncSelect() para fazer o manuseio mais rápido baseado em eventos depende de ter um manípulo de janela de gráficos.
  • Mas a tentativa de usar CHART_WINDOW_HANDLE falhará no OnInit() se o MT4 estiver começando com uma EA já e previamente anexada a um gráfico (como o fato, já tratado, de que o EventSetMillisecondTimer pode falhar no OnInit)
  • Portanto, se você reiniciar o MT4 com a EA já anexada e em execução, a chamada para a WSAAsyncSelect falhará, e você terá apenas o manuseio baseado no timer da atividade do soquete, e não o manuseio mais rápido impulsionado por eventos.

É uma pequena mudança no código, mas não quero colocar mais um grande bloco neste fórum quando apenas você estiver interessado.

 
jjc :

Em vez de postar outro grande bloco de código [...]

Em segundos pensamentos...

Mais uma versão, com as seguintes alterações:

  • Funciona em MT5 (32 bits e 64 bits), bem como MT4
  • Lida com o fato de que CHART_WINDOW_HANDLE pode falhar no OnInit
  • Faz o processamento mais rápido das notificações orientadas a eventos via OnChartEvent, apenas verificando o soquete específico que gerou o evento em vez de executar todo o MainLoop()
  • Opcionalmente, permite conexões de qualquer computador em vez de apenas localhost, comutável por meio de um parâmetro em Inputs

 // *********************************************************************
// Example of non-blocking server sockets, allowing connections
// from multiple concurrent clients. This example waits for 
// each \r-terminated line from a client (e.g. Telnet), and prints it 
// to the Experts log. The behaviour can be extended into something
// more realistic by changing Connection::ProcessIncomingMessage().
//
// Works on both MT4 and MT5 (32-bit and 64-bit). But see the 
// notes to socketselect3264() about the cheat which is used
// on 64-bit MT5.
// *********************************************************************

#property strict 


// ---------------------------------------------------------------------
// This code can either create an EA or a script. To create a script,
// comment out the line below. The code will then have OnTimer() 
// and OnTick() instead of OnStart()
// ---------------------------------------------------------------------

#define COMPILE_AS_EA


// ---------------------------------------------------------------------
// If we are compiling as a script, then we want the script
// to display the inputs page
// ---------------------------------------------------------------------

#ifndef COMPILE_AS_EA
#property show_inputs
#endif


// ---------------------------------------------------------------------
// User-configurable parameters
// ---------------------------------------------------------------------

// Purely cosmetic; causes MT4 to display the comments below
// as the options for the input, instead of using a boolean true/false
enum AllowedAddressesEnum {
   aaeLocal = 0 ,   // localhost only (127.0.0.1)
   aaeAny = 1        // All IP addresses
};

// User inputs
input int                      PortNumber = 51234 ;     // TCP/IP port number
input AllowedAddressesEnum    AcceptFrom = aaeLocal;   // Accept connections from


// ---------------------------------------------------------------------
// Constants
// ---------------------------------------------------------------------

// Size of temporary buffer used to read from client sockets
#define SOCKET_READ_BUFFER_SIZE   10000

// Defines the frequency of the main-loop processing, in milliseconds.
// This is either the duration of the sleep in OnStart(), if compiled
// as a script, or the frequency which is given to EventSetMillisecondTimer()
// if compiled as an EA. (In the context of an EA, the use of additional
// event-driven handling means that the timer is only a fallback, and
// this value could be set to a larger value, just acting as a sweep-up)
#define SLEEP_MILLISECONDS       10


// ---------------------------------------------------------------------
// Forward definitions of classes
// ---------------------------------------------------------------------

// Wrapper around a connected client socket
class Connection;

// ---------------------------------------------------------------------
// Winsock structure definitions and DLL imports
// ---------------------------------------------------------------------

struct sockaddr_in {
   short af_family;
   short port;
   int addr;
   int dummy1;
   int dummy2;
};

struct timeval {
   int secs;
   int usecs;
};

struct fd_set {
   int count;
   int single_socket;
   int dummy[ 63 ];
};

struct fd_set64 {
   long count;
   long single_socket;
   long dummy[ 63 ];
};

#import "Ws2_32.dll"
   int socket( int , int , int );   
   int bind( int , sockaddr_in&, int );
   int htons( int );
   int listen( int , int );
   int accept( int , int , int );
   int closesocket( int );
   int select( int , fd_set&, int , int , timeval&);
   int select( int , fd_set64&, int , int , timeval&);   // See notes to socketselect3264() below
   int recv( int , uchar &[], int , int );
   int ioctlsocket( int , uint , uint &);
   int WSAGetLastError();
   int WSAAsyncSelect( int , int , uint , int );
#import   


// ---------------------------------------------------------------------
// Global variables
// ---------------------------------------------------------------------

// Flags whether OnInit was successful
bool SuccessfulInit = false ;

// Handle of main listening server socket
int ServerSocket;

// List of currently connected clients
Connection * Clients[];

#ifdef COMPILE_AS_EA
// For EA compilation only, we track whether we have 
// successfully created a timer
bool CreatedTimer = false ;   

// For EA compilation only, we track whether we have 
// done WSAAsyncSelect(), which can't reliably done in OnInit()
// because a chart handle isn't necessarily available
bool DoneAsyncSelect = false ;
#endif

// ---------------------------------------------------------------------
// Initialisation - create listening socket
// ---------------------------------------------------------------------

void OnInit ()
{
   SuccessfulInit = false ;
   
   if (! TerminalInfoInteger ( TERMINAL_DLLS_ALLOWED )) { Print ( "Requires \'Allow DLL imports\'" ); return ;}
   
   // (Don't need to call WSAStartup because MT4 must have done this)

   // Create the main server socket
   ServerSocket = socket( 2 /* AF_INET */ , 1 /* SOCK_STREAM */ , 6 /* IPPROTO_TCP */ );
   if (ServerSocket == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket creation" ); return ;}
   
   // Put the socket into non-blocking mode
   uint nbmode = 1 ;
   if (ioctlsocket(ServerSocket, 0x8004667E /* FIONBIO */ , nbmode) != 0 ) { Print ( "ERROR in setting non-blocking mode on server socket" ); return ;}
   
   // Bind the socket to the specified port number. In this example,
   // we only accept connections from localhost
   sockaddr_in service;
   service.af_family = 2 /* AF_INET */ ;
   service.addr = (AcceptFrom == aaeAny ? 0 /* INADDR_ANY */ : 0x100007F /* 127.0.0.1 */ );
   service.port = ( short )htons(PortNumber);
   if (bind(ServerSocket, service, 16 /* sizeof(service) */ ) == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket bind" ); return ;}

   // Put the socket into listening mode
   if (listen(ServerSocket, 10 ) == - 1 ) { Print ( "ERROR " , WSAGetLastError() , " in socket listen" ); return ;}

   #ifdef COMPILE_AS_EA
   // If we're operating as an EA, set up event-driven handling of the sockets
   // as described below. (Also called from OnTick() because this requires a 
   // window handle, and the set-up will fail here if MT4 is starting up
   // with the EA already attached to a chart.)
   SetupAsyncSelect();
   #endif

   // Flag that we've successfully initialised
   SuccessfulInit = true ;

   // If we're operating as an EA rather than a script, then try setting up a timer
   #ifdef COMPILE_AS_EA
   CreateTimer();
   #endif   
}


// ---------------------------------------------------------------------
// Variation between EA and script, depending on COMPILE_AS_EA above.
// The script version simply calls MainLoop() from a loop in OnStart().
// The EA version sets up a timer, and then calls MainLoop from OnTimer().
// It also has event-driven handling of socket activity by getting
// Windows to simulate keystrokes whenever a socket event happens.
// This is even faster than relying on the timer.
// ---------------------------------------------------------------------

#ifdef COMPILE_AS_EA
   // .........................................................
   // EA version. Simply calls MainLoop() from a timer.
   // The timer has previously been created in OnInit(), 
   // though we also check this in OnTick()...
   void OnTimer ()
   {
      MainLoop();
   }
   
   // The function which creates the timer if it hasn't yet been set up
   void CreateTimer()
   {
       if (!CreatedTimer) CreatedTimer = EventSetMillisecondTimer (SLEEP_MILLISECONDS);
   }

   // Timer creation can sometimes fail in OnInit(), at least
   // up until MT4 build 970, if MT4 is starting up with the EA already
   // attached to a chart. Therefore, as a fallback, we also 
   // handle OnTick(), and thus eventually set up the timer in
   // the EA's first tick if we failed to set it up in OnInit().
   // Similarly, the call to WSAAsyncSelect() may not be possible
   // in OnInit(), because it requires a chart handle, and that does
   // not exist in OnInit() during MT4 start-up.
   void OnTick ()
   {
      CreateTimer();
      SetupAsyncSelect();
   }

   // In the context of an EA, we can improve the latency even further by making the 
   // processing event-driven, rather than just on a timer. If we use
   // WSAAsyncSelect() to tell Windows to send a WM_KEYDOWN whenever there's socket
   // activity, then this will fire the EA's OnChartEvent below. We can thus collect
   // socket events even faster than via the timed check, which becomes a back-up.
   // Note that WSAAsyncSelect() on a server socket also automatically applies 
   // to all client sockets created from it by accept(), and also that
   // WSAAsyncSelect() puts the socket into non-blocking mode, duplicating what
   // we have already above using ioctlsocket().
   // The further complication is that WSAAsyncSelect() requires a window handle,
   // and this is not available in OnInit() during MT4 start-up. Therefore,
   // like the timer though for different reasons, we repeat the call to
   // this function during OnTick()
   void SetupAsyncSelect()
   {
       if (DoneAsyncSelect) return ;
       if (WSAAsyncSelect(ServerSocket, ( int ) ChartGetInteger ( 0 , CHART_WINDOW_HANDLE ), 0x100 /* WM_KEYDOWN */ , 0xFF /* All events */ ) == 0 ) {
         DoneAsyncSelect = true ;
      }
   }

   // In an EA, the use of WSAAsyncSelect() above means that Windows will fire a key-down 
   // message whenever there is socket activity. We can then respond to KEYDOWN events
   // in MQL4 as a way of knowing that there is socket activity to be dealt with.
   void OnChartEvent ( const int id, const long & lparam, const double & dparam, const string & sparam)
   {
       if (id == CHARTEVENT_KEYDOWN ) {
         // For a pseudo-keypress from WSAAsyncSelect(), the lparam will be the socket handle
         // (and the dparam will be the type of socket event, e.g. dparam = 1 = FD_READ).
         // Of course, this event can also be fired for real keypresses on the chart...
         
         // What is the lparam?
         if (lparam == ServerSocket) {
             // New pending connection on the listening server socket
            AcceptNewConnections();
         
         } else {
             // See if lparam is one of the client sockets, by looping 
             // through the Clients[] array
             int ctarr = ArraySize (Clients);
             for ( int i = ctarr - 1 ; i >= 0 ; i--) {
               if (Clients[i].SocketHandle() == lparam) {

                   // Yes, we have found a socket matching this "keyboard" event.
                   // Return value from ReadAnyPendingData() is true
                   // if the socket still seems to be alive; false if 
                   // the connection seems to have been closed, and should be discarded
                   if (Clients[i].ReadAnyPendingData()) {
                     // Socket still seems to be alive
                     
                  } else {
                     // Socket appears to be dead
                     ReleaseClientSocket(i, ctarr);
                  }
                   return ; //Early exit!
               }
            }
            
             // If we get here, then the lparam does not match any
             // of the sockets, and the event must be a real keypress,
             // not a pseudo-keypress from WSAAsyncSelect()
         }
      }
   }
    
#else

   // .........................................................
   // Script version. Simply calls MainLoop() repeatedly
   // until the script is removed, with a small sleep between calls
   void OnStart ()
   {
       while (! IsStopped ()) {
         MainLoop();
         Sleep (SLEEP_MILLISECONDS);
      }
   }
#endif


// ---------------------------------------------------------------------
// Main processing loop which handles new incoming connections, and reads
// data from existing connections.
// Can either be called from OnTimer() in an EA; or from OnStart() in a script, 
// on a continuous loop until IsStopped() is true. In an EA, socket 
// activity should almost always get handled by the event-driven stuff 
// above, and this timer loop is just a fallback.
// ---------------------------------------------------------------------

void MainLoop()
{
   // Do nothing if init was unsuccessful
   if (!SuccessfulInit) return ;

   // This main timer loop does two things: accepts new incoming 
   // connections, and reads pending data on all sockets (including
   // new ones which have just been accepted)
   AcceptNewConnections();
   ReadSocketMessages();   
}

// Accept any new pending connections on the main listening server socket,
// wrapping the new caller in a Connection object and putting it
// into the Clients[] array
void AcceptNewConnections()
{
   int selres = socketselect3264(ServerSocket);
   if (selres > 0 ) {
   
       Print ( "New incoming connection..." );
       int NewClientSocket = accept(ServerSocket, 0 , 0 );
       if (NewClientSocket == - 1 ) {
         if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */ ) {
             // Blocking warning; ignore
             Print ( "... would block; ignore" );
         } else {
             Print ( "ERROR " , WSAGetLastError() , " in socket accept" );
         }

      } else {
         Print ( "...accepted" );
         
         // Create a new Connection object to wrap the client socket,
         // and add it to the Clients[] array
         int ctarr = ArraySize (Clients);
         ArrayResize (Clients, ctarr + 1 );
         Clients[ctarr] = new Connection(NewClientSocket);               
         Print ( "Got connection to client #" , Clients[ctarr].GetID());
      }
   }
}


// Process any incoming data from all client connections
// (including any which have just been accepted, above)
void ReadSocketMessages()
{
   int ctarr = ArraySize (Clients);
   for ( int i = ctarr - 1 ; i >= 0 ; i--) {
       // Return value from ReadAnyPendingData() is true
       // if the socket still seems to be alive; false if 
       // the connection seems to have been closed, and should be discarded
       if (Clients[i].ReadAnyPendingData()) {
         // Socket still seems to be alive
         
      } else {
         // Socket appears to be dead. Delete, and remove from list
         ReleaseClientSocket(i, ctarr);
      }
   }
}

// Discards a client socket which appears to have died, deleting the Connection class
// and removing it from the Clients[] array. Takes two parameters: the index of 
// the connection within the Clients[] array, and the current size of that array (which
// is passed by reference, with the function sending back the decremented size)
void ReleaseClientSocket( int idxReleaseAt, int & SizeOfArray)
{
   Print ( "Lost connection to client #" , Clients[idxReleaseAt].GetID());
   
   delete Clients[idxReleaseAt];
   for ( int j = idxReleaseAt + 1 ; j < SizeOfArray; j++) {
      Clients[j - 1 ] = Clients[j];
   }
   SizeOfArray--;
   ArrayResize (Clients, SizeOfArray);           
}

// Wraps the Winsock select() function, doing an immediate non-blocking
// check of a single socket. This is the one area where we must
// vary things between 32-bit and 64-bit. On 64-bit MT5, we can get away
// with using Winsock calls where everything is defined as a 32-bit int;
// we can rely on the fact that socket and window handles are only ever going
// to be 4 bytes rather than 8 bytes - naughty, but works in practice.
// The one exception is the fd_set structure. On 64-bit MT5, 
// we must pass a version of the fd_set where everything is defined
// as 8-byte long rather than 4-byte int. Note that this applies to
// the MT5 version, not the O/S. On 32-bit MT5, even on a 64-bit computer,
// we use the same structure as MT4: 4-byte int rather than 8-byte long
int socketselect3264( int TestSocket)
{
   timeval waitfor;
   waitfor.secs = 0 ;
   waitfor.usecs = 0 ;

   // The following, incidentally, is permitted on MT4, but obviously returns false...
   if ( TerminalInfoInteger ( TERMINAL_X64 )) {
      fd_set64 PollSocket64;
      PollSocket64.count = 1 ;
      PollSocket64.single_socket = TestSocket;

       return select( 0 , PollSocket64, 0 , 0 , waitfor);

   } else {
      fd_set PollSocket32;
      PollSocket32.count = 1 ;
      PollSocket32.single_socket = TestSocket;
   
       return select( 0 , PollSocket32, 0 , 0 , waitfor);
   }
}


// ---------------------------------------------------------------------
// Termination - clean up sockets
// ---------------------------------------------------------------------

void OnDeinit ( const int reason)
{
   closesocket(ServerSocket);
   
   for ( int i = 0 ; i < ArraySize (Clients); i++) {
       delete Clients[i];
   }
   ArrayResize (Clients, 0 );
   
   #ifdef COMPILE_AS_EA
   EventKillTimer ();
   CreatedTimer = false ;
   DoneAsyncSelect = false ;
   #endif
}


// ---------------------------------------------------------------------
// Simple wrapper around each connected client socket
// ---------------------------------------------------------------------

class Connection {
private :
   // Client socket handle
   int mSocket;

   // Temporary buffer used to handle incoming data
   uchar mTempBuffer[SOCKET_READ_BUFFER_SIZE];
   
   // Stored-up data, waiting for a \r character 
   string mPendingData;
   
public :
   Connection( int ClientSocket);
   ~Connection();
   string GetID() { return IntegerToString (mSocket);}
   int SocketHandle() { return mSocket;}
      
   bool ReadAnyPendingData();
   void ProcessIncomingMessage( string strMessage);
};

// Constructor, called with the handle of a newly accepted socket
Connection::Connection( int ClientSocket)
{
   mPendingData = "" ;
   mSocket = ClientSocket; 
   
   // Put the client socket into non-blocking mode
   uint nbmode = 1 ;
   if (ioctlsocket(mSocket, 0x8004667E /* FIONBIO */ , nbmode) != 0 ) { Print ( "ERROR in setting non-blocking mode on client socket" );}
}

// Destructor. Simply close the client socket
Connection::~Connection()
{
   closesocket(mSocket);
}

// Called repeatedly on a timer, to check whether any
// data is available on this client connection. Returns true if the 
// client still seems to be connected (*not* if there's new data); 
// returns false if the connection seems to be dead. 
bool Connection::ReadAnyPendingData()
{
   // Check the client socket for data-readability
   int selres = socketselect3264(mSocket);
   if (selres > 0 ) {
      
       // Winsock says that there is data waiting to be read on this socket
       int szData = recv(mSocket, mTempBuffer, SOCKET_READ_BUFFER_SIZE, 0 );
       if (szData > 0 ) {
         // Convert the buffer to a string, and add it to any pending
         // data which we already have on this connection
         string strIncoming = CharArrayToString (mTempBuffer, 0 , szData);
         mPendingData += strIncoming;
         
         // Do we have a complete message (or more than one) ending in \r?
         int idxTerm = StringFind (mPendingData, "\r" );
         while (idxTerm >= 0 ) {
             if (idxTerm > 0 ) {
               string strMsg = StringSubstr (mPendingData, 0 , idxTerm);         
               
               // Strip out any \n characters lingering from \r\n combos
               StringReplace (strMsg, "\n" , "" );
               
               // Print the \r-terminated message in the log
               ProcessIncomingMessage(strMsg);
            }               
         
             // Keep looping until we have extracted all the \r delimited 
             // messages, and leave any residue in the pending data 
            mPendingData = StringSubstr (mPendingData, idxTerm + 1 );
            idxTerm = StringFind (mPendingData, "\r" );
         }
         
         return true ;
      
      } else if (WSAGetLastError() == 10035 /* WSAEWOULDBLOCK */ ) {
         // Would block; not an error
         return true ;
         
      } else {
         // recv() failed. Assume socket is dead
         return false ;
      }
   
   } else if (selres == - 1 ) {
       // Assume socket is dead
       return false ;
      
   } else {
       // No pending data
       return true ;
   }
}

// Can override this with whatever you want to do with incoming messages:
// handling trading commands, send data back down the socket etc etc etc
void Connection::ProcessIncomingMessage( string strMessage)
{
   Print ( "#" , GetID() , ": " , strMessage);
}
 
jjc: Pensando melhor...

Em vez de publicar contribuições SRC tão longas, considere apenas anexar o arquivo em si. Eu sugeriria que você EDITAR suas contribuições mais longas, apagando as seções do SRC e apenas anexando o arquivo fonte.

 
FMIC:

Em vez de publicar contribuições SRC tão longas, considere apenas anexar o arquivo em si. Eu sugeriria que você EDITAR seus posts mais longos, apagando as seções do SRC e apenas anexando o arquivo fonte em seu lugar.

Muito obrigado por sua contribuição.

Mas, entre as muitas razões pelas quais não vou fazer isso está o fato de que o código acima funciona tanto no MT4 quanto no MT5. Se eu o carregasse como um anexo, ele teria que ser marcado erroneamente como .mq4 ou .mq5, quando na verdade funciona como ambos.

 
jjc:

Mas, entre as muitas razões [...]

Outra das principais razões é o seguinte cenário, que poderia ter se aplicado ao PO:

" Sei bastante sobre programação de soquetes em uma linguagem como Java. Será que alguém escreveu um servidor socket para o MT4? Se o fizeram, então ele deve envolver o uso de funções como listen(), bind(), htons() etc. Então tentarei encontrar tal artigo fazendo uma busca no Google por 'mql4 bind listen htons'".

Tanto quanto sei, nem os motores de busca nem a busca neste site podem procurar dentro de anexos. Assim, afixar o código como anexo torná-lo-ia invisível para uma busca como "mql4 bind listen htons".

Você diz regularmente às pessoas que elas deveriam ter feito uma busca ou consultado a documentação; estou tentando ajudar exatamente com isso.

(Como acontece, "mql4 bind listen htons" não encontra este tópico porque o Google não rasteja a página desde 14 de setembro. Mas deve começar a aparecer no Google em breve).

 
jjc:

Muito obrigado por sua contribuição.

Mas, entre as muitas razões pelas quais não vou fazer isto está o fato de que o código acima funciona tanto no MT4 quanto no MT5. Se eu o carregasse como um anexo, ele teria que ser marcado erroneamente como .mq4 ou .mq5, quando na verdade ele funciona como ambos.

É esse caso que o transforma em um arquivo de cabeçalho de inclusão (".mqh"). Dessa forma, ele será reutilizável tanto na MQL4 quanto na MQL5. Basta certificar-se de não incorporar o código diretamente nos manipuladores de eventos, OnInit(), OnDeinit(), etc. - Dê a essas funções nomes separados e únicos, e então o usuário só tem que colocar uma chamada para eles no próprio código desses manipuladores.
 
jjc:

Pensando melhor...

Mais uma versão, com as seguintes mudanças:

  • Funciona em MT5 (tanto 32 bits como 64 bits), assim como em MT4
  • Trata o fato de que CHART_WINDOW_HANDLE pode falhar no OnInit
  • Faz um processamento mais rápido das notificações orientadas por eventos via OnChartEvent, verificando apenas o soquete específico que gerou o evento em vez de executar todo o MainLoop()
  • Opcionalmente, permite conexões de qualquer computador, em vez de apenas localhost, comutável através de um parâmetro em Inputs


OBRIGADO novamente jjc,

Neste momento estou com pressa e não tenho tempo para testá-lo, mas o farei na próxima semana e lhe falarei sobre isso. Acho que não terei nenhum problema para executá-lo, mas talvez eu lhe faça algumas perguntas. BTW: Por favor, não edite nenhum post; todos eles são perfeitos como são (sinto-me como em StackOverflow =)).