English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Communiquer avec MetaTrader 5 en utilisant Named Pipes sans utiliser de DLL

Communiquer avec MetaTrader 5 en utilisant Named Pipes sans utiliser de DLL

MetaTrader 5Exemples | 12 janvier 2022, 17:21
1 525 0
MetaQuotes
MetaQuotes

Introduction

De nombreux développeurs sont confrontés au même problème : comment accéder au sandbox du terminal de trading sans utiliser de DLL non sécurisées.

L'une des méthodes les plus simples et les plus sûres consiste à utiliser des Named Pipes standard qui fonctionnent comme des opérations de fichier normales. Ils vous permettent d'organiser la communication inter-processeur client-serveur entre les programmes. Bien qu'il existe un article déjà publié Une solution sans DLL pour communiquer entre les terminaux MetaTrader 5 à l'aide de Named Pipes sur ce sujet qui démontre la possibilité d'accéder aux DLL, nous allons utiliser les fonctionnalités standard et sûres du terminal client.

Vous pouvez trouver plus d'informations sur les named pipes dans la bibliothèque MSDN, mais nous allons passer aux exemples pratiques en C++ et MQL5. Nous allons mettre en œuvre le serveur, le client, l'échange de données entre eux et ensuite évaluer les performances.


Mise en œuvre du serveur

Codons un serveur simple en C++. Un script du terminal se connectera à ce serveur et échangera des données avec lui. Le noyau du serveur possède l'ensemble de fonctions WinAPI suivant :

Une fois qu'un pipe nommé est ouvert, il renvoie un handle de fichier qui peut être utilisé pour des opérations de lecture/écriture de fichiers. Vous obtenez ainsi un mécanisme très simple qui ne nécessite aucune connaissance particulière en matière d'exploitation de réseau.

Les Named pipes ont une particularité : ils peuvent être à la fois locaux et en réseau. En d'autres termes, il est facile de mettre en œuvre un serveur distant qui acceptera les connexions réseau des terminaux clients.

Voici un exemple simple de création d'un serveur local comme canal full-duplex qui fonctionne en mode d'échange d'octets :

//--- open 
CPipeManager manager;

if(!manager.Create(L"\\\\.\\pipe\\MQL5.Pipe.Server"))
   return(-1);


//+------------------------------------------------------------------+
//| Create named pipe                                                |
//+------------------------------------------------------------------+
bool CPipeManager::Create(LPCWSTR pipename)
  {
//--- check parameters
   if(!pipename || *pipename==0) return(false);
//--- close old
   Close();
//--- create named pipe
   m_handle=CreateNamedPipe(pipename,PIPE_ACCESS_DUPLEX,
                            PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT,
                            PIPE_UNLIMITED_INSTANCES,256*1024,256*1024,1000,NULL);

   if(m_handle==INVALID_HANDLE_VALUE)
     {
      wprintf(L"Creating pipe '%s' failed\n",pipename);
      return(false);
     }
//--- ok
   wprintf(L"Pipe '%s' created\n",pipename);
   return(true);
  }

Pour obtenir une connexion client, vous devez utiliser la fonction ConnectNamedPipe :

//+------------------------------------------------------------------+
//| Connect client                                                   |
//+------------------------------------------------------------------+
bool CPipeManager::ConnectClient(void)
  {
//--- pipe exists?
   if(m_handle==INVALID_HANDLE_VALUE) return(false);
//--- connected?
   if(!m_connected)
     {
      //--- connect
      if(ConnectNamedPipe(m_handle,NULL)==0)
        {
         //--- client already connected before ConnectNamedPipe?
         if(GetLastError()!=ERROR_PIPE_CONNECTED)
            return(false);
         //--- ok
        }
      m_connected=true;
     }
//---
   return(true);
  }

L'échange de données est organisé à l'aide de 4 fonctions simples :

  • CPipeManager::Send(void *data,size_t data_size)
  • CPipeManager::Read(void *data,size_t data_size)
  • CPipeManager::SendString(LPCSTR command)
  • CPipeManager::ReadString(LPSTR answer,size_t answer_maxlen)

