English Русский 中文 Español Deutsch Português 한국어 Français Italiano Türkçe
アンマネージドのエクスポートを使用した MQL5へのC#コードのエクスポーズ

アンマネージドのエクスポートを使用した MQL5へのC#コードのエクスポーズ

MetaTrader 5統合 | 19 11月 2015, 15:28
3 130 0
investeo
investeo

はじめに

私は長いあいだMQL5でマネージドモードのC# DLLを使用できるようにする簡単なソル―ションを探していました。多くの記事を呼んで、マネージドのDLLに対するC++言語のラッパーを実装しようとしていたとき、作業時間を大幅に節約する素晴らしい解決法に出会いました。

その解決法はアンマネージドのアプリケーションで消費するためのマネージドのC# コードをエクスポートするシンプルな例を提供してくれました。本稿では、マネージドモードのDLLの背景を提供し、なぜそれらがMetaTraderから直接アクセスできないのかをお話し、MetaTraderからマネージドのコードそ使用可能にする解決法を見つけたのでそれをお伝えします。

またアンマネージドのテンプレートのシンプルな用例を紹介し、私がみつけたすべてについて話を続けていきます。これは MetaTrader 5でC# DLLコードを使用しようとするみなさんにたいして背景音を提供します。


1. マネージドコードvsアンマネージドコード

ほとんどの読者の方はマネージドコードとアンマネージドコードの違いをご存じないので数文でそれについてお話していきます。基本的にBasically, MetaTrader はトレーディングルール、インディケータ、エキスパートアドバイザー、スクリプトを実装するのに MQL 言語を使用します。ですが、私は別言語のライブラリをすでに実装してしまており、ランタイム中に動的にそれらをリンクしています。そういうライブラリはまたDLLまたは ダイナミックリンクライブラリと呼ばれます。

ライブラリは実は指定の処理を行うために外部プログラムの数値によって呼ばれることのできるコンパイルされたソースコードを含むバイナリファイルです。たとえば、ニューラルネットワークライブラリはニューラルネットワークのトレーニングおよび検証のために関数をエクスポートすることができ、デリバティブライブラリは異なるデリバティブの計算をエクスポートすることができ、行列ライブラリは行列処理をエクスポートすることができます。MetaTrader 用のDLLは、インディケータやエキスパートアドバイザーの部分を隠すことができるため人気が高まってきました。しかしライブラリを使う主な理由は何度も繰り返して実装する必要なく既存のコードを再利用することです。

.NET が出る前 Visual Basic、Delphi、 VC++、 be it COM、 Win32、plain C++によってコンパイルされたDLLはすべてオペレーティングシステムで直接実行できました。このコードはアンマネージドまたはネイティブコードと呼びます。.NET が出てひじょうに異なる環境を提供しました。

コードは.NET 共通言語ランタイム、CLRによって制御されます。CLR コンパイラはソースコードから作成するために要求され、それは複数の異なる言語、メタデータ、共通中間言語、CILで書かれます。

CIL は機械です。自立の高レベル言語で共通型仕様、CTSに従ったCILによって記述されるオブジェクトのメタデータ完全記述です。CLR はタイプについてすべてを知っているので、マネージドの実行環境を提供してくれます。管理はガベージコレクションと考えることができます。自動メモリ管理およびオブジェクト削除、セキュリティ提供。セキュリティとはアドミニストレータ権限のあるエイリアンコードまたは単純なメモリオーバーライドが起こす可能性のあるネイティブ言語での一般的誤りに対する保護をいいます。

CIL コードは決して直接実行されないことを述べる必要があります。それは常にJIT(ジャストインタイムの)コンパイルまたはアセンブリーへのCIL 事前コンパイルによってネイティブ機械コードに翻訳されます。これについて読むのが初めてという方はマネージドコードの内容は混乱を招くかもしれません。よって次にCLR 内の一般的フローを貼付します。

図1 共通言語ランタイム


2. MQL5からマネージドコードへの可能なアクセス実装

次の段落ではアンマネージドコードからマネージドコードへアクセスを可能にするメソッドについて述べます。

私が使っているメソッドの代わりに別のメソッドを使いたい方がおられるかもしれないので、メソッドについてはすべてお話ししようと思います。使用されているメソッドはCOM Interop、Reverse P/Invoke、C++ IJW、 C++/Cli ラッパークラスおよび アンマネージドの Exportsです。


