MetaTrader + C++: DLL

出典とメンテナンス注記(2026 年 9 月): 投稿 1972 の 2019 年エクスポートにはフロントマターと空の本文しかなく、保存すべき過去のチュートリアルはありません。本記事は、現在の MetaQuotes と Microsoft の一次資料に基づいてタイトルから再構成したことを明示しています。MetaTrader 5/MQL5 からオフライン算術処理を呼ぶ例であり、取引の発注、DLL 権限の回避、ブローカーへの接続、利益の約束は行いません。

DLL はターミナルのプロセス内でネイティブコードを実行します。誤ったシグネチャ、古い依存関係、範囲外書き込み、ブロックする呼び出し、信頼できないバイナリは、ターミナルを停止または侵害する可能性があります。DLL は MQL やプラットフォームの安全策を迂回する近道ではなく、小さくバージョン管理されたシステムインターフェースとして扱ってください。

DLL を使わないほうがよい場合

MQL5 または EX5 ライブラリで表現できる処理なら、そちらを優先します。次の場合はネイティブ DLL を避けてください。

  • 目的が通常の算術、ファイル処理、MQL5 がすでに提供するデータ構造だけである。
  • リモートまたは MQL5 Cloud の Strategy Tester エージェントが必須である。これらは DLL 呼び出しを許可しない。
  • ライブラリのソース、発行者、依存関係、ビルド記録を検証できない。
  • C++ オブジェクト、STL コンテナー、生の所有権移転、コールバック、または MQL メモリへの長寿命ポインターが必要である。
  • 認証情報、口座の秘密、API トークン、秘密鍵をバイナリへ埋め込む必要がある。
  • 失敗や遅延が呼び出し元 MQL プログラムのスレッドを止め得る。

狭いネイティブ依存関係または計測済みのボトルネックに限って DLL を使います。この例では、取引判断、権限、シンボル状態、注文操作を DLL に入れません。

読み込み前に信頼と目に見える同意を扱う

MetaTrader 5 は DLL インポートを潜在的に危険と表示します。プラットフォームの Allow DLL imports は既定値を決め、アプリケーションの Dependencies タブは外部モジュールを表示して、そのアプリの権限を制御します。不要なときはプラットフォーム既定をオフにし、表示された依存関係を確認し、発行者と正確なファイルハッシュを検証した DLL だけに許可を与えます。

MQL5 はアーリーバインディングを使うため、宣言した DLL は OnStart()OnInit() より前に読み込まれることがあります。したがって、プログラム内の権限確認はログには有用ですが、プラットフォームの目に見える同意ダイアログを置き換えません。ダイアログを隠す、ターミナル設定を自動変更する、未知のプログラムのために DLL をグローバル有効化するよう促す、といったことはしないでください。

例のスクリプトは #property script_show_inputs を使ってプロパティ画面を表示します。AutoTrading はオフのままにします。このデモに取引呼び出しはなく、取引権限は不要です。

小さくバージョン管理された ABI を定義する

最初のインターフェースには、固定幅整数、double、呼び出し側所有の配列、明示的な長さ、呼び出し側所有の出力領域、整数ステータスコードを使います。ポインターや C++ オブジェクトを返してはいけません。

境界の論点この例の契約
呼び出し規約MetaQuotes がネイティブインポートに求める __stdcall
リンケージ/エクスポートextern "C"__declspec(dllexport)。最終名を DUMPBIN /EXPORTS で検証する。
アーキテクチャx64 MetaTrader 5 ターミナル用に x64 DLL をビルドする。32 ビット版は別成果物として個別に検査・テストする。
バージョンMtApiVersion()1 を返す。互換性のない変更では DLL ファイル名と API バージョンを更新する。
メモリすべての入出力バッファーを MQL が所有し、DLL は保持も解放もしない。
エラー0 が成功で、その他の安定した整数値が引数または数値エラーを示す。

ネイティブ実装

mt_safe_math.cpp として保存します。

#include <cmath>
#include <cstdint>

#if defined(_WIN32)
#define MT_API extern "C" __declspec(dllexport)
#define MT_CALL __stdcall
#else
#define MT_API extern "C"
#define MT_CALL
#endif

namespace {
constexpr std::int32_t kApiVersion = 1;
constexpr std::int32_t kMaxValues = 1'000'000;

enum Status : std::int32_t {
    kOk = 0,
    kInvalidArgument = 1,
    kInvalidOutput = 2,
    kNonFiniteValue = 3,
};
}

static_assert(sizeof(std::int32_t) == 4);
static_assert(sizeof(double) == 8);

MT_API std::int32_t MT_CALL MtApiVersion() noexcept {
    return kApiVersion;
}