Ils vous permettent d'envoyer/recevoir des données sous forme de données binaires ou de chaînes de texte ANSI en mode compatible MQL5. De plus, comme CFilePipe dans MQL5 ouvre un fichier en mode ANSI par défaut, les chaînes sont automatiquement converties en Unicode à la réception et à l'envoi. Si votre programme MQL5 ouvre un fichier en mode Unicode (FILE_UNICODE), il peut alors échanger des chaînes Unicode (avec signature de départ BOM).


Mise en œuvre client

Nous allons écrire notre client en MQL5. Il sera capable d'effectuer des opérations régulières sur les fichiers en utilisant la classe CFilePipe de la bibliothèque standard. Cette classe est presque identique au CFileBin, mais elle contient une vérification importante de la disponibilité des données dans un fichier virtuel avant de lire ces données.

//+------------------------------------------------------------------+
//| Wait for incoming data                                           |
//+------------------------------------------------------------------+
bool CFilePipe::WaitForRead(const ulong size)
  {
//--- check handle and stop flag
   while(m_handle!=INVALID_HANDLE && !IsStopped())
     {
      //--- enough data?
      if(FileSize(m_handle)>=size)
         return(true);
      //--- wait a little
      Sleep(1);
     }
//--- failure
   return(false);
  }

//+------------------------------------------------------------------+
//| Read an array of variables of double type                        |
//+------------------------------------------------------------------+
uint CFilePipe::ReadDoubleArray(double &array[],const int start_item,const int items_count)
  {
//--- calculate size
   uint size=ArraySize(array);
   if(items_count!=WHOLE_ARRAY) size=items_count;
//--- check for data
   if(WaitForRead(size*sizeof(double)))
      return FileReadArray(m_handle,array,start_item,items_count);
//--- failure
   return(0);
  }

Les Named pipes présentent des différences importantes dans la mise en œuvre de leurs modes local et réseau. Sans cette vérification, les opérations en mode réseau renverront toujours une erreur de lecture lors de l'envoi de grandes quantités de données (plus de 64K).

Connectons nous au serveur avec deux contrôles : soit à l'ordinateur distant nommé « RemoteServerNam », soit à la machine locale.

