
DLLを使用せず、名前のつけられたパイプを使っての MetaTrader 5との通信
はじめに
多くの開発者が同じ課題に出会います。安全性の低い 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





- 無料取引アプリ
- 8千を超えるシグナルをコピー
- 金融ニュースで金融マーケットを探索