MT_API std::int32_t MT_CALL MtMean(
    const double* values,
    std::int32_t count,
    double* out_mean) noexcept {
    if (out_mean == nullptr) {
        return kInvalidOutput;
    }
    *out_mean = 0.0;

    if (values == nullptr || count <= 0 || count > kMaxValues) {
        return kInvalidArgument;
    }

    double sum = 0.0;
    for (std::int32_t index = 0; index < count; ++index) {
        if (!std::isfinite(values[index])) {
            return kNonFiniteValue;
        }
        sum += values[index];
        if (!std::isfinite(sum)) {
            return kNonFiniteValue;
        }
    }

    *out_mean = sum / static_cast<double>(count);
    return kOk;
}

Windows 以外のマクロ分岐は、算術と検証ロジックを別の開発ホストで単体テストするためだけにあります。MetaTrader には Windows DLL ビルドが必要です。カスタム DllMain はありません。Microsoft は最小限に保つことを推奨しており、MSVC の /LD は既定のエントリーポイントを提供できます。

ポータブルなコアテスト

mt_safe_math_test.cpp として保存します。

#include <cassert>
#include <cstdint>
#include <limits>

#if defined(_WIN32)
#define MT_CALL __stdcall
#else
#define MT_CALL
#endif

extern "C" std::int32_t MT_CALL MtApiVersion() noexcept;
extern "C" std::int32_t MT_CALL MtMean(
    const double* values,
    std::int32_t count,
    double* out_mean) noexcept;

int main() {
    assert(MtApiVersion() == 1);

    double values[] = {1.0, 2.0, 3.0, 4.0};
    double mean = -1.0;
    assert(MtMean(values, 4, &mean) == 0);
    assert(mean == 2.5);

    mean = -1.0;
    assert(MtMean(nullptr, 4, &mean) == 1);
    assert(mean == 0.0);

    double invalid[] = {1.0, std::numeric_limits<double>::infinity()};
    assert(MtMean(invalid, 2, &mean) == 3);
    assert(MtMean(values, 4, nullptr) == 2);
}

GCC があるホストでは、ポータブルコアを次のようにテストできます。

g++ -std=c++17 -O2 -Wall -Wextra -Wpedantic -Werror 
  mt_safe_math.cpp mt_safe_math_test.cpp -o mt_safe_math_test
./mt_safe_math_test

このテストが通っても、Windows ABI、エクスポートテーブル、MetaTrader の権限フロー、MQL 宣言は検証されません。以下で別々に確認します。

MQL5 インポートとスモークスクリプト

MQL5/Scripts/MtSafeMathSmoke.mq5 として保存します。

#property script_show_inputs

#import "mt_safe_math_v1.dll"
int MtApiVersion();
int MtMean(double &values[], int count, double &out_mean);
#import

enum NativeStatus
  {
   MT_OK               = 0,
   MT_INVALID_ARGUMENT = 1,
   MT_INVALID_OUTPUT   = 2,
   MT_NON_FINITE_VALUE = 3
  };

void OnStart()
  {
   bool terminal_dlls=(bool)TerminalInfoInteger(TERMINAL_DLLS_ALLOWED);
   bool program_dlls=(bool)MQLInfoInteger(MQL_DLLS_ALLOWED);
   bool terminal_x64=(bool)TerminalInfoInteger(TERMINAL_X64);

   PrintFormat("dll-default=%s dll-program=%s x64=%s",
               terminal_dlls ? "true" : "false",
               program_dlls ? "true" : "false",
               terminal_x64 ? "true" : "false");

   if(!program_dlls || !terminal_x64)
     {
      Print("Stop: this smoke test requires explicit DLL consent and x64.");
      return;
     }

   int api_version=MtApiVersion();
   if(api_version!=1)
     {
      PrintFormat("Stop: incompatible native API version %d",api_version);
      return;
     }

   double values[]={1.0,2.0,3.0,4.0};
   double mean=0.0;
   ResetLastError();
   int status=MtMean(values,ArraySize(values),mean);
   int mql_error=GetLastError();

   PrintFormat("native-status=%d mean=%.8f mql-error=%d",
               status,mean,mql_error);
   if(status!=MT_OK)
      Print("Native calculation rejected the input; no other action was taken.");
  }

MQL プロトタイプはネイティブ側の引数順とサイズに完全一致させます。配列は参照で渡し、長さを別引数で渡します。MQL のインポート構文ではネイティブの const を表現できませんが、DLL は配列を読み取り専用として扱います。

Windows 成果物をビルド、検査、識別する

x64 Native Tools Command Prompt for Visual Studio と空のビルドディレクトリを使います。ソースコミット、cl /Bv 出力、Windows SDK バージョン、完全なコマンド、生成ハッシュを記録します。記録のない IDE 操作より、再実行可能なリリース手順が重要です。