2.1. COM インテロプ

コンポーネントオブジェクトモデル (COM) は1990年代初頭にマイクロソフトによって紹介されたバイナリインターフェースの規格です。この技術の中心になる考えは異なる言語で作成されたオブジェクトを他のあらゆるCOMオフジェクトによって内部実装を知らなくても使えるようにするというものです。そのような要件は実装から完全に分離した厳密に明確に定義されたCOMインターフェースの実装を強制します。

事実、COM は .NET 技術によって取って代わられ、マイクロソフトはCOMの代わりに .NETを使うように促しています。古いコードとの互換性を与えるために.NET は COM と双方向に連携することができます。それは .NET がCOM メソッドを呼ぶことができCOM オブ会苦とが .NET のマネージドのコードを利用することができるということです。

この機能は COM 相互運用またはCOMインテロプと呼ばれます。COM インテロプ API はマネージドの System.RuntimeInteropServices 名前空間にあります。

図2 COM 相互運用モデル

図2 COM 相互運用モデル

次の COM インテロプコードは単一関数 raw_factorialを呼びます。

関数CoInitialize()、CoCreateInstance() および CoUninitialize() とインターフェース呼び出し関数に気づいてください。

#include "windows.h"
#include <stdio.h>
#import "CSDll.tlb" named_guids

int main(int argc, char* argv[])
{
    HRESULT hRes = S_OK;
    CoInitialize(NULL);
    CSDll::IMyManagedInterface *pManagedInterface = NULL;

    hRes = CoCreateInstance(CSDll::CLSID_Class1, NULL, CLSCTX_INPROC_SERVER, 
     CSDll::IID_IMyManagedInterface, reinterpret_cast<void**> (&pManagedInterface));

    if (S_OK == hRes)
    {
        long retVal =0;
        hRes = pManagedInterface->raw_factorial(4, &retVal);
        printf("The value returned by the dll is %ld\n",retVal);
        pManagedInterface->Release();
    }

    CoUninitialize();
    return 0;
}

COM インテロプに関しては詳しいドキュメンテーションIntroduction to COM Interop と私がmsdnブログで見つけた用例How to call C++ code from Managed, and vice versa (Interop)をご一読ください。


2.2. リバース P/Invoke

プラットフォーム Invokeは P/Invoke と呼ばれ .NET がそのシグネチャーが再宣言される限りアンマネージドの言語のあらゆる関数を呼べるようにします。これは .NETからネイティブの関数ポインターを実行することで行われます。用法は Platform Invoke Tutorialでわかりやすく述べられています。

基本的な用法はインポートされた関数に印をつけるためにDllImport 特性を使用することです。

// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}

逆の処理もアンマネージドのコードに対してマネージドの代表コールバックを提供することで記述が可能です。

これは リバース P/Invoke と呼ばれ、マネージドの環境でパブリックなデリゲート関数を実装し、ネイティブDLLに呼び出し元関数をインポートすることで獲得されます。

#include <stdio.h>
#include <string.h>
typedef void (__stdcall *callback)(wchar_t * str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t * input, int count, callback call)
{
      for(int i = 0; i < count; i++)
      {
            call(input);
      }
}

マネージドのコード例は以下に記します。

using System.Runtime.InteropServices;
public class foo
{
    public delegate void callback(string str);
    public static void callee(string str)
    {
        System.Console.WriteLine("Managed: " +str);
    }
    public static int Main()
    {
        caller("Hello World!", 10, new callback(foo.callee));
        return 0;
    }
    [DllImport("nat.dll",CallingConvention=CallingConvention.StdCall)]
    public static extern void caller(string str, int count, callback call);
}

このソル―ションの重要点はこれは連携を始めるためにマネージド側を要求することです。

さらなる参照にはGotchas with Reverse Pinvoke (unmanaged to managed code callbacks) および PInvoke-Reverse PInvoke and stdcall - cdeclをご一読ください。



2.3. C++ IJW

C++ インタロプは It Just Works (IJW))と呼ばれ、Managed Extensions for C++によって提供されるC++ 言語特有の機能です。

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main()
{
   String * pStr = S"Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   
   puts(pChars);
   
   Marshal::FreeHGlobal(pChars);
}

