
DelphiでDLLをMQL5向けに書くためのガイド
はじめに
Delphi 2009の開発環境による例を使用しながらDLLを書くメカニズムを考察していきます。バージョンは、MQL5において全てのコード行はユニコード形式で保存されることに基づいて選択されています。Delphiの古いバージョンではSysUtilsモジュールが ユニコード形式の行を扱う機能が備わっていません。
なんらかの理由で古いバージョン(Delphi 2007以前のもの)をお使いの場合は、ANSI形式で作業をする必要があります。 MetaTrader 5とデータ交換をするには、ユニコードへ直接逆変換でコードを作成する必要があります。そういった煩雑な作業を避けるため、Delphi 2009以降の環境でのMQL5向けDLLモジュールの開発を推奨します。Delphiに慣れるための30日間お試しバージョンは次の公式ウェブサイトからダウンロードできます。 http://embarcadero.com.
1. プロジェクト作成
プロジェクトを作成するために、メニュー項目を選んでDLLウィザードを実行します。 : 「ファイル -> 新規 -> その他 ... -> DLL ウィザード」 図1を参照ください。
図1 DLLウィザードを用いたプロジェクト作成
結果、 図2にあるように空のDLL プロジェクトが作成されます。
図2 空のDLLプロジェクト
プロジェクトタイトルの長い説明のポイントは、動的に割り当てられたメモリで作業する際に正確な接続とメモリマネージャの使用法を思い出していただくことです。これについてはストリングの項目で詳しく述べます。
新規DLLに関数を書き込む前に、プロジェクトをコンフィギュアすることが重要です。
メニューからプロジェクトプロパティウィンドウを開きます。: 「プロジェクト-> オプション ...」 またはキーボード操作では 「Shift + Ctrl + F11」 です。
デバッグの手順を簡素化するには、DLLファイルをフォルダに直接作成する必要があります。 '.. \\MQL5\\Libraries' Trade Terminal MetaTrtader5. このために、 DelphiCompilerタブで図3のように対応するプロパティOutput directoryを設定します。それによりDLLでプロジェクトフォルダから端末フォルダーに生成されるファイルを常にコピーする手間がはぶけます。
図3 DLLファイル作成結果を保存するフォルダを指定します。
アセンブリ中にウィンドウのシステム内に存在がないままBPLモジュールが連結され、作成されたDLLが将来動作しないのを避けるため、パッケージタブ上、ランタイムパッケージで構築フラグにチェックが入っていないことを確認することが重要です。これは図4に示されています。
図4 アセンブリからのBPLファミリーモジュール除外
プロジェクトのコンフィグレーションを完了し、作業フォルダにそれを保存したら、指定されたプロジェクト名がのちにコンパイルされたDLLファイル名となります。
2. 手順と関数の追加
DLLモジュールでエクスポートされた手順と関数を書くときの一般的な状況について、パラメータを用いずに例を使って考察します。パラメータの通知と変換については次の項で述べます。
小規模なデグレッションObject Pascal言語で手順や関数を書くとき、プログラマーは数えきれないコンポーネントを書くのではなく、この環境のために開発された内蔵ライブラリのDelphi関数を使用することができます。たとえば、テクストメッセージを伴うモーダルウィンドウを表示するなどのように同じアクションの処理に、 API関数 MessageBox を使用することができます。それはVCLライブラリの ShowMessageの手順と同じです。
二番目の選択肢では、ダイアログ モジュールをインクルードすることで、それには標準ウィンドウ ライブラリを使用する簡単な作業ができる利点があります。 ただ、DLLファイルのサイズはだいたい500 KB増加することになります。ディスクスペースを圧迫しない小さなDLLファイルにしたい場合は、 VCLの使用はお薦めできません。
説明付検証プロジェクトの例は以下です。
library dll_mql5; uses Windows, // necessary for the work of the MessageBox function Dialogs; // necessary for the work of the ShowMessage procedure from the Dialogs module var Buffer: PWideChar; //------------------------------------------------------+ procedure MsgBox(); stdcall; // //to avoid errors, use the stdcall (or cdecl) for the exported functions //------------------------------------------------------+ begin {1} MessageBox(0,'Hello World!','terminal', MB_OK); {2} ShowMessage('Hello World!');// alternative to the MessageBox function end; //----------------------------------------------------------+ exports //----------------------------------------------------------+ {A} MsgBox, {B} MsgBox name 'MessageBox';// renaming of the exported function //----------------------------------------------------------+ procedure DLLEntryPoint(dwReason: DWord); // event handler //----------------------------------------------------------+ begin case dwReason of DLL_PROCESS_ATTACH: // DLL attached to the process; // allocate memory Buffer:=AllocMem(BUFFER_SIZE); DLL_PROCESS_DETACH: // DLL detached from the process; // release memory FreeMem(Buffer); end; end; //----------------------------------------------------------+ begin DllProc := @DLLEntryPoint; //Assign event handler DLLEntryPoint(DLL_PROCESS_ATTACH); end. //----------------------------------------------------------+
エクスポートされた関数はすべてモディファイアstdcallまたはcdeclで通知される必要があります。モディファイアがひとつも指定されていない場合、Delphiは初期設定呼び出し規約を採用します。それは基本的にスタックというよりはパラメータを渡すためのCPU登録を使用するものです。それはまちがいなく、外部関数 DLLを呼び出す段階で渡されたパラメータへの連携時にエラーを招きます。
"begin end"セクションは標準的なDLLイベントハンドラの初期化コードを含んでいます。DLLEntryPointコールバック手順は、それを呼んだ手順に接続、または切断時に呼ばれます。これらイベントは正しい動的メモリ管理のために使われ、例にあるように必要に応じて割り当てられます。
MQL5を呼びます。
#import "dll_mql5.dll" void MsgBox(void); void MessageBox(void); #import // Call of procedure MsgBox(); // If the names of the function coincide with the names of MQL5 standard library function // use the DLL name when calling the function dll_mql5::MessageBox();
3. 関数へのパラメータ渡と返される値
パラメータを渡すことを考える前に、MQL5とObject Pascalのデータ対応表を分析します。
MQL5のデータタイプ | Object Pascal (Delphi)のデータタイプ | 注意 |
---|---|---|
char | ShortInt | |
uchar | Byte | |
short | SmallInt | |
ushort | Word | |
int | Integer | |
uint | Cardinal | |
long | Int64 | |
ulong | UInt64 | |
float | Single | |
double | Double | |
ushort (символ) | WideChar | |
string | PWideChar | |
bool | Boolean | |
datetime | TDateTime | 変換が必要です。(当項の下を参照ください) |
color | TColor |
表1 MQL5とObject Pascalのデータ対応表
表からおわかりのように、日時以外のすべてのデータタイプにDelphiは完全な類似体を持っています。
渡すパラメータを2とおり見てみます。値渡しと参照渡しです。両者ともパラメータ宣言の形式は表2にあります。
パラメータ変換メソッド | MQL5の通知 | Delphiの通知 | 注意 |
---|---|---|---|
値通知 | int func (int a); | func (a:Integer): Integer; | 正 |
int func (int a); | func (var a: Integer): Integer; | エラー:アクセス違反 に書きこみ <メモリアドレス> | |
リンク通知 | int func (int &a); | func (var a: Integer): Integer; | 正。ただし モディフィアバーなしで行が変換! |
int func (int &a); | func (a: Integer): Integer; | エラー:変数の値の代わりにメモリセル アドレスを含む |
表2 パラメータ渡しのメソッド
ここから、渡されたパラメータと返された値の連携例について考察していきます。
3.1日時変換
まず、変換したい日時データのタイプを処理します。datetimeタイプはフォーマットではなくサイズにおいてのみTDateTimeに対応しているからです。 変換を楽にするために受け取られたデータタイプとしてTDateTimeの代わりにInt64を使用します。 以下は直接変換、逆変換の関数です。
uses SysUtils, // used for the constant UnixDateDelta DateUtils; // used for the function IncSecon, DateTimeToUnix //----------------------------------------------------------+ Function MQL5_Time_To_TDateTime(dt: Int64): TDateTime; //----------------------------------------------------------+ begin Result:= IncSecond(UnixDateDelta, dt); end; //----------------------------------------------------------+ Function TDateTime_To_MQL5_Time(dt: TDateTime):Int64; //----------------------------------------------------------+ begin Result:= DateTimeToUnix(dt); end;
3.2シンプルデータ タイプとの連携
シンプルデータ タイプを変換する方法を検証します。最も一般的に使用されるint、double、dbool、ddatetimeを例として使用します。
Object Pascalを呼びます。
//----------------------------------------------------------+ function SetParam(var i: Integer; d: Double; const b: Boolean; var dt: Int64): PWideChar; stdcall; //----------------------------------------------------------+ begin if (b) then d:=0; // the value of the variable d is not changed in the calling program i:= 10; // assign a new value for i dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt Result:= 'value of variables i and dt are changed'; end;
MQL5を呼びます。
#import "dll_mql5.dll" string SetParam(int &i, double d, bool b, datetime &dt); #import // initialization of variables int i = 5; double d = 2.8; bool b = true; datetime dt= D'05.05.2010 08:31:27'; // calling the function s=SetParam(i,d,b,dt); // output of results printf("%s i=%s d=%s b=%s dt=%s",s,IntegerToString(i),DoubleToString(d),b?"true":"false",TimeToString(dt));結果
変数 i と dt の値は i = 10 d = 2.80000000 b = true dt = 2009.05 に変わります。 05 08 : 42
値 d は値によって変換されているのでここでは変わりません。変数の値が変わるのを防ぐため、DLL関数内部のモディファイアconstが変数bに使用されています。
3.3ストラクチャと配列の連携
異なるタイプのパラメータをストラクチャに、またあるタイプのパラメータを配列にグループ化すると役に立つことが多いものです。前に上げた例から、SetParam関数の変換されたパラメータすべてをストラクチャに統合する作業について考察します。
Object Pascalを呼びます。
type StructData = packed record i: Integer; d: Double; b: Boolean; dt: Int64; end; //----------------------------------------------------------+ function SetStruct(var data: StructData): PWideChar; stdcall; //----------------------------------------------------------+ begin if (data.b) then data.d:=0; data.i:= 10; // assign a new value for i data.dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt Result:= 'The values of variables i, d and dt are changed'; end;
MQL5を呼びます。
struct STRUCT_DATA { int i; double d; bool b; datetime dt; }; #import "dll_mql5.dll" string SetStruct(STRUCT_DATA &data); #import STRUCT_DATA data; data.i = 5; data.d = 2.8; data.b = true; data.dt = D'05.05.2010 08:31:27'; s = SetStruct(data); printf("%s i=%s d=%s b=%s dt=%s", s, IntegerToString(data.i),DoubleToString(data.d), data.b?"true":"false",TimeToString(data.dt));結果
変数 i 、 d 、dt の値は i = 10 d = 0.00000000 b = true dt = 2009.05 に変わります。 05 12 : 19
前に挙げた例との大きな違いを知っておくことは重要です。ストラクチャは参照を使って変換されるので、選択フィールドが呼ばれた関数内で編集されないように保護されない可能性があります。この場合、データの統合を監視するタスクは全面的にプログラマの仕事です。
では、連続するフィボナッチの数列を配列に書いていく例で配列の連携を考えていきます。
Object Pascalを呼びます。
//----------------------------------------------------------+ function SetArray(var arr: IntegerArray; const len: Cardinal): PWideChar; stdcall; //----------------------------------------------------------+ var i:Integer; begin Result:='Fibonacci numbers:'; if (len < 3) then exit; arr[0]:= 0; arr[1]:= 1; for i := 2 to len-1 do arr[i]:= arr[i-1] + arr[i-2]; end;
MQL5を呼びます。
#import "dll_mql5.dll" string SetArray(int &arr[],int len); #import int arr[12]; int len = ArraySize(arr); // passing the array by reference to be filled by data in DLL s = SetArray(arr,len); //output of result for(int i=0; i<len; i++) s = s + " " + IntegerToString(arr[i]); printf(s);結果
フィボナッチの数列0 1 1 2 3 5 8 13 21 34 55 89
3.4ストリング連携
メモリ管理に戻ります。DLLでは、自分自身のメモリ管理を操作することが可能です。ただし、DLLとそれを呼ぶプログラムがしばしば異なるプログラム言語で書かれ、一般的システムメモリでなく作業に個別のメモリ管理が使われるため、 DLL とアプリケーションを共に使う際、メモリ操作の正確性に関する負荷はすべてプログラマが負うところとなります。
メモリ作業をするには、黄金律に従うことが大切です。それは、「メモリ割り当てをする者はそれを解き放つものである。」というものです。すなわち DLLに割り当てられた mql5プログラムコードでメモリを解放してはならない。その逆もしかり。
Windows API関数呼び出し法でメモリ管理の例を考察していきます。ここでの場合、mql5プログラムはメモリをバッファに割り当てます。DLLに PWideChar として渡されるバッファのポインタです。そしてDLLだけがこのバッファに必要な値を書きこみます。以下の例にそのことが示されています。
Object Pascalを呼びます。
//----------------------------------------------------------+ procedure SetString(const str:PWideChar) stdcall; //----------------------------------------------------------+ begin StrCat(str,'Current time:'); strCat(str, PWideChar(TimeToStr(Now))); end;
MQL5を呼びます。
#import "dll_mql5.dll" void SetString(string &a); #import // the string must be initialized before the use // the size of the buffer must be initially larger or equal to the string length StringInit(s,255,0); //passing the buffer reference to DLL SetString(s); // output of result printf(s);
結果
現在時刻: 11: 48:51
以下の例に示すように、ラインバッファ用のメモリは複数の方法でDLLから選択可能です。
Object Pascalを呼びます。
//----------------------------------------------------------+ function GetStringBuffer():PWideChar; stdcall; //----------------------------------------------------------+ var StrLocal: WideString; begin // working through the dynamically allocated memory buffer StrPCopy(Buffer, WideFormat('Current date and time: %s', [DateTimeToStr(Now)])); // working through the global varialble of WideString type StrGlobal:=WideFormat('Current time: %s', [TimeToStr(Time)]); // working through the local varialble of WideString type StrLocal:= WideFormat('Current data: %s', [DateToStr(Date)]); {A} Result := Buffer; {B} Result := PWideChar(StrGlobal); // it's equal to the following Result := @StrGlobal[1]; {С} Result := 'Return of the line stored in the code section'; // pointer to the memory, that can be released when exit from the function {D} Result := @StrLocal[1]; end;MQL5を呼びます。
#import "dll_mql5.dll" string GetStringBuffer(void); #import printf(GetStringBuffer());
結果
現在日付: 2010年5月19日
すばらしいのは4つのオプションすべてが動作することです。最初の2つのオプションでは、グローバルに割り当てられたメモリによってラインに連携が行われます。
オプションAでは、メモリは独立して割り当てられます。オプションBでは、メモリ管理との連携は目盛マネージャによって行われると推測されます。
オプションCでは、ライン定数はメモリではなくコードセグメントに保存され、メモリマネージャはそのストレージに動的メモリを割り当てません。 オプションDは明確なプログラミングエラーです。ローカル変数に割り当てられたメモリは関数実行後すぐに解放される可能性があるからです。
また、メモリマネージャがこのメモリをすぐに解放せず、トラッシュで書き込みをする時間がなかったとしても、後者のオプションの使用はお薦めできません。
3.5デフォルトパラメータの使用
その他選択としてのパラメータ使用について述べます。それらが興味深いのは、プロシージャや関数を呼ぶとき、その値は指定される必要がないからです。とはいうものの、以下に例示するように、プロシージャや関数の宣言に際しすべての必須パラメータの後に必ず記述はされる必要があります。
Object Pascalを呼びます。
//----------------------------------------------------------+ function SetOptional(var a:Integer; b:Integer=0):PWideChar; stdcall; //----------------------------------------------------------+ begin if (b=0) then Result:='Call with default parameters' else Result:='Call without default parameters'; end;MQL5を呼びます。
#import "dll_mql5.dll" string SetOptional(int &a, int b=0); #import i = 1; s = SetOptional(i); // second parameter is optional printf(s);
結果
デフォルトパラメータを呼びます。
デバッグを簡単にするために、上記例のコードはスクリプトとして生成される必要があります。それは、次のファイルに配置します。 Testing_DLL.mq5
4. 設計段階で発生しうるエラー
エラー: DLLロードは許可されていません。
状況:メニュー ' Tools-Options' によってMetaTrader 5設定に移動します。 そして図5に示すようDLL関数のインポートを許可します。
図5 DLL関数インポートの許可
エラー:『DLL名』に『関数名』が見つかりません。
解決:DLLプロジェクトの「エクスポート」項目でコールバック関数が指定されているか確認します。指定されていれば、DLLで関数名が完全一致しているか確認する必要があります。大文字小文字の区別が必要です!
エラー: [メモリアドレス]書き込みアクセス障害
解決法:送信されたパラメータ記述が正しいか確認する必要があります。(表2参照)通常このエラーはライン処理に伴うもので、本稿3.4にあるライン連携の推奨に従うことが重要です。
5. DLLコード例
DLL使用の視覚的例として、3行で構成されるレグレッションチャンネルの計算パラメータに配慮します。canalのコンストラクションが正しいことを検証するのに、内蔵の『Canalレグレッション』を利用します。近似線のLS(最小二乗法)計算はサイト http://alglib.sources.ru/から取り入れています。 そこにはデータ処理アルゴリズムが集められています。アルゴリズムコードはDelphiを含め、数々のプログラム言語で紹介されています。
aおよびbの係数を近似線y = a + b * xで計算するには、LRLine linreg.pas.ファイルに記述されているプロシージャを使用します。
procedure LRLine ( const XY: TReal2DArray; / / Two-dimensional array of real numbers for X and Y coordinates N : AlglibInteger; // number of points var Info : AlglibInteger; // conversion status var A: Double; / / Coefficients of the approximating line var B: Double);
チャンネルのパラメータを計算するには、CalcLRChannel関数を使用します。
Object Pascalを呼びます。
//----------------------------------------------------------+ function CalcLRChannel(var rates: DoubleArray; const len: Integer; var A, B, max: Double):Integer; stdcall; //----------------------------------------------------------+ var arr: TReal2DArray; info: Integer; value: Double; begin SetLength(arr,len,2); // copy the data to a two-dimensional array for info:= 0 to len - 1 do begin arr[info,0]:= rates[info,0]; arr[info,1]:= rates[info,1]; end; // calculation of linear regression coefficients LRLine(arr, len, info, A, B); // find the maximal deviation from the approximation line found // and determine the width of the channel max:= rates[0,1] - A; for info := 1 to len - 1 do begin value:= Abs(rates[info,1]- (A + B*info)); if (value > max) then max := value; end; Result:=0; end;
MQL5を呼びます。
#import "dll_mql5.dll" int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max); #import double arr[][2], //data array for processing in the ALGLIB format a, b, // Coefficients of the approximating line max; // maximum deviation from the approximating line is equal to half the width of the channel int len = period; //number of points for calculation ArrayResize(arr,len); // copying the history to a two-dimensional array int j=0; for(int i=rates_total-1; i>=rates_total-len; i--) { arr[j][0] = j; arr[j][1] = close[i]; j++; } // calculation of channel parameters CalcLRChannel(arr,len,a,b,max);
計算にCalcLRChannel関数を使用するインディケータコードは、 LR_Channel.mq5 と下記に配置されています。
//+------------------------------------------------------------------+ //| LR_Channel.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include <Charts\Chart.mqh> #include <ChartObjects\ChartObjectsChannels.mqh> #import "dll_mql5.dll" int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max); #import input int period=75; CChart *chart; CChartObjectChannel *line_up,*line_dn,*line_md; double arr[][2]; //+------------------------------------------------------------------+ int OnInit() //+------------------------------------------------------------------+ { if((chart=new CChart)==NULL) {printf("Chart not created"); return(false);} chart.Attach(); if(chart.ChartId()==0) {printf("Chart not opened");return(false);} if((line_up=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} if((line_dn=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} if((line_md=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} return(0); } //+------------------------------------------------------------------+ 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[]) //+------------------------------------------------------------------+ { double a,b,max; static double save_max; int len=period; ArrayResize(arr,len); // copying of history to a two-dimensional array int j=0; for(int i=rates_total-1; i>=rates_total-len; i--) { arr[j][0] = j; arr[j][1] = close[i]; j++; } // procedure of calculating the channel parameters CalcLRChannel(arr,len,a,b,max); // if the width of the channel has changed if(max!=save_max) { save_max=max; // Delete the channel line_md.Delete(); line_up.Delete(); line_dn.Delete(); // Creating a channel with new coordinates line_md.Create(chart.ChartId(),"LR_Md_Line",0, time[rates_total-1], a, time[rates_total-len], a+b*(len-1) ); line_up.Create(chart.ChartId(),"LR_Up_Line",0, time[rates_total-1], a+max, time[rates_total-len], a+b*(len-1)+max); line_dn.Create(chart.ChartId(),"LR_Dn_Line",0, time[rates_total-1], a-max, time[rates_total-len], a+b*(len-1)-max); // assigning the color of channel lines line_up.Color(RoyalBlue); line_dn.Color(RoyalBlue); line_md.Color(RoyalBlue); // assigning the line width line_up.Width(2); line_dn.Width(2); line_md.Width(2); } return(len); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) //+------------------------------------------------------------------+ { // Deleting the created objects chart.Detach(); delete line_dn; delete line_up; delete line_md; delete chart; }
インディケータ作業の結果、図6に示す青のリグレッション チャンネルを作成しました。チャンネルのコンストラクションが正確であることを確認するために、チャートには技術分析のインスツルメントであるMetaTrader 5のスタッフィング アーセナルから「リグレッションカナル」を表示し、赤でマークしています。
図にあるように、チャンネルの中央ラインは一つに結合しています。また、チャンネル(数ポイント)の幅にはかすかな違いがありますが、それは計算方法が異なるためです。
図6 リグレッションチャンネルの比較
おわりに
本稿はDelphiアプリケーション開発プロットフォームを用いてDLLを書く機能について述べてきました。
MetaQuotes Ltdによってロシア語から翻訳されました。
元の記事: https://www.mql5.com/ru/articles/96





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