
アンマネージドのエクスポートを使用した MQL5へのC#コードのエクスポーズ
はじめに
私は長いあいだ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 相互運用モデル
次の 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
参照資料
- Exporting .NET DLLs with Visual Studio 2005 to be Consumed by Native Applications
- Interoperating with Unmadged Coded
- Introduction to COM Interop
- Component Object Model (COM)
- Exporting from a DLL Using __declspec(dllexport)
- How to: Declare Handles in Native Types
- How to call C++ code from Managed, and vice versa (Interop)
- Reverse P/Invoke and exception
- How to call a managed DLL from native Visual C++ code in Visual Studio.NET or in Visual Studio 2005
- Platform Invoke Tutorial
- PInvoke-Reverse PInvoke and __stdcall - __cdecl
- Gotchas with Reverse Pinvoke (unmanaged to managed code callbacks)
- Mixing .NET and native code
- Export Managed Code as Unmanaged
- Understanding Classic COM Interoperability With .NET Applications
- Managed Extensions for C++ Programming
- Robert Giesecke's site
- MSBuild Tasks
- Common Language Runtime
MetaQuotes Ltdにより英語から翻訳されました。
元の記事: https://www.mql5.com/en/articles/249





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