
外部世界と情報交換するメタトレーダー4 エキスパートアドバイザー
はじめに
本稿では、メタトレーダー4・エキスパートアドバイザーに、サーバーとクライアント両方の役割を与えてくれるソフトウェア・ツールを紹介します。クライアントは、自分のサーバーと、ほかのどんなタイプのサーバーでも、P2P・プロトコル・コネクションを提供する接続を確立することができます。紹介するソフトウェア・ツールは2つのコンポーネントで構成されます。
NetEventsProc.exe - これは、(コンソールなしの)バックグラウンド・モードで作働し、2番目のコンポーネントNetEventsProcDLL.dllが必要とする際に処理を行う(または中止する)ウィンドウズ・プロセスです。(アプリケーションから要求することによって、サーバーとクライアント両方のためにそれらを生成することができます。自分のサーバーと、P2P・プロトコル・コネクションを提供するほかのあらゆるタイプのサーバー両方のためです。例えば、ウェブサイトと情報交換するクライアントを生成することができます。もちろんそれは、HTTP-プロトコルをサポートできるならの話ですが。
NetEventsProcDLL.dll - NetEventsProc.exeのプロセス・サービスを要求するアプリケーションと、NetEventsProc.exeプロセス自体とのインターフェースです。DLLインターフェースのおかげで、どんなプログラム言語で書かれたプログラムでも、双方向情報交換をおこなうこのソフトウェア・ツールを使用することができるのです。メタトレーダー4・エキスパートアドバイザーはこのソフトウェア・ツールの機能を利用した1つの例にすぎません。
本稿の構成は以下の通りです。
クイックスタート: 紹介するソフトウェア・ツールの具体的な使い方を、始め簡単に、それから応用例を紹介していきます。
DLLインターフェース: アプリケーションから宣言する際の、DLL関数とNetEventsProc.exeプロセスが提供するサービスの詳細について記述しています。
プロジェクト実行: プロジェクト実行の詳細について可能な限り記述しています。
添付のNetServerClient.zipアーカイブには、2つのマイクロソフト・ビジュアル・スタジオ2010ウルティメイト・プロジェクトがあります。 NetEventsProc - NetEventsProc.exeを作成するものと、NetEventsProcDLL - NetEventsProcDLL.dllを作成するためのものです。ソースコードについて詳しく説明しています。コード実行の詳細を知ることができ、好きなように自分のニーズに合わせてプロジェクトをカスタマイズできます。
NetEventsProc.exeは非同期ソケットを使ってサーバーとクライアントをインプリメントします。非同期モードにソケットをスイッチするためには、非同期モードで稼働可能な1つの方法が利用できます。ソケットをWSAEventSelect(h_Socket, h_Event, FD_ALL_EVENTS)ネットワークイベントに結び付けるのです。
このプロジェクトは、偉大なる師Elmueの教えに基づいています。
1. クイックスタート
1.1. exe.zipアーカイブ
このアーカイブを見てください。以下のコンポーネントが見つかります。
NetEventsProcDLL.dll - これを"C:\Windows\System32\"フォルダに移動させてください。
NetEventsProc.exe - "C:\NetEventsProc\"フォルダを作成して、そこへこのコンポーネントを入れてください。NetEventsProcDLL.dllは、このフォルダ内で正確にNetEventsProc.exeモジュールを探すでしょう。
以下に示すアーカイブのコンポーネントは、DLL関数をNetEventsProcDLL.dllからアプリケーションへインポートするメカニズムです。
ImportNetEventsProcDLL.mqh - NetEventsProcDLL.dllからメタトレーダー4・エキスパートアドバイザーへインポートされた関数のプロトタイプです。このファイルをターミナルデータ・フォルダ"MetaTrader 4\experts\include\"に置いてください。
cNetEventsProcDLL.h - NetEventsProcDLL.dll内全てのDLL関数プロトタイプを含むC++クラス定義C++プログラムにおけるこのクラスを含むものは、NetEventsProcDLL.dllから全てのDLL関数のインポートを許可します。他のプログラム言語で書かれたプログラムのため、それぞれの関数を別々にインポートするか、このクラス定義に書き換える必要があります。
NetEventsProcDLL.lib - ロードタイム・ダイナミック・リンクモードで、NetEventsProcDLL.dllからDLL関数をインポートするプログラムに含まれるモジュールです(/EHsc/link NetEventsProcDLL.libにコンパイルします)。
これでコンピューターの環境設定が完了します。メタトレーダー4・エキスパートアドバイザーとアプリケーションを、サーバーとクライアントをつなぐDLL関数を使って、どんなプログラム言語でも書けるようになりました。
本題から逸れないように、ImportNetEventsProcDLL.mqhとcNetEventsProcDLL.hのソースコードを紹介しておきます。ImportNetEventsProcDLL.mqhのヘッダー・ファイルには、NetEventsProcDLL.dllプログラムのインポートしたDLL関数のプロトタイプと、2つの追加関数があります。
string GetErrMsg(int s32_Error); string FormatIP(int IP);
GetErrMsg関数はDLL関数のリターンコードをテキストにコンバートします。FormatIP関数は、IPアドレスのバイナリー値を、"93.127.110.161"のような標準テキストフォーマットにコンバートします。ImportNetEventsProcDLL.mqhファイルをターミナルデータフォルダ"MetaTrader 4\experts\include\"に置いてください。
以下がImportNetEventsProcDLL.mqhのソースコードです(インポートしたDLL関数プロトタイプの定義に関する部分のみ紹介します)。
// ImportNetEventsProcDLL.mqh #import "NetEventsProcDLL.dll" // クライアント向け: int ConnectTo(string ps8_ServerIP, // in - string ps8_ServerIP = "0123456789123456" int s32_Port, // in int& h_Client[]); // out - int h_Client[1] int ConnectClose(int h_Client); // in // // サーバー向け: int ServerOpen(int s32_Port); // in int GetAllConnections(int& ph_Client[], // out - int ph_Client[62] int& ps32_ClientIP[], // out - int ps32_ClientIP[62] int& ps32_ClientCount[]); // out - int ps32_ClientCount[1] int DisconnectClient(int h_Client); // in int ServerClose(); // // クライアント・サーバー両方のため int SendToInt (int h_Client, // in int& ps32_SendBuf[], // in int s32_SendBufLen); // in - int要素におけるSendBuf[]アレイ・サイズ int SendToDouble(int h_Client, // in double& pd_SendBuf[], // in int s32_SendBufLen); // in - doubleエレメントにおけるSendBuf[] アレイ・サイズ int SendToString(int h_Client, // in string ps8_SendBuf, // in int s32_SendBufLen); // in - charエレメントにけるSendBufストリング・サイズ int ReadFromInt (int h_Client, // in int& ps32_ReadBuf[], // in int s32_ReadBufLen, // in - intエレメントにおけるReadBuf[]アレイ・サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - intエレメントにおける実読込データのカウント int ReadFromDouble(int h_Client, // in double& pd_ReadBuf[], // in int s32_ReadBufLen, // in - doubleエレメントにおけるReadBuf[]アレイ・サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - doubleエレメントにおける実読込データのカウント int ReadFromString(int h_Client, // in string ps8_ReadBuf, // in int s32_ReadBufLen, // in - charエレメントにおけるReadBuf 文字列サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - charエレメントにおける実読込データのカウント // #import //*************************************************************************************** ... ... ... // APIエラーコードでヒューマン・エラー・メッセージを取得 string GetErrMsg(int s32_Error) { ... .. } // DWORD IPをstring IPへコンバート string FormatIP(int IP) { ... ... ... }
cNetEventsProcDLL.hファイルは、NetEventsProcDLL.dllからインポートした全DLL関数にC++グラス定義を持ちます。以下がそのファイルのソースコードです。
//+---------------------------------------------------------------------------+ //| cNetEventsProcDLL.h | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ // cNetEventsProcDLL.h #pragma once #define EXPFUNC __declspec(dllexport) class cNetEventsProcDLL { public: static BOOL MessageDLL_PROCESS_ATTACH(void); static BOOL MessageDLL_PROCESS_DETACH(void); //******************************************************************************************************************* static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, //in - ps8_ServerIP = "0123456789123456" int s32_Port, //in int* ph_Client); //out - int ph_Client[1] static EXPFUNC int __stdcall ConnectClose(int h_Client); //in static EXPFUNC int __stdcall ServerOpen(int s32_Port); //in static EXPFUNC int __stdcall GetAllConnections(int* ph_Client, // out - int ph_Client[62] int* ps32_ClientIP, // out - int ps32_ClientIP[62] int* ps32_ClientCount); // out - int ps32_ClientCount[1] static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client); // in static EXPFUNC int __stdcall ServerClose(); static EXPFUNC int __stdcall SendToInt(SOCKET h_Client, // in int* ps32_SendBuf, // in int s32_SendBufLen); // in - intエレメントにおけるSendBuf[] アレイ・サイズ static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client, // in double* pd_SendBuf, // in int s32_SendBufLen); // in - doubleエレメントにおけるSendBuf[] アレイ・サイズ static EXPFUNC int __stdcall SendToString(SOCKET h_Client, // in char* ps8_SendBuf, // in int s32_SendBufLen); // charエレメントのけるSendBuf文字列サイズ static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client, // in int* ps32_ReadBuf, // in int s32_ReadBufLen, // intエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - intエレメントにおける実読込データのカウント static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client, // in double* pd_ReadBuf, // in int s32_ReadBufLen, // doubleエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - doubleエレメントにおける実読込データのカウント static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client, // in char* ps8_ReadBuf, // in int s32_ReadBufLen, // charエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - charエレメントにおける実読込データのカウント //******************************************************************************************************************* protected: static DWORD SendTo(SOCKET h_Client, char* ps8_SendBuf, INT s32_SendBufLen); static DWORD ReadFrom(SOCKET h_Client, char* ps8_ReadBuf, INT s32_ReadBufLen, INT& s32_ReadLen); };
1.2. FastStart.zip アーカイブ
このアーカイブは、デモで使っている全プログラムのソースコードを含んでいます。C++プログラムは、マイクロソフト・ビジュアル・スタジオ2010ウルティメイト・プロジェクトのClientとEchoServerに使用されています。MQL4プログラムのソースコードは、DLL関数をMQL4プログラムへインポートするのに使用するImportNetEventsProcDLL.mqhファイルと一緒に、このアーカイブでも見ることができます。 このファイルを"MetaTrader 4\experts\include\"に置いてください。
より詳しく理解するために、これら全プログラムのソースコードについて説明します。MQL4で使うDLL関数とC++プログラミング言語の運用例を3つ見ていきます。
Section 1.2.1. メタトレーダー4・エキスパートアドバイザー・サーバーとC++・クライアント間の情報交換を行います。
Section 1.2.2. C++サーバーとメタトレーダー4・エキスパートアドバイザー・クライアント間の情報交換を行います。
Section 1.2.3. メタトレーダー4・エキスパートアドバイザー間の情報交換を行います。これらのEAの1つは、値を入れることで、サーバーが提供する他のメタトレーダー4・エキスパートアドバイザー・インディケーター(エキスパートアドバイザー・クライアント)の役割を持ちます。言い換えれば、"確かな"インディケーター値の割り当てをクライアントに実行したということです。
1.2.1. メタトレーダー4の・エキスパートアドバイザー・サーバーとC++プログラム・クライアント
この古典的なメタトレーダー4・エキスパートアドバイザー・サーバーとC++プログラム間の情報交換のタスクについて考えていきましょう。
EchoServer.mq4 - エコー・サーバーの役割をするエキスパートアドバイザーです。
Client.cpp - このエキスパートアドバイザー・サーバーでクライアントの役割をするC++プログラムです。
C++クライアントは、ユーザーがコンソールから入力したメッセージを読み、エキスパートアドバイザーに送ります。エキスパートアドバイザーはこれらのメッセージを受け取ってターミナルウィンドウに表示し、受信者に返します。以下はこの考え方のイメージです。
エコーサーバーの役割をおこなうEchoServer.mq4メタトレーダー4・エキスパートアドバイザーのソースコードを紹介します。
//+---------------------------------------------------------------------------+ //| EchoServer.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ServerOpen(int s32_Port); // in */ /*int ServerClose(); */ /*int GetAllConnections(int& ph_Client[], // out - int ph_Client[62] int& ps32_ClientIP[], // out - int ps32_ClientIP[62] int& ps32_ClientCount[]); // out - int ps32_ClientCount[1] */ /*int SendToString(int h_Client, // in string ps8_SendBuf, // in int s32_SendBufLen); // in - charエレメントにけるSendBuf文字列サイズ */ /*int ReadFromString(int h_Client, // in string ps8_ReadBuf, // in int s32_ReadBufLen, // in - charエレメントにおけるReadBuf 文字列サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - charエレメントにおける実読込データのカウント */ // グローバル変数 int s32_Error; int i; int s32_Port = 2000; bool b_ServerOpened = false; // for GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount) int ph_Client [62]; int ps32_ClientIP [62]; int ps32_ClientCount[1 ]; // for int ReadFromString(h_Client, ps8_Buf, s32_BufLen, ps32_ReadLen) // for int SendToString (h_Client, ps8_Buf, s32_BufLen) string ps8_Buf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; int s32_BufLen; int ps32_ReadLen[1]; //+------------------------------------------------------------------+ //| エキスパート初期化関数 | //+------------------------------------------------------------------+ int init() { //---- s32_BufLen = StringLen(ps8_Buf); if (!b_ServerOpened) { s32_Error = ServerOpen(s32_Port); Print("ServerOpen()リターン: ",GetErrMsg(s32_Error)); if (s32_Error == OK) { b_ServerOpened = true; Print("サーバーを開いてクライアント接続を要求しています。。。"); } } //---- return(0); } //+------------------------------------------------------------------+ //| エキスパート非初期化関数 | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ServerOpened) { s32_Error = ServerClose(); Print("ServerClose()リターン: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ServerOpened = false; } //---- return(0); } int start() { //---- if (!b_ServerOpened) return(0); s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (s32_Error != 0) { Print("GetAllConnections(...)リターン: ",GetErrMsg(s32_Error)); return(1); } Print("クライアント・カウント = ", ps32_ClientCount[0]); for (i = 0; i<ps32_ClientCount[0]; i++) { Print("h_Client = ", ph_Client[i], " クライアントIP = ", FormatIP(ps32_ClientIP[i])); s32_Error = ReadFromString(ph_Client[i], ps8_Buf, s32_BufLen, ps32_ReadLen); Print("ReadFromString(",ph_Client[i],",...)リターン: ", GetErrMsg(s32_Error)); if (s32_Error == 0) { Print("ReadFromString(",ph_Client[i],",...) ps32_ReadLen = ",ps32_ReadLen[0]); if (ps32_ReadLen[0] > 0) { Print("ReadFromString(",ph_Client[i],",...) リアルデータ: ", StringSubstr(ps8_Buf,0,ps32_ReadLen[0])); s32_Error = SendToString(ph_Client[i], ps8_Buf, StringLen(StringSubstr(ps8_Buf,0,ps32_ReadLen[0]))); Print("SendToString(", ph_Client[i],",...)リターン: ",GetErrMsg(s32_Error)); } } } //---- return(0); } //+------------------------------------------------------------------+
次に、Client.cppのソースコードを紹介します。エキスパートアドバイザー・サーバーのクライアントの役割を担うC++プログラムです。
//+---------------------------------------------------------------------------+ //| Client.cpp | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ // Client.cpp #include <winsock2.h> #pragma comment(lib, "ws2_32") #include <iostream> #pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.libは \FastStart\Client\ に置かれます。 #include "cNetEventsProcDLL.h" // LocalIp = 0x6401a8c0 -> 192.168.1.100 // 全ローカルIPのリストをこのコンピューターに返す(マルチ・ネットワーク・アダプターであればマルチIPを返す)。 DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount); /* これはシンプルなクライアントです。 エコサーバーに接続し、インプット情報を送り、サーバーからエコーを読みます。 注意!!!ローカルサーバーにつなぐ際は、"127.0.0.1"を使ってはいけません!!! 注意 !!! GetLocalIPs(...)関数を使って、全ローカルIPを取得します。以下が呼び出し例です。 // ローカルIPリスト char s8_IpList[10][20]; int s32_IpCount; DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount); if (u32_Err) { printf("ローカルIPの取得に失敗しました! ネットワークが利用可能かどうか確認してください。"); return 1; } printf("\nLocal IP's list:\n"); for (int i = 0; i<s32_IpCount; i++) printf("\n%s\n",s8_IpList[i]); // */ // 以下が接続したいサーバーです。 #define SERVER_IP "192.168.1.5" //これがGetLocalIPs(...)関数を使って取得したローカルIPになります。 // ループバックと呼ばれる"127.0.0.1"のアドレスを使ってはいけません !!! #define PORT 2000 int main() { // ローカルIPリスト char s8_IpList[10][20]; int s32_IpCount; DWORD u32_Err = GetLocalIPs(s8_IpList, s32_IpCount); if (u32_Err) { printf("ローカルIPの取得に失敗しました! ネットワークが利用可能かどうか確認してください。"); return 1; } printf("\nLocal IP's list:\n"); for (int i = 0; i<s32_IpCount; i++) printf("\n%s\n",s8_IpList[i]); // char s8_ServerIP[] = SERVER_IP; int s32_Port = PORT; int ph_Client[1]; int h_Client; DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client); if (u32_Error) { printf("\nConnectTo(...) 接続に失敗しました。: %d\n", u32_Error); return 1; } else printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]); h_Client = ph_Client[0]; // 接続に成功しました!この接続でSendTo(...)データを取得しましょう.....。 char ps8_SendData[200]; int s32_SendDataLen;; char ps8_ReadBuf[1025]; DWORD s32_ReadBufLen = 1025; int ps32_ReadLen[1]; while(true) { std::cout << "\nサーバーへ送信する内容を入力するか、イグジットしてください。 'q':\n" << std::endl; std::cin.getline(ps8_SendData, 200); s32_SendDataLen = strlen(ps8_SendData); OemToCharBuff(ps8_SendData, ps8_SendData, s32_SendDataLen); if (ps8_SendData[0] == 'q') { u32_Error = cNetEventsProcDLL::ConnectClose(h_Client); if (u32_Error) { printf("\nConnectClose(...) 接続停止に失敗しました。: %d\n", u32_Error); break; } else { printf("\nConnectClose(...) OK.\n"); break; } } u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendData, s32_SendDataLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); printf("\nSendTo(%d...)を%d bytes\nへ送りました。", h_Client, s32_SendDataLen); CharToOemBuff(ps8_SendData, ps8_SendData, s32_SendDataLen); printf("\nSendTo(%d...) データを送信しました。: %s\n",h_Client, ps8_SendData); printf("エコーを待っています.....。"); break; case ERROR_INVALID_PARAMETER: printf("\nSendTo(%d...)リターン: ERROR_INVALID_PARAMETER(%d)\n",h_Client, u32_Error); printf("\nERROR_INVALID_PARAMETER -> パラメーターの一部が無効です。: h_Client, ps8_SendData, u32_SendDataLen is invalid...\n"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...)リターン: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> データは次のFD_WRITEイベント後に送信されます。何もしないでください。\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...)リターン: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING ->エラー:以前に送信したオペレーションはまだ処理中です。このデータは送信されません。しばらく経ってから試してみてください。\n"); break; default: printf("\nSendTo(%d...)重大なエラーが発生しました。: %d\n",h_Client, u32_Error); // 重大なエラー -> イベントループの不具合 printf("\n接続がクローズされました!\n"); break; }; if (u32_Error == 0 || u32_Error == WSAEWOULDBLOCK) { int ReadLen = 0; while(!ReadLen) { u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if (u32_Error) { printf("\nReadFromString(%d...)エラーが発生しました: %d\n", h_Client, u32_Error); break; } ReadLen = ps32_ReadLen[0]; } if (u32_Error) { printf("\nReadFromString(%d...)エラーが発生しました: %d\n", h_Client, u32_Error); } else { printf("\nReadFromString(%d...) OK, %d bytes\nを読み込みます。", h_Client, ReadLen); } if (ReadLen > 0) { CharToOemBuff(ps8_ReadBuf, ps8_ReadBuf, s32_SendDataLen); ps8_ReadBuf[ReadLen] = 0; printf("\nReadFromString(%d...)データを読み込みました。: %s\n", h_Client, ps8_ReadBuf); } } } return 0; } // LocalIp = 0x6401a8c0 -> 192.168.1.100 // 全ローカルIPのリストをこのコンピューターに返す(マルチ・ネットワーク・アダプターであればマルチIPを返す)。 DWORD GetLocalIPs(char s8_IpList[][20], int &s32_IpCount) { // Winsock version 2.0は全てのウィンドウズ・オペレーティング・システムで利用可能です。 // ただし、Winsock 1.1を使用しているウィンドウズ95は例外です。 WSADATA k_Data; DWORD u32_Error = WSAStartup(MAKEWORD(2,0), &k_Data); if (u32_Error) return u32_Error; int ps32_IpList[20]; char s8_Host[500]; if (gethostname(s8_Host, sizeof(s8_Host)) == SOCKET_ERROR) return WSAGetLastError(); struct hostent* pk_Host = gethostbyname(s8_Host); if (!pk_Host) return WSAGetLastError(); s32_IpCount = 0; for (DWORD i=0; TRUE; i++) { if (!pk_Host->h_addr_list[i]) break; // IPリストはなくなりました。 ps32_IpList[i] = *((DWORD*)pk_Host->h_addr_list[i]); s32_IpCount++; } if (!s32_IpCount) return WSAENETDOWN; // ローカルIPがないということは、利用可能なネットワークがないということです。 for (int i = 0; i<s32_IpCount; i++) { BYTE* pu8_Addr = (BYTE*)&ps32_IpList[i]; sprintf(s8_IpList[i],"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]); } return 0; }
デモを稼働させるには、以下のことが必要です。:
EchoServer.mq4ファイルをターミナルデータ・フォルダ"МetaТrader 4\experts\"に置いてコンパイルしてください。
マイクロソフト・ビジュアル・スタジオ2010ウルティメイトでクライアントプロジェクトを開き、リリース環境設定を使って構築してください。別のIDEでプロジェクトを構築するのならば、NetEventsProcDLL.libモジュールを、編集者のために追加エントリーとして特定することを忘れないでください(次をコンパイルします: /EHsc/link NetEventsProcDLL.lib)。
Client.exe実行モジュールを稼働させてください。コード10057のエラーと、コンピューターのローカルIPリストを取得します。Client.cppソースコードで以下のstringを訂正してください。
#define SERVER_IP "192.168.1.5"
最初(唯一)のローカルIPを"192.168.1.5"に置き換え、プロジェクトをコンパイルし直します。
メタトレーダー4・ターミナルのチャート上でEchoServer・エキスパート・アドバイザーを稼働させてください。全てが正常に行われると、ターミナルウィンドウは次のメッセージを表示します。: "ServerOpen()リターン: OK"、 "サーバーを開いてクライアント接続を要求しています...。"それから、すべてのティックで、エキスパートアドバイザーは、接続要求をチェック、接続し、すべての接続先からのメッセージを読んで、メッセージのコピーを受信者に送り返します。エキスパートアドバイザーは、ターミナルウィンドウに現在の情報を全て表示します。
これでステップ3で作成したClient.exeプログラムを起動することができます。
このプログラムのコピーをいくつか稼働して、エキスパートアドバイザーがどのようにして全コピーと情報交換するのかみることができます。
ステップ3のローカルIPの代わりに、エキスパートアドバイザーを稼働させているコンピューターのグローバルIPをセットすると、Client.exeを稼働させ、インターネットにつながっている他のコンピューター上にあるエキスパートアドバイザーと通信することができます。もちろん起動している全てのファイアウォールを終了するのを忘れないでください。
1.2.2. C++プログラムサーバー&メタトレーダー4・エキスパートアドバイザー・クライアント
さて、ここからは古典的なメタトレーダー4・エキスパートアドバイザーとC++プログラム間の情報交換のタスクについて考えていきましょう。
EchoServer.cpp - C++プログラムはエコーサーバーの役割をおこないます。
Client.mq4 - エキスパートアドバイザーはこのC++サーバーでクライアントの役割をおこないます。
エキスパートアドバイザー・クライアントは、シンボルのクォートを読み込みます。クォートをC++サーバーに送信するのです。C++サーバーは、クォートを受信者に返します。受信者はターミナルウィンドウでそれらを表示することになります。以下はこの考え方のイメージです。
以下に、クライアントの役割をおこなう、メタトレーダー4エキスパートアドバイザーClient.mq4のソースコードを紹介します。
//+---------------------------------------------------------------------------+ //| Client.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ConnectTo(string ps8_ServerIP, // in - string ps8_ServerIP = "0123456789123456" int s32_Port, // in int& ph_Client[]); // out - int ph_Client[1] */ /*int SendToDouble(int h_Client, // in double& pd_SendBuf[], // in int s32_SendBufLen); // in - doubleエレメントにおけるSendBuf[] アレイ・サイズ */ /*int ReadFromDouble(int h_Client, // in double& pd_ReadBuf[], // in int s32_ReadBufLen, // in - doubleエレメントにおけるReadBuf[]アレイ・サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - doubleエレメントにおける実読込データのカウント */ /*int ConnectClose(int h_Client); // in */ // グローバル変数 int s32_Error; int i; // for int ConnectTo(ps8_ServerIP, s32_Port, ph_Client); // out - int h_Client[1] string ps8_ServerIP = "192.168.1.5"; // 固有ローカルIP int s32_Port = 2000; int ph_Client[1]; bool b_ConnectTo = false; // for int SendToDouble(ph_Client[0], pd_Buf, s32_BufLen); // for int ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen); double pd_Buf[1]; int s32_BufLen = 1; int ps32_ReadLen[1]; //+------------------------------------------------------------------+ //| エキスパート初期化関数 | //+------------------------------------------------------------------+ int init() { //---- if (!b_ConnectTo) { s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client); Print("ConnectTo(...) リターン: ",GetErrMsg(s32_Error)); Print("ConnectTo(...) ハンドル: ",ph_Client[0]); if (s32_Error == OK) { b_ConnectTo = true; Print("クライアントがサーバーに接続できました。: ",ps8_ServerIP); } } //---- return(0); } //+------------------------------------------------------------------+ //| エキスパート非初期化関数 | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ConnectTo) { s32_Error = ConnectClose(ph_Client[0]); Print("ConnectClose(...) リターン: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ConnectTo = false; } //---- return(0); } int start() { //---- if (!b_ConnectTo) return(0); RefreshRates(); double pd_Value[1]; pd_Value[0] = NormalizeDouble(Bid,Digits); s32_Error = SendToDouble(ph_Client[0], pd_Value, s32_BufLen); if (s32_Error != 0) { Print("SendToDouble(",ph_Client[0],"...) リターン: ",GetErrMsg(s32_Error)); return(1); } else Print("SendToDouble(",ph_Client[0],"...)リターン: OK"); s32_Error = ReadFromDouble(ph_Client[0], pd_Buf, s32_BufLen, ps32_ReadLen); if (s32_Error != 0) { Print("ReadFromDouble(",ph_Client[0],"...) リターン: ", GetErrMsg(s32_Error)); return(1); } else Print("ReadFromDouble(",ph_Client[0],"...) リターン: OK"); pd_Buf[0] = NormalizeDouble(pd_Buf[0],Digits); if (ps32_ReadLen[0] > 0) Print("読み込んだdouble値: ", pd_Buf[0]); //---- return(0); } //+------------------------------------------------------------------+
次に、以下はエキスパートアドバイザー・クライアントのエコーサーバーの役割をおこなうEchoServer.cppC++プログラムのソースコードです。
//+---------------------------------------------------------------------------+ //| EchoServer.cpp | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ // EchoServer.cpp #include <winsock2.h> #pragma comment(lib, "NetEventsProcDLL") // NetEventsProcDLL.libは次の場所に置かれます ...\FastStart\EchoServer\ #include <iostream> #include <conio.h> #include "cNetEventsProcDLL.h" BOOL FormatIP(DWORD u32_IP, char* s8_IP); int main() { int s32_Port = 2000; // ポート2000を待ち受けるサーバーを生成してみます。 // ポートは変更できます。 DWORD u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port); if (u32_Error) { printf("\nServerOpen()を開くことに失敗しました。: %d\n", u32_Error); return 1; } else printf("\nServerOpen()を開き、接続しています...\n"); DWORD u32_Count = 0; DWORD u32_CountOld = 0; double pd_Buf[1025]; DWORD u32_BufLen = 1025; int ps32_ReadLen[1]; pd_Buf[0] = 0; int ph_Client[62]; int ps32_ClientIP[62]; int ps32_ClientCount[1]; while(!kbhit()) { u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (u32_Error) { printf("\nGetAllConnections(...)接続に失敗しました。: %d\n", u32_Error); break; } else u32_Count = ps32_ClientCount[0]; if (u32_Count != u32_CountOld) { u32_CountOld = u32_Count; printf("\n現在の接続数 = %d\n", u32_Count); printf("# h_Connect (peer IP)\n"); for (DWORD i = 0; i<u32_Count; i++) { char s8_IP[20]; sprintf(s8_IP, "%s","123456789012345"); FormatIP(ps32_ClientIP[i], s8_IP); printf("%d %d (%s)\n", i, ph_Client[i], s8_IP); } } for (DWORD i = 0; i<u32_Count; i++) { u32_Error = cNetEventsProcDLL::ReadFromDouble(ph_Client[i], pd_Buf, u32_BufLen, ps32_ReadLen); if (u32_Error) { printf("ReadFromDouble(%d...)エラーが発生しました。: %d\n", ph_Client[i], u32_Error); } if (ps32_ReadLen[0]) { printf("ReadFromDouble(%d...)は%d double values\nを読んでいます。", ph_Client[i], ps32_ReadLen[0]); printf("\nReadFromDouble(%d...)データを読み込みました: %9.5f\n", ph_Client[i], pd_Buf[0]); } if (ps32_ReadLen[0]) { u32_Error = cNetEventsProcDLL::SendToDouble(ph_Client[i], pd_Buf, ps32_ReadLen[0]); if (u32_Error) { printf("SendToDouble(%d...)エラーが発生しました。: %d\n", ph_Client[i], u32_Error); } else { printf("SendToDouble(%d...) は %d double values\nを送信しました。", ph_Client[i], ps32_ReadLen[0]); printf("SendToDouble(%d...)送信したデータ: %9.5f\n",ph_Client[i], pd_Buf[0]); } } } } u32_Error = cNetEventsProcDLL::ServerClose(); if (u32_Error) { printf("\nServerClose()接続停止に失敗しました。: %d\n", u32_Error); return 1; } else printf("\nServerClose() fine...\n"); Sleep(10000); return 0; } BOOL FormatIP(DWORD u32_IP, char* s8_IP) { DWORD u32_Len = strlen(s8_IP); if ( u32_Len < 15) return FALSE; BYTE* pu8_Addr = (BYTE*)&u32_IP; sprintf(s8_IP,"%d.%d.%d.%d",pu8_Addr[0], pu8_Addr[1], pu8_Addr[2], pu8_Addr[3]); return TRUE; }
デモを稼働させるには、以下のことが必要です。:
Client.mq4ファイルをターミナルデータ・フォルダ"MetaTrader 4\experts\"に置いて、ローカルIP(前例1.2.1で取得したもの)を文字列に割り当ててください。
string ps8_ServerIP = "192.168.1.5";
次はコンパイルしてください。C++サーバーをもう1つのコンピューターで稼働させる場合は、 このコンピューターのグローバルIPをここにペーストしてください。起動している全てのファイアウォールを終了するのを忘れないでください。
マイクロソフト・ビジュアル・スタジオ2010ウルティメイトでクライアントプロジェクトを開き、リリース環境設定を使って構築してください。別のIDEでプロジェクトを構築するのならば、NetEventsProcDLL.libモジュールを、編集者のために追加エントリーとして特定することを忘れないでください(次をコンパイルします: /EHsc/link NetEventsProcDLL.lib)。
EchoServer.exe C++サーバーを起動してください。全てが正常に行われると、コンソールは次のメッセージを表示します。: "ServerOpen()OK.サーバーを開いて接続しています...。"いずれかのキーを押すと、サーバーをクローズしプログラムを終了します
メタトレーダー4・ターミナルのチャート上でClient.mq4クライアントを稼働させてください。
複数のチャートで、同時にクライアントを稼働させることができます。別のターミナルでも、別のコンピューターでも稼働させることができます。
C++サーバーとメタトレーダー4・クライアントがどのように機能するか見てください。C++サーバーコンソールのいずれかのキーを押すと、C++プログラムはサーバーをクローズして終了します
1.2.3. メタトレーダー4・エキスパートアドバイザー・インディケーター(エキスパートアドバイザー・サーバー)&メタトレーダー4クライアント・インディケーター
エキスパートアドバイザー・インディケーター(エキスパートアドバイザー・サーバー)はクライアント・インディケーターをインディケーター値と一緒に提供します。このケースでは、スタンダード・iEnvelops(...)インディケーターの値です。この例は"確かな"インディケーター値を全利用者のクライアントに割り当てる実際の値を持つことができます。
以下はこの考え方のイメージです。
以下に、iEnvelops(...) インディケーター値のプロバイダーの役割をおこなう、メタトレーダー4・エキスパートアドバイザーServerSendInd.mq4のソースコードを紹介します。
//+---------------------------------------------------------------------------+ //| ServerSendInd.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ ru/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ServerOpen(int s32_Port); // in */ /*int ServerClose(); */ /*int GetAllConnections(int& ph_Client[], // out - int ph_Client[62] int& ps32_ClientIP[], // out - int ps32_ClientIP[62] int& ps32_ClientCount[]); // out - int ps32_ClientCount[1] */ /*int ReadFromString(int h_Client, // in string ps8_ReadBuf, // in int s32_ReadBufLen, // in - charエレメントにおけるReadBuf 文字列サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - charエレメントにおける実読込データのカウント */ /*int SendToDouble(int h_Client, // in double& pd_SendBuf[], // in int s32_SendBufLen); // in - doubleエレメントにおけるSendBuf[] アレイ・サイズ */ // グローバル変数 int s32_Error; int i; int s32_Port = 2000; bool b_ServerOpened = false; // for GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount) int ph_Client [62]; int ps32_ClientIP [62]; int ps32_ClientCount[1 ]; // for int ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen) string ps8_ReadBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; int s32_ReadBufLen; int ps32_ReadLen[1]; // for int SendToDouble(ph_Client[0], pd_SendBuf, s32_SendBufLen); #define BARS_COUNT 200 double pd_SendBuf [BARS_COUNT]; //BARS_COUNT/2ラインにつきバー2本 int s32_SendBufLen = BARS_COUNT; //+------------------------------------------------------------------+ //| エキスパート初期化関数 | //+------------------------------------------------------------------+ int init() { //---- s32_ReadBufLen = StringLen(ps8_ReadBuf); if (!b_ServerOpened) { s32_Error = ServerOpen(s32_Port); Print("ServerOpen()リターン: ",GetErrMsg(s32_Error)); if (s32_Error == OK) { b_ServerOpened = true; Print("サーバーを開いてクライアント接続を要求しています...。"); } } //---- return(0); } //+------------------------------------------------------------------+ //| エキスパート非初期化関数 | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ServerOpened) { s32_Error = ServerClose(); Print("ServerClose()リターン: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ServerOpened = false; } //---- return(0); } int start() { //---- if (!b_ServerOpened) return(0); s32_Error = GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (s32_Error != 0) { Print("GetAllConnections(...)エラーが発生しました。: ",GetErrMsg(s32_Error)); return(1); } Print("クライアント・カウント = ", ps32_ClientCount[0]); for (i = 0; i<ps32_ClientCount[0]; i++) { Print("h_Client = ", ph_Client[i], " クライアントIP = ", FormatIP(ps32_ClientIP[i])); s32_Error = ReadFromString(ph_Client[i], ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if (s32_Error != 0) { Print("ReadFromString(",ph_Client[i],")エラーが発生しました。: ", GetErrMsg(s32_Error)); continue; } if (ps32_ReadLen[0] > 0) { // ps8_ReadBuf = "EURUSD分" すなわち、 "シンボル+タイムフレーム" string Sym = StringSubstr(ps8_ReadBuf,0,6); int TimeFrame = StrToInteger(StringSubstr(ps8_ReadBuf,6,ps32_ReadLen[0]-6)); int k; for (k = 0; k<BARS_COUNT/2; k++) { while(true) { double UpperLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_UPPER, k); if (GetLastError() != 0) continue; else break; } while(true) { double LowerLine_k = iEnvelopes(Sym, TimeFrame, 14, MODE_SMA, 0, PRICE_CLOSE, 0.1, MODE_LOWER, k); if (GetLastError() != 0) continue; else break; } pd_SendBuf[k] = UpperLine_k; pd_SendBuf[k+BARS_COUNT/2] = LowerLine_k; } s32_Error = SendToDouble(ph_Client[i], pd_SendBuf, s32_SendBufLen); if (s32_Error != 0) { Print("SendToDouble(",ph_Client[i],")エラーが発生しました。: ", GetErrMsg(s32_Error)); continue; } } } //---- return(0); } //+------------------------------------------------------------------+
次に、 ServerSendInd.mq4エキスパートアドバイザーから、iEnvelops(...) インディケーター値を取得するクライアント・インディケーターClientIndicator.mq4のソースコードを紹介します。
//+---------------------------------------------------------------------------+ //| ClientIndicator.mq4 | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ #property copyright "Copyright © 2012, https://www.mql4.com/ en/users/more" #property link "https://www.mql4.com/ ru/users/more" #include <ImportNetEventsProcDLL.mqh> /*int ConnectTo(string ps8_ServerIP, // in - string ps8_ServerIP = "0123456789123456" int s32_Port, // in int& ph_Client[]); // out - int ph_Client[1] */ /* /*int ConnectClose(int h_Client); // in */ /*int SendToString(int h_Client, // in string ps8_SendBuf, // in int s32_SendBufLen); // in - charエレメントにけるSendBuf文字列サイズ */ /*int ReadFromDouble(int h_Client, // in double& pd_ReadBuf[], // in int s32_ReadBufLen, // in - doubleエレメントにおけるReadBuf[]アレイ・サイズ int& ps32_ReadLen[]); // out - int ps32_ReadLen[1] - doubleエレメントにおける実読込データのカウント */ // グローバル変数 int s32_Error; int i; // for int ConnectTo(ps8_ServerIP, s32_Port, ph_Client); // out - int h_Client[1] string ps8_ServerIP = "192.168.1.5"; // 固有ローカルIP int s32_Port = 2000; int ph_Client[1]; bool b_ConnectTo = false; // for int SendToString (h_Client, ps8_SendBuf, s32_SendBufLen) string ps8_SendBuf = "0123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789"; int s32_SendBufLen; // for int ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen); #define BARS_COUNT 200 double pd_ReadBuf [BARS_COUNT]; int s32_ReadBufLen = BARS_COUNT; int ps32_ReadLen[1]; string Indicator_Name = "Envelopes: "; //---- #property indicator_chart_window #property indicator_buffers 2 #property indicator_color1 Blue #property indicator_color2 Red double UpperLine[]; double LowerLine[]; //+------------------------------------------------------------------+ //| カスタムインディケーター初期化関数 | //+------------------------------------------------------------------+ int init() { //---- インディケーター SetIndexStyle (0,DRAW_LINE); SetIndexBuffer(0,UpperLine); SetIndexStyle (1,DRAW_LINE); SetIndexBuffer(1,LowerLine); s32_SendBufLen = StringLen(ps8_SendBuf); if (!b_ConnectTo) { s32_Error = ConnectTo(ps8_ServerIP, s32_Port, ph_Client); Print("ConnectTo(...) リターン: ",GetErrMsg(s32_Error)); Print("ConnectTo(...) ハンドル: ",ph_Client[0]); if (s32_Error == OK) { b_ConnectTo = true; Print("クライアントがサーバーに接続できました。: ",ps8_ServerIP); } } //---- return(0); } //+------------------------------------------------------------------+ //| カスタムインディケーター非初期化関数 | //+------------------------------------------------------------------+ int deinit() { //---- if (b_ConnectTo) { s32_Error = ConnectClose(ph_Client[0]); Print("ConnectClose(...) リターン: ",GetErrMsg(s32_Error)); if (s32_Error == OK) b_ConnectTo = false; } //---- return(0); } //+------------------------------------------------------------------+ //| カスタムインディケーター繰り返し関数 | //+------------------------------------------------------------------+ int start() { //---- if (!b_ConnectTo) return(0); string Sym = Symbol(); int TimeFrame = Period(); ps8_SendBuf = Symbol() + DoubleToStr(Period(),0); s32_Error = SendToString(ph_Client[0], ps8_SendBuf, StringLen(ps8_SendBuf)); if (s32_Error != 0) { Print("SendToString(", ph_Client[0],",...)エラーが発生しました。: ",GetErrMsg(s32_Error)); return (1); } s32_Error = ReadFromDouble(ph_Client[0], pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if (s32_Error != 0) { Print("ReadFromDouble(",ph_Client[0],"...) リターン: ", GetErrMsg(s32_Error)); return(1); } if (ps32_ReadLen[0] == 0) return (0); //-------------------------------------------------------------------- int Counted_bars = IndicatorCounted(); // 計算するバーの本数 i = Bars - Counted_bars - 1; // 計算しない最初のインデックス if (i > BARS_COUNT/2-1) i = BARS_COUNT/2-1; // たくさんのバーがあるなら詳細なカウントを計算 //----------------------------------------------------------------------- for (i = BARS_COUNT/2-1; i >= 0; i--) { UpperLine [i] = pd_ReadBuf[i]; LowerLine [i] = pd_ReadBuf[i+BARS_COUNT/2]; } return; } // int start()終了 //--------------------------------------------------------------------
デモを稼働させるには、以下のことが必要です。:
ServerSendInd.mq4ファイルをターミナルデータ・フォルダ"МetaТrader 4\experts\"に置いてコンパイルしてください。
ClientIndicator.mq4ファイルをターミナルデータ・フォルダ"МetaТrader 4\experts\indicators\"に置いてローカルIP(前例1.2.1で取得したもの)を文字列に割り当ててください。
string ps8_ServerIP = "192.168.1.5";
ServerSendInd をもう1つのコンピューターで稼働させる場合は、 このコンピューターのグローバルIPをここにペーストしてください。起動している全てのファイアウォールを終了するのを忘れないでください。コンパイルしてください。
ServerSendIndを稼働してください。全てが正常に行われると、コンソールは次のメッセージを表示します。: "ServerOpen()OK.サーバーを開いて接続しています...。"
メタトレーダー4・ターミナルのチャート上でClientIndicator.mq4インディケーターを稼働させてください。2本のインディケーター・ラインがチャートに表示されます。スタンダード・エンベロープ・インディケーターを同チャートで使用すると、このインディケーターのラインが標準インディケーターのラインと重なってしまうので注意してください。
複数のチャートで、同時にClientIndicatorインディケータ稼働させることができます。別のターミナルでも、別のコンピューターでも稼働させることができます。
- ServerSendIndサーバーとClientIndicatorがどのように機能するか見てください。ClientIndicatorを表示しているチャートのタイムフレームを変えてみてください。ServerSendInd・サーバーが、ただちにそのタイムフレームに必要なインディケーター値を送信するようセットアップされるでしょう。
2. DLLインターフェースについて
このセクションんでは、全DLL関数と、それらを呼び出すのに必要なパラメーターについて詳しく説明します。全ての関数は実行が成功するとゼロを返します。失敗すると関数はwinsock2 API エラーコードを返します。エクスポートした全てのDLL関数は、C++クラスcNetEventsProcDLL.h宣言で与えられます。そのファイルのソースコードを紹介します。
//+---------------------------------------------------------------------------+ //| cNetEventsProcDLL.h | //| Copyright © 2012, https://www.mql4.com/ en/users/more | //| tradertobe@gmail.com | //+---------------------------------------------------------------------------+ //--- cNetEventsProcDLL.h #pragma once #define EXPFUNC __declspec(dllexport) //--- class cNetEventsProcDLL { public: static BOOL MessageDLL_PROCESS_ATTACH(void); static BOOL MessageDLL_PROCESS_DETACH(void); //--- static EXPFUNC int __stdcall ConnectTo(char *ps8_ServerIP, // in - ps8_ServerIP = "0123456789123456" int s32_Port, // in int* ph_Client); // out - int ph_Client[1] //--- static EXPFUNC int __stdcall ConnectClose(int h_Client); // in //--- static EXPFUNC int __stdcall ServerOpen(int s32_Port); // in //--- static EXPFUNC int __stdcall GetAllConnections(int* ph_Client, // out - int ph_Client[62] int* ps32_ClientIP, // out - int ps32_ClientIP[62] int* ps32_ClientCount); // out - int ps32_ClientCount[1] //--- static EXPFUNC int __stdcall DisconnectClient(SOCKET h_Client); // in //--- static EXPFUNC int __stdcall ServerClose(); //--- static EXPFUNC int __stdcall SendToInt(SOCKET h_Client, // in int *ps32_SendBuf, // in int s32_SendBufLen); // in - intエレメントにおけるSendBuf[] アレイ・サイズ //--- static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client, // in double* pd_SendBuf, // in int s32_SendBufLen); // in - doubleエレメントにおけるSendBuf[] アレイ・サイズ //--- static EXPFUNC int __stdcall SendToString(SOCKET h_Client, // in char* ps8_SendBuf, // in INT s32_SendBufLen); // charエレメントのけるSendBuf文字列サイズ //--- static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client, // in int *ps32_ReadBuf, // in int s32_ReadBufLen, // intエレメントにおけるReadBuf[]アレイ・サイズ int *ps32_ReadLen); // out - int ps32_ReadLen[1] - intエレメントにおける実読込データのカウント //--- static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client, // in double *pd_ReadBuf, // in int s32_ReadBufLen, // doubleエレメントにおけるReadBuf[]アレイ・サイズ int *ps32_ReadLen); // out - int ps32_ReadLen[1] - doubleエレメントにおける実読込データのカウント //--- static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client, // in char *ps8_ReadBuf, // in int s32_ReadBufLen, // charエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - charエレメントにおける実読込データのカウント //--- protected: static DWORD SendTo(SOCKET h_Client,char *ps8_SendBuf,INT s32_SendBufLen); static DWORD ReadFrom(SOCKET h_Client,char *ps8_ReadBuf,INT s32_ReadBufLen,INT &s32_ReadLen); };
ここから、このファイルの表示規則における、すべてのDLL関数について考慮していきましょう。
ConnectTo - サーバーに接続確率を要求
static EXPFUNC int __stdcall ConnectTo(char* ps8_ServerIP, // in - ps8_ServerIP = "0123456789123456" int s32_Port, // in int* ph_Client); // out - int ph_Client[1]
関数のパラメータ:
char* ps8_ServerIP - 接続したいサーバーのIPアドレス(例:"93.127.110.162")。サーバーがローカルであるなら、IPを明示し、"127.0.0.1"ではなく例1.2.1で紹介したように、取得したIPを明示してください。
int s32_Port - サーバーが"聞いている"ポートナンバーです。
int* ph_Client - 関数の終了に成功すると、接続ハンドルはこの変数に置かれます。このハンドルは、接続のための一連のオペレーションにおいて、常に利用されるものです。
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
DWORD u32_Error = cNetEventsProcDLL::ConnectTo(SERVER_IP, PORT, ph_Client); if (u32_Error) { printf("\nConnectTo(...) 接続に失敗しました。: %d\n", u32_Error); return 1; } else printf("\nConnectTo(...) OK, ph_Client[0] = : %d\n", ph_Client[0]); int h_Client = ph_Client[0];
ConnectClose - サーバーに接続停止を要求
static EXPFUNC int __stdcall ConnectClose(int h_Client); // in
関数のパラメータ:
int h_Client - クローズされる接続ハンドル成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。
C++例:
int u32_Error = cNetEventsProcDLL::ConnectClose(h_Client); if (u32_Error) printf("\nConnectClose(...) 接続停止に失敗しました。: %d\n", u32_Error); else printf("\nConnectClose(...) OK.\n");
ServerOpen - サーバー生成を要求
static EXPFUNC int __stdcall ServerOpen(int s32_Port); //in
関数のパラメータ:
int s32_Port - クライアント要求を待って、サーバーが"聞いている"ポートナンバー成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。
C++例:
int u32_Error = cNetEventsProcDLL::ServerOpen(s32_Port); if (u32_Error) { printf("\nServerOpen()を開くことに失敗しました。: %d\n", u32_Error); return 1; } else printf("\nServerOpen()を開き、接続しています...\n");
GetAllConnections - 現在の全接続に関する情報を取得することをサーバーに要求
static EXPFUNC int __stdcall GetAllConnections(int* ph_Client, // out - int ph_Client[62] int* ps32_ClientIP, // out - int ps32_ClientIP[62] int* ps32_ClientCount); // out - int ps32_ClientCount[1]
関数のパラメータ:
int ph_Client[62] - サーバーが現在全ての接続のハンドルを置く出力アレイ
int ps32_ClientIP[62] - サーバーが現在全ての接続のIPアドレスを置く出力アレイこれらのアドレスを"92.127.110.161"のようなスタンダード・フォーマットにコンバートするためには、メタトレーダー4・エキスパートアドバイザーのstring FormatIP(int IP)関数か、C++プログラム例にある同じ役割をする関数を利用してください。アレイサイズのナンバー62は、非同時に明示します。1つのサーバーにつき接続可能(クライアント)数の上限を示します。
int* ps32_ClientCount - サーバーは現在の接続数をこの変数に代入します。接続数とは、上記のアレイにおけるエレメント数のことです。
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
int ph_Client[62]; int ps32_ClientIP[62]; int ps32_ClientCount[1]; int u32_Error = cNetEventsProcDLL::GetAllConnections(ph_Client, ps32_ClientIP, ps32_ClientCount); if (u32_Error) { printf("\nGetAllConnections(...)接続に失敗しました。: %d\n", u32_Error); } else int u32_Count = ps32_ClientCount[0];
DisconnectClient - サーバーにクライアントの接続停止を要求
static EXPFUNC int __stdcall DisconnectClient(int h_Client); // in
関数のパラメータ:
int h_Client - クローズされる接続ハンドル成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。
C++例:
int u32_Error = cNetEventsProcDLL::DisconnectClient(h_Client); if (u32_Error) printf("\nDisconnectClient(...)エラーが発生しました。: %d\n", u32_Error); else printf("\nDisconnectClient(...) OK.\n");
ServerClose - サーバー停止を要求
static EXPFUNC int __stdcall ServerClose();
サーバーを停止すると、現在の全ての接続がクローズするので、どのクライアントも"接続していません"のリターン・コードをオペレーション結果として取得します。成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
int u32_Error = cNetEventsProcDLL::ServerClose(); if (u32_Error) printf("\nServerClose()接続停止に失敗しました。: %d\n", u32_Error); else printf("\nServerClose() OK.\n");
WSAEWOULDBLOCK - 送信に成功しました。データは受信者にまだ届いていませんが、しばらくすると届くでしょう。ユーザーは何もする必要はありません。
WSA_IO_PENDING - データ送信は完了ませんでした。あとでデータ送信をやり直してください。これが一般的な状況です。関数の実行は成功したと考えることができます。
SendToInt - 現在の接続を経由して、データブロック(intタイプアレイ)の送信を要求
static EXPFUNC int __stdcall SendToInt(SOCKET h_Client, // in int* ps32_SendBuf, // in int s32_SendBufLen); // in - intエレメントにおけるSendBuf[] アレイ・サイズ
関数のパラメータ:
SOCKET h_Client - 現接続ハンドル
int ps32_SendBuf[s32_SendBufLen] - クライアントへ送信しなければならないシングルブロック(intタイプ・アレイ)
int s32_SendBufLen - アレイサイズ
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
int ps32_SendBuf[200]; int s32_SendBufLen=200; int u32_Error = cNetEventsProcDLL::SendToInt(h_Client, ps32_SendBuf, s32_SendBufLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...)リターン: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> データは次のFD_WRITEイベント後に送信されます。何もしないでください。\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...)リターン: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING ->エラー:以前に送信したオペレーションはまだ処理中です。このデータは送信されません。しばらく経ってから試してみてください。\n"); break; default: printf("\nSendTo(%d...)重大なエラーが発生しました。: %d\n",h_Client, u32_Error); break; };
SendToDouble - 現在の接続を経由して、データブロック(doubleタイプアレイ)の送信を要求
static EXPFUNC int __stdcall SendToDouble(SOCKET h_Client, // in double* pd_SendBuf, // in int s32_SendBufLen); // in - intエレメントにおけるSendBuf[] アレイ・サイズ
関数のパラメータ:
SOCKET h_Client - 現接続ハンドル
double pd_SendBuf[s32_SendBufLen] - クライアントへ送信しなければならないシングルブロック(doubleタイプ・アレイ)
int s32_SendBufLen - アレイサイズ
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
double pd_SendBuf[200]; int s32_SendBufLen=200; int u32_Error = cNetEventsProcDLL::SendToDouble(h_Client, pd_SendBuf, s32_SendBufLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...)リターン: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> データは次のFD_WRITEイベント後に送信されます。何もしないでください。\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...)リターン: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING ->エラー:以前に送信したオペレーションはまだ処理中です。このデータは送信されません。しばらく経ってから試してみてください。\n"); break; default: printf("\nSendTo(%d...)重大なエラーが発生しました。: %d\n",h_Client, u32_Error); break; };
SendToString - 現在の接続を経由して、データブロック(charタイプアレイ)の送信を要求
static EXPFUNC int __stdcall SendToString(SOCKET h_Client, // in char* ps8_SendBuf, // in int s32_SendBufLen); // in - intエレメントにおけるSendBuf[] アレイ・サイズ
関数のパラメータ:
SOCKET h_Client - 現接続ハンドル
char ps8_SendBuf[s32_SendBufLen] - クライアントへ送信しなければならないシングルブロック(charタイプ・アレイ)
int s32_SendBufLen - アレイサイズ
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
char ps8_SendBuf[200]; int s32_SendBufLen=200; int u32_Error = cNetEventsProcDLL::SendToString(h_Client, ps8_SendBuf, s32_SendBufLen); switch (u32_Error) { case 0: printf("\nSendTo(...) OK"); break; case WSAEWOULDBLOCK: printf("\nSendTo(%d...)リターン: WSAEWOULDBLOCK(%d)\n",h_Client, u32_Error); printf("\nWSAEWOULDBLOCK -> データは次のFD_WRITEイベント後に送信されます。何もしないでください。\n"); break; case WSA_IO_PENDING: printf("\nSendTo(%d...)リターン: WSA_IO_PENDING(%d)\n",h_Client, u32_Error); printf("\nWSA_IO_PENDING ->エラー:以前に送信したオペレーションはまだ処理中です。このデータは送信されません。しばらく経ってから試してみてください。\n"); break; default: printf("\nSendTo(%d...)重大なエラーが発生しました。: %d\n",h_Client, u32_Error); break; };
ReadFromInt - 現在の接続を経由して、データブロック(intタイプアレイ)の受信を要求
static EXPFUNC int __stdcall ReadFromInt(SOCKET h_Client, // in int* ps32_ReadBuf, // in int s32_ReadBufLen, // intエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - intエレメントにおける実読込データのカウント
関数のパラメータ:
SOCKET h_Client - 現接続ハンドル
int ps32_ReadBuf[s32_ReadBufLen] - データブロックを受信するためのintタイプアレイ
int s32_ReadBufLen - アレイ受信サイズ
int* ps32ReadLen - この変数はデータブロックのリアル・サイズを持ちます。ブロックを受信して、ps32_ReadBuf[]アレイに代入します。受信アレイサイズがデータブロックを受け取るのに十分でなければ、この変数はサイズを維持し、データブロックの受け取りをマイナス信号で要求します。ブロックはスタック内にとどまり、リターンコードがゼロになるでしょう。明示したハンドルでクライアント・スタック内にデータがない場合は、この変数はゼロになり、リターンコードも同じようにゼロになるでしょう。
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
int ps32_ReadBuf[2000]; int s32_ReadBufLen=2000; int ps32_ReadLen[1]; int u32_Error = cNetEventsProcDLL::ReadFromInt(h_Client, ps32_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if(u32_Error) printf("ReadFromInt(%d...)エラーが発生しました。: %d", h_Client, u32_Error); else if(ps32_ReadLen[0] >= 0) printf("ReadFromInt(%d...)OK. %d intナンバーを読み込みました。", h_Client, ps32_ReadLen[0]); else printf("ReadFromInt(%d...) OK. ReadBufは少なくとも%d intナンバーサイズでなければいけません。", h_Client, -ps32_ReadLen[0]);
ReadFromDouble - 現在の接続を経由して、データブロック(doubleタイプアレイ)の受信を要求
static EXPFUNC int __stdcall ReadFromDouble(SOCKET h_Client, // in double* pd_ReadBuf, // in int s32_ReadBufLen, // doubleエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - doubleエレメントにおける実読込データのカウント
関数のパラメータ:
SOCKET h_Client - 現接続ハンドル
double pd_ReadBuf[s32_ReadBufLen] - データブロックを受信するためのdoubleタイプアレイ
int s32_ReadBufLen - アレイ受信サイズ
int* ps32ReadLen - この変数はデータブロックのリアル・サイズを持ち、ps32_ReadBuf[]アレイに代入します。受信アレイサイズがデータブロックを受け取るのに十分でなければ、この変数はサイズを維持し、データブロックの受け取りをマイナス信号で要求します。ブロックはスタック内にとどまり、リターンコードがゼロになるでしょう。明示したハンドルでクライアント・スタック内にデータがない場合は、この変数はゼロになり、リターンコードも同じようにゼロになるでしょう。
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
double ps32_ReadBuf[2000]; int s32_ReadBufLen = 2000; int ps32_ReadLen[1]; int u32_Error = cNetEventsProcDLL::ReadFromDouble(h_Client, pd_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if(u32_Error) printf("ReadFromDouble(%d...)エラーが発生しました。: %d", h_Client, u32_Error); else if(ps32_ReadLen[0] >= 0) printf("ReadFromDouble(%d...)OK, %d doubleナンバーを読み込みました。", h_Client, ps32_ReadLen[0]); else printf("ReadFromDouble(%d...)OK. ReadBufは少なくとも%d doubleナンバーサイズでなければいけません。", h_Client, -ps32_ReadLen[0]);
ReadFromString - 現在の接続を経由して、データブロック(charタイプアレイ)の受信を要求
static EXPFUNC int __stdcall ReadFromString(SOCKET h_Client, // in char* ps8_ReadBuf, // in int s32_ReadBufLen, // charエレメントにおけるReadBuf[]アレイ・サイズ int* ps32_ReadLen); // out - int ps32_ReadLen[1] - charエレメントにおける実読込データのカウント
関数のパラメータ:
SOCKET h_Client - 現接続ハンドル
char ps8_ReadBuf[s32_ReadBufLen] - データブロックを受信するためのcharタイプアレイ
int s32_ReadBufLen - アレイ受信サイズ
int* ps32ReadLen - この変数はデータブロックのリアル・サイズを持ち、ps32_ReadBuf[]アレイに代入します。受信アレイサイズがデータブロックを受け取るのに十分でなければ、この変数はサイズを維持し、データブロックの受け取りをマイナス信号で要求します。ブロックはスタック内にとどまり、リターンコードがゼロになるでしょう。明示したハンドルでクライアント・スタック内にデータがない場合は、この変数はゼロになり、リターンコードも同じようにゼロになるでしょう。
成功したら、関数はtrueを返し、失敗するとwinrock2 API エラーコードを返します。C++例:
char ps8_ReadBuf[2000]; int s32_ReadBufLen = 2000; int ps32_ReadLen[1]; int u32_Error = cNetEventsProcDLL::ReadFromString(h_Client, ps8_ReadBuf, s32_ReadBufLen, ps32_ReadLen); if(u32_Error) printf("ReadFromStrung(%d...)エラーが発生しました。: %d", h_Client, u32_Error); else if(ps32_ReadLen[0] >= 0) printf("ReadFromString(%d...)OK, %d charを読み込みました。", h_Client, ps32_ReadLen[0]); else printf("ReadFromString(%d...) OK. ReadBufは少なくとも%d charナンバーサイズでなければいけません。", h_Client, -ps32_ReadLen[0]);
次の関数グループは、現在の接続を経由してデータを直接交換するようにします。それぞれの接続で、データを非同時性モードで受信します。つまり、受信者からの返信をおこなわない、ということです。
全データは交換独立ユニット、即ちブロックで送受信されます。受信者の全ブロックは、FIFOスタックに集積されます。受信者はスタックからこれらのブロックをいつでも回収することができます。交換関数はシングルブロックとして機能します。
データ送信機能において、送信が成功しても、リターンコードがゼロではなく、以下のような値になる場合があります。
ほかのリターンコードはユーザーエラーであることを示します。
3. プロジェクト実行
添付のNetServerClient.zipアーカイブには、2つのマイクロソフト・ビジュアル・スタジオ2010ウルティメイト・プロジェクがあります。
- NetEventsProc - NetEventsProc.exeを作成
- NetEventsProcDLL - NetEventsProcDLL.dllを作成
ソースコードについて詳しく説明しています。コード実行の詳細を知ることができ、好きなように自分のニーズに合わせてプロジェクトをカスタマイズできます。
NetEventsProc.exeは非同期ソケットを使ってサーバーとクライアントをインプリメントします。非同期モードにソケットをスイッチするためには、非同期モードで稼働可能な1つの方法が利用できます。ソケットをWSAEventSelect(h_Socket, h_Event, FD_ALL_EVENTS)ネットワークイベントに結び付けるのです。
本稿が読者の関心を得られるなら、次の記事では全ての実行方法について詳しく見ていきたいと考えています。とりあえず、今回はこれでおわりです。再度述べますが、このプロジェクトは、偉大なる師Elmueの教えに基づいています。
4. おわりに
本稿がメタトレーダー4・エキスパートアドバイザー・ターミナルと第3のアプリケーションとの情報交換の問題を、アプリケーションのロケーションに関わらず、解決することを願っています。インストールしたターミナルにある、ローカルコンピューターであれ、リモートコンピューターであれ、解決するでしょう。本稿には、たくさんのスパゲッティ・コードはないと思います。使用したDLL関数のプロセスはとてもシンプルで明かり易いと思います。少なくとも、私はそのようになることを心がけて作成しました。
さらに1つ大事なことを書いておきます。
ほとんどすべてのコンピューターは、ローカルネットワーク(LAN)のノードです。あなたが1つだけコンピューターを持っていても、それは1つのコンピューターで構成されるLANノードと言えるでしょう。
ワイドエリア・ネットワーク(WAN)への接続は追加のハードウェア・デバイスを経由して行われます。ルーターやモデムなど、他の技術用語でよばれているものを経由してです。単純にルーターと呼ぶことが多いでしょう。このルーターに、プロバイダはグローバルIPアドレスを提供します。
ルーターは、WANで機能する際に、グローバルIPアドレスの使用を許可し、ローカルネットワークからのWAN接続を助けるなどしてセキュリティの問題を解決します。しかし同時に、2コンピューター間のダイレクトPeer-To-Peer接続の可能性があるWANの初期読取機能を捻じ曲げることになります。
この影響は、ルーターがネットワーク・アドレス・トランスレーション(NAT)と呼ばれるパフォーマンスをおこなうことが原因で起きます。ネットワーク・アドレスは、<プロトコル、IP、ポート> の組み合わせで表せられます。この組み合わせの各要素はルーターによって変更できます。全て特定のルーターモデルに起因します。
このケースでは、LANからWANへアクセスしたコンピューターは、真のWANがもたらす全ての利点を持つことはできません。ルーターの大多数が、要求されるとLANからグローバルWANネットワークを宣言するLANクライアントのネットワーク・アドレスの記憶を許可する、OUTBOUNDという機能を持っています。
この機能のおかげで、ルーターはクライアントへ、クライアント要求のレスポンスとして、受け取った全情報を送ることができるのです。だからLANクライアントはWANサーバーに接続できるのです。しかし、このことは常に正しくはありません。セキュリティの問題や、ネットワーク・アドレスがハードウェアにブロックされる動作規則などがあるからです。
したがって、LANコンピューターでサーバーを構築するには、ポート・フォワーディングと呼ばれる設定をしなければいけません。本稿の例では、最も単純な例で、1つのLANコンピューターで必要なフォワード・ポート・ナンバー2000でした。これはあなた自身で、ブラウザ上でルーターミニサイトに接続したりしてみてください。プロフェッショナルな技術を磨いてください。このミニサイトはおそらく192.168.1.1で利用可能です。
WANを経由して情報交換することを望むなら、これらすべてのことを考慮してください。
次回の記事は、Peer-To-Peer (p2p)接続タイプについて記述します。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/1361





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