cl /Bv
cl /nologo /std:c++17 /O2 /W4 /WX /EHsc /MT /LD mt_safe_math.cpp ^
  /link /OUT:mt_safe_math_v1.dll /INCREMENTAL:NO
dumpbin /headers mt_safe_math_v1.dll | findstr /i machine
dumpbin /exports mt_safe_math_v1.dll

ヘッダーが目的の x64 machine を示し、エクスポートテーブルに呼び出し可能な MtApiVersionMtMean が正確に存在する場合だけ続行します。extern "C" は C++ 名前マングリングを制御しますが、装飾規則と呼び出し規約はアーキテクチャで異なります。MQL 呼び出しが「動いたように見える」まで装飾名を推測・改名するのではなく、ABI を意図的に修正して検証します。

同じ記録済みツールチェーンと入力を使い、2 つの新しいディレクトリでビルドして SHA-256 を比較します。異なるなら、決定的な工程と呼ぶ前にビルド入力を調査します。コンパイラーや SDK の更新をまたいで同一ハッシュになるとは主張しません。

Get-FileHash -LiteralPath .mt_safe_math_v1.dll -Algorithm SHA256
Get-AuthenticodeSignature -LiteralPath .mt_safe_math_v1.dll |
  Format-List Status,StatusMessage,SignerCertificate

ハッシュは正確なバイト列を識別しますが、作成者を証明しません。認証されたリリースチャネルでハッシュを公開し、配布するバイナリには署名して、想定した発行者を検証します。リリースマニフェスト、署名結果、エクスポート、アーキテクチャ、API バージョン、テスト結果を一緒に保管します。

文字列、配列、構造体、所有権

MetaQuotes が文書化する制限をインターフェース設計に反映します。

MQL の値ネイティブ境界の規則
単純スカラー明示的に参照としない限り値渡し。正確なサイズを一致させる。
double &array[]DLL はデータバッファー先頭を受け取る。ArraySetAsSeries は分からないため、要素数を別に渡して検証する。
値渡しの stringDLL はコピーされた文字列バッファーへのポインターを受け取る。保持しない。
string &元の文字列バッファーを参照する。変更と容量の規則を誤りやすいため、最初の ABI では避ける。
テキストプロトコルCP_UTF8 など明示的なコードページで作った呼び出し側所有の uchar[] と、バイト長・出力容量を使う。終端文字を長さに含むか定義する。
単純構造体文字列、クラス、ポインター、動的配列を含まない POD 相当だけを候補にする。パッキングとフィールド幅を明示的に揃える。MQL5 構造体は既定でパックされる。
複雑な構造体または文字列配列インポート DLL へ渡さない。MetaQuotes が明示的に制限している。

インポート呼び出しが戻った後に MQL 配列や文字列ポインターを保持してはいけません。DLL で new/malloc したメモリを MQL または別ランタイムに解放させてもいけません。Microsoft は異なるランタイム間でメモリや CRT オブジェクトを DLL 境界越しに扱うとヒープ破損が起こり得ると説明しています。呼び出し側がバッファーを確保し、容量を明示し、その範囲だけに書くほうが監査しやすくなります。

エラー、ログ、秘密情報

2 つのエラーチャネルを混同せずに使います。

  • ネイティブ関数は文書化されたステータスを返し、失敗時には呼び出し側所有の出力を安全な値へ初期化する。
  • MQL はターミナル/ランタイム診断用に GetLastError() を別に記録する。

C++ 例外を C ABI の外へ出してはいけません。エクスポート関数を noexcept に保ち、内部の失敗を安定したステータスへ変換します。市場データ全体、口座識別子、ユーザー名を含むパス、認証情報をログに出しません。有用な診断記録は、ターミナル build とアーキテクチャ、DLL API バージョン、期待するリリース ID/ハッシュ、関数名、要素数、ネイティブステータス、MQL エラー、所要時間です。

DLL にブローカー認証情報、API キー、署名鍵、口座パスワード、「隠し」エンドポイントを含めてはいけません。ネイティブバイナリは解析できます。後の設計で特権的な外部アクセスが必要になったら、別の脅威モデルとシークレットストアを定義し、この計算境界へ秘密を紛れ込ませないでください。

Strategy Tester とデモテスト計画

MetaQuotes によれば、リモートテストエージェントと MQL5 Cloud エージェントは DLL 呼び出しを実行できません。ローカルエージェントは Allow import DLL が有効な場合だけ呼び出せます。この制約を前提に設計し、回避したり未レビューのコードへ黙ってフォールバックしたりしません。