そのソル―ションは、アンマネージドのアプリケーションでマネージドのC++ 言語を使いたい方に有用です。すべてを参照したい場合は Interoperability in Managed Extensions for C++ および Using IJW in Managed C++をご一読ください。


2.4. C++/Cli ラッパークラス

C++/Cli ラッパークラスの実装は、埋め込みやC++/Cliモードで書かれた別クラスによるラップのマネージドクラスから名前を取ります。ラッパー DLL を書く最初のステップは、オリジナルのマネージドのクラスをラップする C++ クラスを書くことです。

ラッパークラスは gcroot<> テンプレートを使用する.NETオブジェクトに対するハンドルを持ち、オリジナルクラスからの呼び出しをすべてデリゲートする必要があります。ラッパークラスは IL (中間言語)フォーマットにコンパイルされるためマネージドです。

次のステップは、ILクラスをラップし、 __declspec(dllexport)命令ですべての呼び出しをデリゲートする#pragmaアンマネージド命令を持つ C++ クラスを書くことです。これらステップはどんなアンマネージドのアプリケーションでも使用可能なネイティブのC++ DLL を作成します。

実装例をご覧ください。最初のステップはC# コード実装です。

例の計算クラスには2つのパブリックメソッドが含まれています。

public class Calculator
{
    public int Add(int first, int second)
    {
        return first + second;
    }
    public string FormatAsString(float i)
    {
        return i.ToString();
    }
}

次のステップでは、calculatorクラスからの全メソッドをデリゲートするマネージドのラッパーを書きます。

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_CppCliWrapper_Calculator {
private:
    //Aggregating the managed class
    gcroot<CppCliWrapper::Calculator^> __Impl;
public:
    ILBridge_CppCliWrapper_Calculator() {
        __Impl = gcnew CppCliWrapper::Calculator;
    }
    int Add(int first, int second) {
        System::Int32 __Param_first = first;
        System::Int32 __Param_second = second;
        System::Int32 __ReturnVal = __Impl->Add(__Param_first, __Param_second);
        return __ReturnVal;
    }
    wchar_t* FormatAsString(float i) {
        System::Single __Param_i = i;
        System::String __ReturnVal = __Impl->FormatAsString(__Param_i);
        wchar_t* __MarshaledReturnVal = marshal_to<wchar_t*>(__ReturnVal);
        return __MarshaledReturnVal;
    }
};

オリジナルの Calculator クラスへの参照はgcnewインストラクションを使って gcroot<> テンプレートとして格納されます。ラップされたメソッドはすべてオリジナルメソッドと同じ名前を持ち、パラメータと戻り値はそれぞれ __Param および __ReturnVal が前にきます。

C++/CliをラップしネイティブのC++ DLLメソッドをエクスポートするアンマネージドのC++ クラスを実装する必要があります。

ヘッダーファイルには__declspec(dllexport) 命令を持つクラス定義が含まれ、ラッパークラスに対するポインターを格納する必要があります。

#pragma once
#pragma unmanaged

#ifdef THISDLL_EXPORTS
#define THISDLL_API __declspec(dllexport)
#else
#define THISDLL_API __declspec(dllimport)
#endif

//Forward declaration for the bridge
class ILBridge_CppCliWrapper_Calculator;

class THISDLL_API NativeExport_CppCliWrapper_Calculator {
private:
    //Aggregating the bridge
    ILBridge_CppCliWrapper_Calculator* __Impl;
public:
    NativeExport_CppCliWrapper_Calculator();
    ~NativeExport_CppCliWrapper_Calculator();
    int Add(int first, int second);
    wchar_t* FormatAsString(float i);
};

その実装です。

#pragma managed
#include "ILBridge_CppCliWrapper_Calculator.h"
#pragma unmanaged
#include "NativeExport_CppCliWrapper_Calculator.h"

NativeExport_CppCliWrapper_Calculator::NativeExport_CppCliWrapper_Calculator() {
    __Impl = new ILBridge_CppCliWrapper_Calculator;
}
NativeExport_CppCliWrapper_Calculator::~NativeExport_CppCliWrapper_Calculator()
{
    delete __Impl;
}
int NativeExport_CppCliWrapper_Calculator::Add(int first, int second) {
    int __ReturnVal = __Impl->Add(first, second);
    return __ReturnVal;
}
wchar_t* NativeExport_CppCliWrapper_Calculator::FormatAsString(float i) {
    wchar_t* __ReturnVal = __Impl->FormatAsString(i);
    return __ReturnVal;
}

