English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
DLLを使用せず、名前のつけられたパイプを使っての MetaTrader 5との通信

DLLを使用せず、名前のつけられたパイプを使っての MetaTrader 5との通信

MetaTrader 5 | 27 10月 2015, 10:21
2 154 0
MetaQuotes
MetaQuotes

はじめに

多くの開発者が同じ課題に出会います。安全性の低い DLL を使わずトレーディングターミナルのサンドボックスを手に入れる方法です。

もっとも簡単で安全な方法の一つは、通常のファイル処理で動作する標準的な「名前付きパイプ」を使用することです。名前付きパイプにより、プロセッサ内でプログラム間のクライアントサーバー通信を行うことができます。本件に関してはすでに発表済みの記事 A DLL-free solution to communicate between MetaTrader 5 terminals using Named Pipes で DLL へのアクセスの有効化を示していますが、ここではクライアントターミナルの標準的で安全な機能を使用していきます。

MSDN ライブラリで名前付きパイプに関する情報をもっと見つけることができますが、ここでは C++ 言語および MQL5言語での実用例に取り組みます。サーバー、クライアント、それらの間のデータ交換を実装し、基準に従ってパフォーマンスを評価します。


サーバー実装

C++ 言語でシンプルなサーバーのコード作成を行います。ターミナルからのスクリプトはこのサーバーに連結し、それを使用してデータ交換を行います。サーバーのコアは以下の WinAPI 関数セットです。

  • CreateNamedPipe -名前付きパイプを作成します。
  • ConnectNamedPipe -サーバーがクライアント接続を待つことを可能にします。
  • WriteFile -パイプにデータを書き込みます。
  • ReadFile -パイプからデータを読み出します。
  • FlushFileBuffers -集積されたバッファをフラッシュします。
  • DisconnectNamedPipe-サーバーの接続を切ります。
  • CloseHandle -ハンドルをクローズします。

名前付きパイプが開かれると、それは通常のファイル書き込み/読み出し処理に使用されるファイルハンドルを返します。結果、ネットワーク処理について特別な知識を必要としないひじょうにシンプルなメカニズムを取得します。

名前付きパイプにはひとつ顕著な特徴があります。ローカルでもネットワークでもありうることです。それは、クライアントターミナルからネットワーク接続を受け付けるリモードサーバーの実装が簡単である、ということです。

ここにバイト交換モードで動作する全二重チャンネルのローカルサーバー作成のシンプルな例があります。

//--- 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);
  }

クライアント接続を得るには、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);
  }

データ交換は 4 個のシンプルな関数を使用して作成されます。

  • 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)

それらによりデータを MQL5 の互換モードでバイナリデータまたは ANSI テキストストリングとして送受信可能です。それ以上に、MQL5 の CFilePipe はデフォルトでは ANSI モードでファイルを開くため、ストリングは受信と送信の際自動的に Unicode に変換されます。お手持ちのMQL5 プログラムが Unicode モード(FILE_UNICODE)でファイルを開くなら、それは Unicode ストリング(BOM 開始シグネチャ付)を交換することが可能です。


クライアント実装

クライアントを MQL5で書きます。それで「標準ライブラリ」からの CFilePipe クラスを使用して通常のファイル処理を行うことができるようになります。このクラスは CFileBin とほとんど同じですが、このクラスはここでのデータを読む前に仮想ファイル内でデータ利用可能性の重要な検証をします。

//+------------------------------------------------------------------+
//| 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);
  }

名前付きパイプはローカルモード、ネットワークモードで実装に顕著な違いがあります。よってそのような検証をしなければ、ネットワークモード処理は大容量のデータ(64K以上)を送信する際、つねに読み出しエラーを返してしまうのです。

2種類のチェックを行うサーバーに接続します。「RemoteServerName」という名前のリモートコンピュータまたはローカルマシンのいずれかです。

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");


データ交換

問題なく接続したら識別情報をつけてテキストストリングをサーバーに送信します。Unicode ストリングは自動的に ANSI に変換されます。というのもファイルは ANSI モードで開かれるからです。

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

応答では、サーバーはそのストリング『パイプサーバーからハロー』と整数 1234567890 を送信します。クライアントはもう一度ストリング『テストストリング』と整数 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;
     }

オーケーです。シンプルなデータ交換が終わりました。ここからはベンチマークです。


ベンチマークを行います。

テストではサーバーからクライアントに向けて8MB のブロックでダブルタイプの数字の配列として1GB のデータを送信します。そしてブロックの正確性をチェックし、転送レートを計測します。

以下は 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;
     }

そして 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);
     }

転送中にエラーが発生していないか確認するために、転送されるブロックの最初と最後のエレメントがチェックされます。また、転送完了時、クライアントは問題なくデータを受け取った旨を伝える確認信号をサーバーに対して送信します。最終確認をしなければ、一方が速く接続を切りすぎた場合、簡単にデータ喪失が起こります。