次の順序で進めます。

  1. 無効値、境界値、非有限値、最大サイズについてポータブルコアテストを実行する。
  2. クリーン環境で x64 DLL をビルドし、ヘッダー、依存関係、エクスポート、ハッシュ/署名を確認する。
  3. ターミナルを閉じた状態で、検証済み・バージョン付き DLL をデータディレクトリの MQL5/Libraries へコピーする。
  4. AutoTrading オフ、最小権限の使い捨てまたはデモ用ターミナルプロファイルを開く。
  5. 警告なしでスモークスクリプトをコンパイルし、Dependencies を確認して、この既知 DLL だけを明示的に許可する。
  6. 2 回実行して Journal を比較する。期待値は API 1、状態 0、平均 2.50000000、取引操作なし。
  7. Strategy Tester が必要なら、DLL を明示許可したローカルエージェントを使い、リモート/クラウド最適化は未対応と記録する。
  8. 広い配布の前に削除とロールバックを演習する。

ネイティブ関数のファズ/ストレステストは、ターミナルを繰り返しクラッシュさせる危険を避け、独立したテストプロセスで行います。MetaQuotes は DLL コードが呼び出しモジュールのスレッドで動くと説明しているため、エクスポート呼び出しは短く保ちます。

許可リスト、配布、ロールバック、削除

許可リストには次を記録します。

  • 一意な DLL ファイル名とネイティブ API バージョン。
  • x64 アーキテクチャと期待するエクスポート名。
  • SHA-256 と期待する署名者の身元/状態。
  • ソースリビジョン、MSVC/SDK バージョン、ビルドコマンド。
  • 直接依存関係とライセンス。
  • 合格したテストと承認日。

DLL と対応する MQL ソース/EX5 を 1 組としてレビュー・配布します。バージョン付きファイル名を使い、ロールバック時に読み込み済みモジュールを上書きしないようにします。DLL を置換または削除する前に、アプリの DLL 権限を無効化し、スクリプト/EA をチャートから外し、ターミナルを終了してプロセス終了を確認します。その後、前の承認済みペアを復元するか、バージョン付きファイルを削除します。デモプロファイルで再起動し、スモークテストを再実行します。

ターミナルが DLL を読み込んでいる可能性がある間は上書きしません。ターミナルディレクトリを広い検索パスへ加える、依存 DLL を Windows システムディレクトリへコピーする、セキュリティ制御を無効化して読み込みを「直す」といった操作も避けます。

トラブルシューティング表

症状読み取り専用の確認安全な対応
OnStart() 前に停止するJournal、Dependencies、DLL 権限、正確なファイル名信頼を検証した後だけ権限を戻す。MQL5/Libraries のファイルを確認する。
“Function not found”dumpbin /exports、綴り、API バージョン対応するペアを再ビルドする。装飾名を推測しない。
DLL を読み込めないdumpbin /headersdumpbin /dependents、ターミナルの x64 状態正しいアーキテクチャとレビュー済み依存関係を用意する。ネットから無関係な DLL をコピーしない。
ターミナルがクラッシュ、出力が破損プロトタイプ順/型、配列長、出力ポインター、ネイティブ単体テストインポートを無効化して終了し、候補 DLL を外して前の承認版を戻す。
ローカルは成功、リモート最適化は失敗テスターのエージェント種別と公式 DLL 制限リモート/クラウド未対応とするか DLL 依存を除く。制限を回避しない。
ハッシュまたは署名が違うリリースマニフェスト、署名者、ソース/ツールチェーン記録ファイルを隔離し、来歴が解決するまで配布を止める。

受け入れ条件と停止条件

次をすべて満たした場合だけ受け入れます。

  • ソースとビルド入力がバージョン管理され、クリーンビルド手順が記録済み。
  • 正確なアーキテクチャ、依存関係、2 つのエクスポート名、API バージョン、SHA-256、署名状態が許可リストと一致。
  • ネイティブの正常/異常テストが通り、未説明の sanitizer/静的解析指摘がない。
  • MQL スモークスクリプトが警告なしでコンパイルされ、デモプロファイルで同じ期待ログを 2 回出す。
  • AutoTrading がオフのままで、ネットワーク、ファイル、認証情報、注文操作が発生しない。
  • ローカル Strategy Tester の挙動とリモート/クラウド未対応を文書化。
  • ロールバックと完全削除を実際に演習済み。

予期しない依存/エクスポート、権限表示の不一致、クラッシュ、ハング、範囲外レポート、説明できない非決定ビルド、ハッシュ/署名不一致、ソース/バイナリ/ログ内の秘密、ライブ取引有効化や安全策回避の試みがあれば直ちに停止します。

一次資料

Leave a Reply