このラッパークラスの段階を踏んだ作成ガイドは .NET to C++ Bridgeに記載があります。

ラッパー作成の参照の完全版はMixing .NET and native code にあります。またネイティブタイプでハンドルを宣言することについての一般的情報はHow to: Declare Handles in Native Typesをご一読ください。


2.5. アンマネージドのエクスポート

.NETコンパイラの詳細について読みたい方にはお薦めする本 Expert .NET 2.0 IL Assemblerにこの専門的理論の完全な記載があります。主要な考えはILDasmを使用してすでにコンパイル済みのモジュールをILに逆コンパイルし、モジュールのテーブルVTable および VTableFixupを変更し、ILAsmによってDLLを再コンパイルすることでマネージドのDLLのアンマネージドのエクスポートとしてマネージドのメソッドにエクスポーズすることです。

このタスクは手ごわそうに見えますが、この処理の結果はあらゆるアンマネージドのアプリケーションからも使用可能なDLLを作成することです。これはまだマネージドのアセンブリであるため .NETのフレームワークをインストールする必要があることを覚えておかなくてはなりません。このための段階的な指導書はExport Managed Code as Unmanagedです。

ILDasmを使って DLL を逆コンパイルしたら、 IL 言語のソースコードを取得します。以下にペーストしたアンマネージドのエクスポートでILコードの簡単な例をよく見てください。

assembly extern mscorlib {}
..assembly UnmExports {}
..module UnmExports.dll
..corflags 0x00000002
..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)
..method public static void foo()
{
..vtentry 1:1
..export [1] as foo
ldstr "Hello from managed world"
call void [mscorlib]System.Console::WriteLine(string)
ret
}

アンマネージドのエクスポートを実装するIL 言語のソースコードは以下です。

..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)

また

..vtentry 1:1
..export [1] as foo

最初の部分は関数 entry を VTableFixup テーブルに追加し、VT_01仮想アドレスを関数に設定するためのものです。2番目の部分は、この関数にはどのVTEntryが使用されるか指定し、またエクスポートされる関数に対してしてエクスポートのエイリアスを指定します。

このソル―ションのメリットは、DLLの実装フェーズでは本に述べられているように、通常のマネージドのC# DLL以外の追加コードはまったく実装する必要がありません。ということは、このメソッドはすべてのセキュリティおよびクラスライブラリを伴いマネージドの世界をアンマネージドののクライアントに完全に開くものです。

.NET のアセンブリ言語になるドローバックはすべての人に適切であるとは言えません。Robert Giesecke氏の著書http://sites.google.com/site/robertgiesecke/にあるアンマネージドののエクスポートテンプレートを見つけるまでは代わりにc++言語のラッパークラスを書くんだと確信していました。それはILコードの内部に入ることなくアンマネージドのエクスポートを利用することが可能なのです。


3. アンマネージドのエクスポート C# テンプレート

R.Gieseckeによるアンマネージドのエクスポート C# プロジェクトは、ビルドののちに適切な VT-fixupsを自動で追加する MSBuild task を使用しており、そのためILコードを変更する必要はまったくありません。テンプレートパッケージをzipファイルとしてダウンロードし、「ビジュアルスタジオ」のフォルダProjectTemplatesにコピーをする必要があります。

プロジェクトのコンパイル後、結果のDLLファイルはMetaTraderによって問題なくインポートされます。次項でその例を提供します。


4. 例

正しい手法を使ってMetaTrader と C#の間で変数、配列、ストラクチャを渡し方法を見つけるのは困難な仕事でした。ここに提供される情報によりみなさんは多くの時間を節約できると思います。すべての例は Windows Vista で .NET 4.0 およびVisual C# Express 2010を使ってコンパイルされました。またC# DLLから本稿に関数を呼ぶMQL5コードを伴うDLL も添付しています。


4.1. 例1 DLL 関数に2つの整数、ダブル、浮動少数変数を追加し結果をMetaTraderに帰します。

using System;
using System.Text;
using RGiesecke.DllExport;
using System.Runtime.InteropServices;

namespace Testme
{
    class Test
    {

        [DllExport("Add", CallingConvention = CallingConvention.StdCall)]
        public static int Add(int left, int right)
        {
            return left + right;
        }

