Wine環境のMT5でWinAPIを使ったHTTP通信を実装する方法

Wine環境のMT5でWinAPIを使ったHTTP通信を実装する方法

はじめに

MetaTrader 5(MT5)は強力なトレーディングプラットフォームですが、Linux環境で使用する場合はWineを介して実行することになります。この環境でのHTTP通信の実装は通常よりも複雑です。本記事では、Wine上のMT5でWinAPIを使って安定したHTTP通信を行う方法を解説します。

課題と解決策

MT5のストラテジーテスターでも動作するHTTP通信を実装する場合、通常のWebRequest()関数は使用できません。代わりにWinAPIを直接呼び出す方法を使いますが、Wine環境では特有の問題が発生します。

ここでは、実際に動作するコードと、遭遇した問題の解決方法を紹介します。

WinAPIのインポート

まず、必要なWinAPI関数をインポートします。Unicode版関数を使用することで、Wine環境での互換性が向上します。

#import "wininet.dll"
   int InternetOpenW(string agent, int accessType, string proxy, string proxyBypass, int flags);
   int InternetConnectW(int hInternet, string server, int port, string user, string password, int service, int flags, int context);
   int HttpOpenRequestW(int hConnect, string verb, string objectName, string version, string referer, string acceptTypes, int flags, int context);
   bool HttpSendRequestW(int hRequest, string headers, int headersLength, char &optional[], int optionalLength);
   bool InternetReadFile(int hFile, uchar &buffer[], int numberOfBytesToRead, int &numberOfBytesRead);
   bool InternetCloseHandle(int hInternet);
   int GetLastError(void);
#import

// 定数定義
#define INTERNET_OPEN_TYPE_DIRECT 1
#define INTERNET_SERVICE_HTTP 3
#define INTERNET_DEFAULT_HTTP_PORT 80

通常のANSI版関数(InternetOpenAなど)ではなく、Unicode版関数(InternetOpenWなど)を使用することがポイントです。

HTTP通信の実装

次に、HTTPリクエストを送信して応答を受け取る関数を実装します。

string SendHttpRequest(string method, string server, string path, string headers, char &body[])
{
   string response = "";

   // インターネット接続を開く
   int hInternet = InternetOpenW("MQL5Agent", INTERNET_OPEN_TYPE_DIRECT, "", "", 0);

   if(hInternet == 0)
   {
      int errorCode = GetLastError();
      Print("Error: InternetOpenW failed with error code ", errorCode);
      return "";
   }

   // サーバーに接続
   int hConnect = InternetConnectW(hInternet, server, INTERNET_DEFAULT_HTTP_PORT, "", "", INTERNET_SERVICE_HTTP, 0, 0);

   if(hConnect == 0)
   {
      int errorCode = GetLastError();
      Print("Error: InternetConnectW failed with error code ", errorCode);
      InternetCloseHandle(hInternet);
      return "";
   }

   // HTTPリクエストを作成
   int hRequest = HttpOpenRequestW(hConnect, method, path, "HTTP/1.1", "", "", 0, 0);

   if(hRequest == 0)
   {
      int errorCode = GetLastError();
      Print("Error: HttpOpenRequestW failed with error code ", errorCode);
      InternetCloseHandle(hConnect);
      InternetCloseHandle(hInternet);
      return "";
   }

   // リクエストを送信
   int bodyLength = ArraySize(body);
   bool success = HttpSendRequestW(hRequest, headers, StringLen(headers), body, bodyLength);

   if(!success)
   {
      int errorCode = GetLastError();
      Print("Error: HttpSendRequestW failed with error code ", errorCode);

      // エラーコードの詳細
      if(errorCode == 12002) Print("タイムアウトが発生しました");
      else if(errorCode == 12007) Print("サーバー名の解決ができませんでした");
      else if(errorCode == 12029) Print("接続に失敗しました");
      else if(errorCode == 12030) Print("接続が切断されました");
      else if(errorCode == 12152) Print("HTTPサーバーエラー");

      InternetCloseHandle(hRequest);
      InternetCloseHandle(hConnect);
      InternetCloseHandle(hInternet);
      return "";
   }

   // レスポンスを読み込み
   uchar buffer[4096];
   int bytesRead = 0;

   do
   {
      success = InternetReadFile(hRequest, buffer, 4096, bytesRead);
      if(!success)
      {
         int errorCode = GetLastError();
         Print("Error: InternetReadFile failed with error code ", errorCode);
         break;
      }

      if(bytesRead > 0)
      {
         string chunk = CharArrayToString(buffer, 0, bytesRead, CP_UTF8);
         response += chunk;
      }
   }
   while(bytesRead > 0);

   // ハンドルを閉じる
   InternetCloseHandle(hRequest);
   InternetCloseHandle(hConnect);
   InternetCloseHandle(hInternet);

   return response;
}

テスト実装

実際に使用するためのシンプルなEAの例です。公開テスト用APIエンドポイント(JSONPlaceholder)にリクエストを送信します。

int OnInit()
{
   // JSONPlaceholderのテストエンドポイント
   string apiServer = "jsonplaceholder.typicode.com";
   string apiPath = "/todos/1";

   // シンプルなヘッダー
   string headers = "Accept: application/json\r\n";

   // 空のボディを明示的に初期化(GETリクエスト用)
   char emptyBody[];
   ArrayResize(emptyBody, 1);
   emptyBody[0] = 0;

   Print("OnInit: テストAPIにリクエストを送信中...");

   // GETリクエストを送信
   string response = SendHttpRequest("GET", apiServer, apiPath, headers, emptyBody);

   // レスポンスをチェック
   if(response == "")
   {
      Print("OnInit: データ取得に失敗しました。");
      return INIT_FAILED;
   }

   Print("OnInit: 取得したデータ: ", response);

   // JSONレスポンスの簡易解析
   if(StringLen(response) > 0)
   {
      if(StringFind(response, "\"userId\"") >= 0 &&
         StringFind(response, "\"id\"") >= 0 &&
         StringFind(response, "\"title\"") >= 0)
      {
         Print("OnInit: APIリクエストが成功し、正しいJSONレスポンスを受信しました。");
         return INIT_SUCCEEDED;
      }
   }

   Print("OnInit: 予期しないレスポンス形式です。");
   return INIT_FAILED;
}

発生した問題と解決法

問題1: HttpSendRequestAのエラー

最初の実装では、HttpSendRequestA関数でエラーコード0が返されていました。

2025.04.18 10:35:43.370 2025.01.01 00:00:00 Error: HttpSendRequestA failed with error code 0, Server: jsonplaceholder.typicode.com, Path: /todos/1

解決策:

  1. ANSI版関数からUnicode版関数に変更(A接尾辞からW接尾辞へ)
  2. パラメータ型を修正(特に空のボディの初期化方法)
  3. エラー処理の詳細化

問題2: Wine環境での互換性

Wine環境ではWindowsネイティブAPIとの互換性に問題が生じることがあります。

解決策:

  1. 空の文字列にはNULLではなく""を使用
  2. パラメータを明示的に初期化
  3. デバッグ情報を詳細に出力

まとめ

Wine環境でのMT5からHTTP通信を行うには、いくつかの特別な配慮が必要です。Unicode版のWinAPI関数を使用し、パラメータの型と初期化に注意することで、安定したHTTP通信が実現できます。

この方法を使えば、MT5のストラテジーテスターでも外部APIからデータを取得できるため、よりリアルなバックテストや、外部データを活用したEAの開発が可能になります。

特にWebRequestが使えない環境や、よりカスタマイズされたHTTP通信が必要な場合に、このWinAPIを使ったアプローチは非常に有効です。