ローカルで PipeServer.exe サーバーを実行し、任意のチャートに PipeClient.mq5 スクリプトをアタッチします。

PipeServer.exe PipeClient.mq5
MQL5 パイプサーバー
Copyright 2012, MetaQuotes Software Corp.
パイプ '\\.\pipe\MQL5.Pipe.Server' 作成
クライアント:接続待ち...
クライアント:'PipeClient.mq5 on MQL5 build 705'として接続
サーバー:ストリング送信
サーバー:整数送信
サーバー:ストリング読み出し
サーバー:「テストストリング」受信
サーバー: 整数読み出し
サーバー:1234567890 受信
サーバー:ベンチマーク開始
......................................................
........
サーバー:2921MB / 秒のスピードで1024MB送信
PipeClient (EURUSD,H1)  クライアント:パイプオープン
PipeClient (EURUSD,H1)  サーバー: パイプサーバーからハロー 受け取り
PipeClient (EURUSD,H1)  サーバー:1234567890 受け取り
PipeClient (EURUSD,H1)  クライアント:2921MB / 秒のスピードで 1024MB受け取り 


ローカルでの交換では、転送レートは実に驚くべきものです。ほとんど3GB / 秒です。これは、およそどんなデータ量でも MQL5 プログラムに転送するのに名前付きパイプを使用することが可能である、ということです。

ここで通常の1GB LAN でのデータ転送のベンチマークを行います。

PipeServer.exe PipeClient.mq5
MQL5 パイプサーバー
Copyright 2012, MetaQuotes Software Corp.
パイプ '\\.\pipe\MQL5.Pipe.Server' 作成
クライアント:接続待ち...
クライアント:'PipeClient.mq5 on MQL5 build 705'として接続
サーバー:ストリング送信
サーバー:整数送信
サーバー:ストリング読み出し
サーバー:「テストストリング」受信
サーバー: 整数読み出し
サーバー:1234567890 受信
サーバー:ベンチマーク開始
......................................................
........
サーバー:63MB / 秒のスピードで1024MB 送信
PipeClient (EURUSD,H1)  クライアント:パイプオープン
PipeClient (EURUSD,H1)  サーバー: パイプサーバーからハロー 受信
PipeClient (EURUSD,H1)  サーバー:1234567890 受信
PipeClient (EURUSD,H1)  クライアント:63MB / 秒のスピードで 1024MB 受信 


ローカルネットワークでは、1GB のデータは63MB / 秒の割合で転送されました。それは良い成績です。実際、それはギガバイトネットワーク最大バンド幅の 63% にあたります。


おわりに

MetaTrader 5 トレーディングプラットフォームの保護システムにより、MQL5 プログラムをそのサンドボックス外で実行することはできません。そうすることで信頼できない Expert Advisors の使用の際、トレーダーを脅威から守っているのです。名前付きパイプを使用することで第三者ソフトウェアと簡単に統合ができ、外から EA を管理することができるのです。しかも安全に。

MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/503

添付されたファイル |
pipeclient.mq5 (3.15 KB)
pipeserver.zip (43.59 KB)
その他のアプリのためにMetaTrader5の取引価格を準備する方法 その他のアプリのためにMetaTrader5の取引価格を準備する方法
この記事は、エラーのハンドリングも含めて、ディレクトリの作成、データのコピー、Market Watchでのシンボルを使用の例などを紹介します。これら全ての要素は、最終的にユーザーによって定義されたフォーマットにてデータが格納されるためのシングルスクリプトにて集められます。
トレーディングシグナルを定期購入する方法 トレーディングシグナルを定期購入する方法
「シグナル」サービスは MetaTrader 4 および MetaTrader 5でのソーシャルトレーディングを紹介します。その「サービス」はトレーディングプラットフォームに統合され、だれでも簡単にプロのトレーダーのトレードをコピーすることができます。シグナル提供者の何千というシグナルをどれでも選び、数回クリックするだけで定期購買をすると、提供者のトレードがみなさんのアカウントにコピーされます。
MetaTraderのマーケットからトレードロボットを購入し、インストールする方法 MetaTraderのマーケットからトレードロボットを購入し、インストールする方法
メタトレーダーのプロダクトは、mql5.com のウェブサイト上またはMetaTrader4,MetaTrader5から直接買うことができます。 希望のお支払い方法を選択して、トレーディングスタイルに合ったプロダクトをお選びいただき、アクティベートしてください。
Market Product向けの優れた記述方法 Market Product向けの優れた記述方法
MQL5「マーケット」には数多くのプロダクツが販売されていますが、その説明にはいま一つ物足りないということがあります。テキストの多くは明らかに改善が必要です。一般トレーダーが理解できないようなものだからです。本稿はみなさんのプロダクトを有利な位置に置くことに役立つことでしょう。販売しているものが何であるか的確に顧客に示す、注意を引く説明を書くための提案を活用ください。