void OnStart()
  {
//--- wait for pipe server
   while(!IsStopped())
     {
      if(ExtPipe.Open("\\\\RemoteServerName\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break;
      if(ExtPipe.Open("\\\\.\\pipe\\MQL5.Pipe.Server",FILE_READ|FILE_WRITE|FILE_BIN)!=INVALID_HANDLE) break;
      Sleep(250);
     }
   Print("Client: pipe opened");


Échange de données

Après une connexion réussie, envoyons une chaîne de texte contenant des informations d'identification au serveur. La chaîne Unicode sera automatiquement convertie en ANSI, puisque le fichier est ouvert en mode ANSI.

//--- send welcome message
   if(!ExtPipe.WriteString(__FILE__+" on MQL5 build "+IntegerToString(__MQ5BUILD__)))
     {
      Print("Client: sending welcome message failed");
      return;
     }

En réponse, le serveur enverra sa chaîne de caractères « Hello from pipe server » et le nombre entier 1234567890. Le client enverra à nouveau la chaîne « Test string » et le nombre entier 1234567890.

//--- read data from server
   string        str;
   int           value=0;

   if(!ExtPipe.ReadString(str))
     {
      Print("Client: reading string failed");
      return;
     }
   Print("Server: ",str," received");

   if(!ExtPipe.ReadInteger(value))
     {
      Print("Client: reading integer failed");
      return;
     }
   Print("Server: ",value," received");
//--- send data to server
   if(!ExtPipe.WriteString("Test string"))
     {
      Print("Client: sending string failed");
      return;
     }

   if(!ExtPipe.WriteInteger(value))
     {
      Print("Client: sending integer failed");
      return;
     }

OK, nous en avons terminé avec l'échange simple de données. Il est maintenant temps de procéder à l'évaluation des performances.


Critères de performance

À titre de test, nous allons envoyer 1 gigaoctet de données sous forme de tableau de nombres de type double par blocs de 8 mégaoctets du serveur au client, puis vérifier l'exactitude des blocs et mesurer le taux de transfert.

Voici ce code en serveur C++ :

//--- benchmark
   double  volume=0.0;
   double *buffer=new double[1024*1024];   // 8 Mb

   wprintf(L"Server: start benchmark\n");
   if(buffer)
     {
      //--- fill the buffer
      for(size_t j=0;j<1024*1024;j++)
         buffer[j]=j;
      //--- send 8 Mb * 128 = 1024 Mb to client
      DWORD   ticks=GetTickCount();

      for(size_t i=0;i<128;i++)
        {
         //--- setup guard signatures
         buffer[0]=i;
         buffer[1024*1024-1]=i+1024*1024-1;
         //--- 
         if(!manager.Send(buffer,sizeof(double)*1024*1024))
           {
            wprintf(L"Server: benchmark failed, %d\n",GetLastError());
            break;
           }
         volume+=sizeof(double)*1024*1024;
         wprintf(L".");
        }
      wprintf(L"\n");
      //--- read confirmation
      if(!manager.Read(&value,sizeof(value)) || value!=12345)
         wprintf(L"Server: benchmark confirmation failed\n");
      //--- show statistics
      ticks=GetTickCount()-ticks;
      if(ticks>0)
         wprintf(L"Server: %.0lf Mb sent at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks);
      //---
      delete[] buffer;
     }

et dans le client MQL5 :

//--- benchmark
   double buffer[];
   double volume=0.0;

   if(ArrayResize(buffer,1024*1024,0)==1024*1024)
     {
      uint  ticks=GetTickCount();
      //--- read 8 Mb * 128 = 1024 Mb from server
      for(int i=0;i<128;i++)
        {
         uint items=ExtPipe.ReadDoubleArray(buffer);
         if(items!=1024*1024)
           {
            Print("Client: benchmark failed after ",volume/1024," Kb, ",items," items received");
            break;
           }
         //--- check the data
         if(buffer[0]!=i || buffer[1024*1024-1]!=i+1024*1024-1)
           {
            Print("Client: benchmark invalid content");
            break;
           }
         //---
         volume+=sizeof(double)*1024*1024;
        }
      //--- send confirmation
      value=12345;
      if(!ExtPipe.WriteInteger(value))
         Print("Client: benchmark confirmation failed ");
      //--- show statistics
      ticks=GetTickCount()-ticks;
      if(ticks>0)
         printf("Client: %.0lf Mb received at %.0lf Mb per second\n",volume/1024/1024,volume/1024/ticks);
      //---
      ArrayFree(buffer);
     }

Notez que le premier et le dernier élément des blocs transférés sont vérifiés afin de s'assurer qu'il n'y a eu aucune erreur lors du transfert. De plus, lorsque le transfert est terminé, le client envoie un signal de confirmation au serveur concernant la réussite de la réception des données. Si vous n'utilisez pas les confirmations finales, vous rencontrerez facilement une perte de données si l'une des parties ferme la connexion trop tôt.

Exécutez localement le serveur PipeServer.exe et attachez le script PipeClient.mq5 à n'importe quel graphique :

PipeServer.exe PipeClient.mq5
MQL5 Pipe Server
Copyright 2012, MetaQuotes Software Corp.
Pipe '\\.\pipe\MQL5.Pipe.Server' created
Client: waiting for connection...
Client: connected as 'PipeClient.mq5 on MQL5 build 705'
Server: send string
Server: send integer
Server: read string
Server: 'Test string' received
Server: read integer
Server: 1234567890 received
Server: start benchmark
......................................................
........
Server: 1024 Mb sent at 2921 Mb per second
PipeClient (EURUSD,H1)  Client: pipe opened
PipeClient (EURUSD,H1)  Server: Hello from pipe server received
PipeClient (EURUSD,H1)  Server: 1234567890 received
PipeClient (EURUSD,H1)  Client: 1024 Mb received at 2921 Mb per second


Pour les échanges locaux, le taux de transfert est vraiment étonnant - près de 3 gigaoctets par seconde. Cela signifie que les Named pipes peuvent être utilisés pour transférer presque n'importe quelle quantité de données dans des programmes MQL5.

Comparons maintenant les performances de transfert de données dans un réseau local ordinaire de 1 gigabit :

PipeServer.exe PipeClient.mq5
MQL5 Pipe Server
Copyright 2012, MetaQuotes Software Corp.
Pipe '\\.\pipe\MQL5.Pipe.Server' created
Client: waiting for connection...
Client: connected as 'PipeClient.mq5 on MQL5 build 705'
Server: send string
Server: send integer
Server: read string
Server: 'Test string' received
Server: read integer
Server: 1234567890 received
Server: start benchmark
......................................................
........
Server: 1024 Mb sent at 63 Mb per second
PipeClient (EURUSD,H1)  Client: pipe opened
PipeClient (EURUSD,H1)  Server: Hello from pipe server received
PipeClient (EURUSD,H1)  Server: 1234567890 received
PipeClient (EURUSD,H1)  Client: 1024 Mb received at 63 Mb per second


Dans le réseau local, 1 gigaoctet de données a été transféré à un taux de 63 mégaoctets par seconde, ce qui est très bon. En fait, c'est 63 % de la bande passante maximale du réseau gigabit.


Conclusion

Le système de protection de la plateforme de trading MetaTrader 5 ne permet pas aux programmes MQL5 de s'exécuter en dehors de leur sandbox, protégeant les traders contre les menaces lors de l'utilisation d'Expert Advisors non fiables. En utilisant des Named pipes , vous pouvez facilement créer des intégrations avec des logiciels tiers et gérer les AE de l'extérieur. En toute sécurité.

Traduit du russe par MetaQuotes Ltd.
Article original : https://www.mql5.com/ru/articles/503

Fichiers joints |
pipeclient.mq5 (3.15 KB)
pipeserver.zip (43.59 KB)
Comment s'abonner aux signaux de trading Comment s'abonner aux signaux de trading
Le service Signals introduit le trading social avec MetaTrader 4 et MetaTrader 5. Le Service est intégré à la plateforme de trading, et permet à quiconque de copier facilement les trades des traders professionnels. Sélectionnez l'un des milliers de fournisseurs de signaux, abonnez-vous en quelques clics et les transactions du fournisseur seront copiées sur votre compte.
Comment préparer des cotations MetaTrader 5 pour d'autres applications Comment préparer des cotations MetaTrader 5 pour d'autres applications
L'article décrit les exemples de création de répertoires, de copie de données, d'archivage, d'utilisation des symboles dans Market Watch ou la liste commune, ainsi que les exemples de traitement des erreurs, etc. Tous ces éléments peuvent éventuellement être regroupés dans un seul script pour archiver les données dans un format défini par l'utilisateur.
Comment rédiger une bonne description pour un produit Market Comment rédiger une bonne description pour un produit Market
MQL5 Market propose de nombreux produits à vendre, mais certaines de leurs descriptions laissent vraiment à désirer. De nombreux textes ont manifestement besoin d'être améliorés, car les traders ordinaires ne sont pas en mesure de les comprendre. Cet article vous aidera à mettre votre produit en valeur. Utilisez nos recommandations pour rédiger une description accrocheuse qui montrera facilement à vos clients ce que vous vendez exactement.
Comment acheter un robot de trading sur MetaTrader Market et l'installer ? Comment acheter un robot de trading sur MetaTrader Market et l'installer ?
Un produit de MetaTrader Market peut être acheté sur le site Web MQL5.com ou directement depuis les plateformes de trading MetaTrader 4 et MetaTrader 5. Choisissez un produit qui convient à votre style de trading, payez-le en utilisant votre méthode de paiement préférée et activez le produit.