        [DllExport("Sub", CallingConvention = CallingConvention.StdCall)]
        public static int Sub(int left, int right)
        {
            return left - right;
        }

        [DllExport("AddDouble", CallingConvention = CallingConvention.StdCall)]
        public static double AddDouble(double left, double right)
        {
            return left + right;
        }

        [DllExport("AddFloat", CallingConvention = CallingConvention.StdCall)]
        public static float AddFloat(float left, float right)
        {
            return left + right;
        }

    }
}

ご存じのとおり、エクスポートされた関数はどれも DllExport 命令が先にきます。 最初のパラメータはエクスポートされた関数のを述べ、コンベンション集積を呼ぶ2番目のパラメータはMetaTraderに対し CallingConvention.StdCallを使用する必要があります。

インポートしDLLからエクスポートされる関数を使うMQL5 コードは簡単で、ネイティブのC++言語で書かれたその他の DLL となんら異なるところはありません。まず#importブロック内でインポートされた関数を宣言する必要があります。そして DLL からのどの関数がのちにMQL5 コードから使われるのか指示します。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample1.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Add(int left,int right);
   int Sub(int left,int right);
   float AddFloat(float left,float right);
   double AddDouble(double left,double right);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   for(int i=0; i<3; i++)
     {
      Print(Add(i,666));
      Print(Sub(666,i));
      Print(AddDouble(666.5,i));
      Print(AddFloat(666.5,-i));
     }
  }
//+------------------------------------------------------------------+

結果

2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666


4.2. 例2 1次元配列アクセス

[DllExport("Get1DInt", CallingConvention = CallingConvention.StdCall)]
        public static int Get1DInt([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  int[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DFloat", CallingConvention = CallingConvention.StdCall)]
        public static float Get1DFloat([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  float[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DDouble", CallingConvention = CallingConvention.StdCall)]
        public static double Get1DDouble([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  double[] tab, int i, int idx)
        {
            return tab[idx];
        }

1次元配列を整列するために MarshalAs 命令は最初のパラメータとして UnmanagedType.LPArray を、そして2番目のパラメータとして SizeParamIndex を渡します。SizeParamIndex はどのパラメータ(0から数えます)が配列サイズを持つパラメータなのか指示します。

上記の例で i が配列サイズ idx が返すエレメントのインデックスです。

配列アクセスを使用したMQL5 例のコードを以下に示します。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample2.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
   int Get1DInt(int &t[],int i,int idx);
   float Get1DFloat(float &t[],int i,int idx);
   double Get1DDouble(double &t[],int i,int idx);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int tab[3];
   tab[0] = 11;
   tab[1] = 22;
   tab[2] = 33;

   float tfloat[3]={0.5,1.0,1.5};
   double tdouble[3]={0.5,1.0,1.5};

   for(int i=0; i<3; i++)
     {
      Print(tab[i]);
      Print(Get1DInt(tab,3,i));
      Print(Get1DFloat(tfloat,3,i));
      Print(Get1DDouble(tdouble,3,i));

     }
  }
//+------------------------------------------------------------------+

結果

2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.00000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11


4.3. 例3 1次元配列作成とそのMetaTraderへの戻し

[DllExport("SetFiboArray", CallingConvention = CallingConvention.StdCall)]
        public static int SetFiboArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
        int[] tab, int len, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] res)
        {
            res[0] = 0;
            res[1] = 1;
            
            if (len < 3) return -1;
            for (int i=2; i<len; i++)
                res[i] = res[i-1] + res[i-2];
            return 0;
        }

この例は入力パラメータのコンベンション集積を比較するため2つの入力配列を使用しています。変更されたエレメントが Metatraderに戻される(参照渡し)場合、MarshalAs属性の前に [In, Out,] 属性を入れるだけです。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample3.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
    int SetFiboArray(int& t[], int i, int& o[]);
#import


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
int fibo[10];
static int o[10];

   for (int i=0; i<4; i++)
   { fibo[i]=i; o[i] = i; }
   
   SetFiboArray(fibo, 6, o);
   
   for (int i=0; i<6; i++)
      Print(IntegerToString(fibo[i])+":"+IntegerToString(o[i]));
      
  }
//+------------------------------------------------------------------+

結果

2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:5
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:3
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 3:2
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 2:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 1:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:0


4.4. 例4 二次元配列へのアクセス

public static int idx(int a, int b) {int cols = 2; return a * cols + b; }
 
        [DllExport("Set2DArray", CallingConvention = CallingConvention.StdCall)]
        public static int Set2DArray([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] tab, int len)
        {
            tab[idx(0, 0)] = 0;
            tab[idx(0, 1)] = 1;
            tab[idx(1, 0)] = 2;
            tab[idx(1, 1)] = 3;
            tab[idx(2, 0)] = 4;
            tab[idx(2, 1)] = 5;
            
            return 0;
        }

二次元配列は整列するのがそう簡単ではありませんが、ある技を使いました。それというのは、2D配列を1次元として渡し、予備のidx関数で配列エレメントにアクセスするのです。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample4.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Set2DArray(int &t[][2],int i);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int t2[3][2];

   Set2DArray(t2,6);

   for(int row=0; row<3; row++)
      for(int col=0; col<2; col++)
         Print("t2["+IntegerToString(row)+"]["+IntegerToString(col)+"]="+IntegerToString(t2[row][col]));

  }
//+------------------------------------------------------------------+

結果

2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][1]=5
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][0]=4
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][1]=3
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][0]=2
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][1]=1
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][0]=0


4.5. 例5 ストリングのコンテンツ置き換え

[DllExport("ReplaceString", CallingConvention = CallingConvention.StdCall)]
        public static int ReplaceString([In, Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder str,
        [MarshalAs(UnmanagedType.LPWStr)]string a, [MarshalAs(UnmanagedType.LPWStr)]string b)
        {
            str.Replace(a, b);

            if (str.ToString().Contains(a)) return 1;
            else  return 0;
        }

例は短いですが実装するのにかなり時間がかかりました。というのも[In,Out] 属性を使用してストリングパラメータを使用してみようとしたからです。キーワードref または outでは成功しませんでした。

ストリング変数の代わりにStringBuilder を使用するソル―ション

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample5.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"   
   int ReplaceString(string &str,string a,string b);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string str="A quick brown fox jumps over the lazy dog";
   string stra = "fox";
   string strb = "cat";


   Print(str);
   Print(ReplaceString(str,stra,strb));
   Print(str);

  }
//+------------------------------------------------------------------+

結果

2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown cat jumps over the lazy dog
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) 0
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown fox jumps over the lazy dog


4.6. 例6 MqlTick ストラクトの送信と変更

     private static List<MqlTick> list;

     [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MqlTick
        {
            public Int64 Time;
            public Double Bid;
            public Double Ask;
            public Double Last;
            public UInt64 Volume;
        }

        [DllExport("AddTick", CallingConvention = CallingConvention.StdCall)]
        public static int AddTick(ref MqlTick tick, ref double bidsum)
        {
            bidsum = 0.0;

            if (list == null) list = new List<MqlTick>();

            tick.Volume = 666;
            list.Add(tick);

            foreach (MqlTick t in list) bidsum += t.Ask;

            return list.Count;
        }

MqlTick ストラクトが参照として渡され、キーワード ref でマークされます。MqlTick ストラクト自体は [StructLayout (LayoutKind.Sequential, Pack =1)] 属性のあとにくる必要があります。

Pack パラメータはストラクト内でデータ配置を記述します。詳細は StructLayoutAttribute.Pack Field をお読みください。

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample6.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int AddTick(MqlTick &tick, double& bidsum);
#import
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
int OnCalculate(const int rates_total,
                const int prev_calculated,
                const datetime& time[],
                const double& open[],
                const double& high[],
                const double& low[],
                const double& close[],
                const long& tick_volume[],
                const long& volume[],
                const int& spread[])
  {
//---
   MqlTick newTick;
   double bidsum;
   
   SymbolInfoTick(Symbol(), newTick);
   
   Print("before = " + IntegerToString(newTick.volume));
   
   Print(AddTick(newTick, bidsum));
   
   Print("after = " + IntegerToString(newTick.volume) + " : " + DoubleToString(bidsum));
   
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

結果

2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 8.167199999999999
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 6
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 6.806
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 5
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 5.4448
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 4
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 4.0836
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 3
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 2.7224
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 2
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 1.3612
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 1
2011.01.30 23:59:04     TickDLLSend (EURUSD,M1) before = 0


おわりに

本稿ではMQL5 コードとマネージドの C# 間の異なる連携手法を提供します。

またC# に対してMQL5を整理する方法および MQL5 スクリプト内にてエクスポートされたDLL関数を呼びだす方法例も提供します。ここでお話する例がマネージドのコードで DLLを書くことに関する将来的な研究の基になると信じています。

本稿はまたすでにC#で実装されている多くのライブラリを使用するために MetaTrader にドアを開けるものです。より以上の参照には参照資料項にリンクがある記事をお読みください。

その検証には以下のフォルダにファイルを置いてください。

  • MQL5\Libraries\testme.dll
  • MQL5\Scripts\unmanagedexportsdllexample1.mq5
  • MQL5\Scripts\unmanagedexportsdllexample2.mq5
  • MQL5\Scripts\unmanagedexportsdllexample3.mq5
  • MQL5\Scripts\unmanagedexportsdllexample4.mq5
  • MQL5\Scripts\unmanagedexportsdllexample5.mq5
  • MQL5\Experts\unmanagedexportsdllexample6.mq5


参照資料

  1. Exporting .NET DLLs with Visual Studio 2005 to be Consumed by Native Applications
  2. Interoperating with Unmadged Coded
  3. Introduction to COM Interop
  4. Component Object Model (COM)
  5. Exporting from a DLL Using __declspec(dllexport)
  6. How to: Declare Handles in Native Types
  7. How to call C++ code from Managed, and vice versa (Interop)
  8. Reverse P/Invoke and exception
  9. How to call a managed DLL from native Visual C++ code in Visual Studio.NET or in Visual Studio 2005
  10. Platform Invoke Tutorial
  11. PInvoke-Reverse PInvoke and __stdcall - __cdecl
  12. Gotchas with Reverse Pinvoke (unmanaged to managed code callbacks)
  13. Mixing .NET and native code
  14. Export Managed Code as Unmanaged
  15. Understanding Classic COM Interoperability With .NET Applications
  16. Managed Extensions for C++ Programming
  17. Robert Giesecke's site
  18. MSBuild Tasks
  19. Common Language Runtime

MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/249

MetaTrader 5 ターミナルのストラテジーテスタ内でティック作成をするアルゴリズム MetaTrader 5 ターミナルのストラテジーテスタ内でティック作成をするアルゴリズム
MetaTrader 5 により内蔵ストラテジーテスタでExpert Advisors および MQL5を利用し自動トレーディングをシミュレートすることができます。このタイプのシミュレーションは Expert Advisorsの検証と呼ばれ、マルチスレッド最適化を用い、同時に数多くのインスツルメントについて実装することができます。完全な検証のために用可能な分履歴をもとにティック生成が行われる必要があります。本稿ではアルゴリズムの詳細記述を提供します。それによりティックはMetaTrader 5 クライアントターミナルで履歴検証に対して作成されます。
時系列の主要特性分析 時系列の主要特性分析
本稿ではさまざまな時系列特性を予めすばやく判断するために設計されるクラスを紹介します。これを行うにあたり、統計的パラメータと自己相関関数を決め、時系列のスペクトル推定を実行し、ヒストグラムを作成します。
MetaTrader 5 から MetaTrader 4へトレードをコピーする方法 MetaTrader 5 から MetaTrader 4へトレードをコピーする方法
今日MetaTrader 5 の実アカウントでトレードすることはできますか?そのようなトレードをどうやって行いますか?本稿はこういった疑問に対する見解およびMetaTrader 5 ターミナルから MetaTrader 4ターミナルへトレードをコピーするためのワーキングコードを提供します。本稿は Expert Advisors の開発者、実践的トレーダー双方に役立つものです。
「マーケット」でプロダクトを宣伝する方法 「マーケット」でプロダクトを宣伝する方法
マーケットを通じて世界中の何百万ものMetaTraderユーザーに取引アプリケーションを提供し始めましょう。このサービスでは、多数のオーディエンスへのアクセス、ライセンスソリューション、試用版、更新の公開、支払いの受け入れなど、既成のインフラストラクチャが提供されています。簡単な販売者登録手続きを完了するだけで製品を公開できます。このサービスが提供する既成の技術基盤を使用すると、プログラムから追加の利益を生み出し始めることができます。