Разработка сайта для Вашего бизнеса. Веб дизайн. Дизайн логотипа, фирменного стиля, рекламная фотография . Комплексный рекламный креатив.

Ralex. We do the work.
На рынке с 1999го года. Средняя ценовая категория. Ориентация на эффективность решений.
Ознакомтесь с нашим портфолио
Узнайте больше о услугах
Свяжитесь с нами:
E-mail: [email protected]
Tel: (044) 587 - 84 - 78
Custom web design & дизайн и разработка сайта "под ключ"
Креативный, эффективный дизайн. Система управления сайтом (СУС).
Custom flexible разработка систем электронной коммерции
Система e-commerce разрабатывается под индивидуальные потребности. Гибкая функциональность.
Search Engine Optimzation & оптимизация под поисковые системы (SEO)
Постоянная оптимизация и мониторинг сайта в поисковых системах. Достигаем результата быстро и эффективно
Custom logo design & дизайн логотипа и фирменного стиля
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.
профессиональная рекламная фотография
креативно, смело, качественно
Custom logo design & рекламный креатив. дизайн рекламы
Многолетний опыт. Огромное портфолио. Уникальное предложение и цена.

Керівництво з написання DLL для MQL5 на Delphi

  1. Вступ Механізм написання DLL буде розглянуто на прикладі середовища розробки Delphi 2009. Вибір саме...
  2. 3. Передача параметрів в функцію і повертаються значення
  3. 4. Можливі помилки на етапі розробки
  4. 5. Приклад коду DLL
  5. висновок

Вступ

Наша взаимовыгодная связь https://banwar.org/

Механізм написання DLL буде розглянуто на прикладі середовища розробки Delphi 2009. Вибір саме цієї версії обумовлений тим, що в MQL5 рядки зберігаються в форматі Юнікод. А в більш старих версіях Delphi в модулі SysUtils відсутні функції для роботи з рядками Юнікод формату.

Якщо ви, з якихось причин, використовуєте більш ранню версію (Delphi 2007 і нижче), то вам доведеться працювати з рядками в ANSI форматі, а для обміну даними з MetaTrader 5 виробляти пряме і зворотне перетворення в Юнікод. Щоб уникнути таких складнощів рекомендую розробляти DLL модуль для MQL5 в середовищі не нижче ніж Delphi 2009. Ознайомчу, 30-й денну версію Delphi можна скачати з офіційного сайту http://embarcadero.com/

1. Створення проекту

Для створення проекту необхідно запустити DLL Wizard вибравши пункт меню: 'File -> New -> Other ... -> DLL Wizard', як показано на малюнку 1.

Малюнок 1. Створення проекту за допомогою DLL Wizard

В результаті, буде створено порожній проект DLL, як показано на малюнку 2.

В результаті, буде створено порожній проект DLL, як показано на малюнку 2

Малюнок 2. Порожній проект DLL

Суть довгого коментаря в заголовку проекту зводиться до нагадування про правильне підключенні і використанні менеджера пам'яті при роботі з динамічно виділеної пам'яттю. Про це більш детально буде розказано в розділі, присвяченому роботі з рядками.

Перед тим як приступити до наповнення DLL новими функціями, важливо налаштувати проект.

Викличте вікно властивостей проекту через меню: 'Project -> Options ...' або через поєднання клавіш 'Shift + Ctrl + F11'.

З метою спрощення налагодження, необхідно, щоб файл DLL створювався безпосередньо в папці '.. \ MQL5 \ Libraries' торгового терміналу MetaTrtader5. Для цього на закладці DelphiCompiler задайте відповідне значення властивості Output directory, як показано на малюнку 3. Це позбавить вас від необхідності постійного копіювання файлу створюваної DLL з папки проекту в папку терміналу.

Це позбавить вас від необхідності постійного копіювання файлу створюваної DLL з папки проекту в папку терміналу

Малюнок 3. Завдання папки для розміщення результуючого файлу DLL

Щоб в процесі складання не підключалися модулі сімейства BPL, без наявності яких, в системній папці Windows, створювана DLL в майбутньому не зможе працювати, важливо проконтролювати, щоб на вкладці Packages, прапор Build with runtime packages був знятий, як показано на малюнку 4.

Малюнок 4. Виключення з збірки, модулів сімейства BPL

Після настройки проекту, збережіть його в вашу робочу папку, з огляду на, що задається ім'я проекту є майбутнім ім'ям скомпільованої DLL файлу.

2. Додавання процедур і функцій

Розглянемо загальні положення при написанні експортованих процедур і функцій в модулі DLL, на прикладі процедури без параметрів. Оголошення і передача параметрів буде розглянута в наступному розділі.

Невеликий відступ. При написанні процедур і функцій на мові Object Pascal в розпорядження програміста з'являється можливість використовувати вбудовані бібліотечні функції Delphi, не кажучи про численному безлічі компонентів розроблених для цього середовища. Наприклад, для виконання одного і того ж дії, виведення на екран модального вікна з текстовим повідомленням, можна використовувати як API функцію - MessageBox так і процедуру з VCL бібліотеки - ShowMessage.

Другий варіант передбачає підключення модуля Dialogs, даючи перевагу простій роботи зі стандартними діалоговими вікнами Windows. Однак, розмір результуючого DLL файлу збільшиться приблизно на 500 Кб. Тому, якщо ви прихильник створювати маленькі, по займаному дискового простору, DLL файли, вам доведеться відмовитися від використання VCL.

Нижче наведено приклад тестового проекту з поясненнями:

library

dll_mql5; uses Windows, // необхідний для роботи функції MessageBox Dialogs; // необхідний для роботи процедури ShowMessage з модуля Dialogs var Buffer: PWideChar; procedure MsgBox (); stdcall; begin {1} MessageBox (0, 'Hello World!', 'terminal', MB_OK); {2} ShowMessage ( 'Hello World!'); // альтернатива функції MessageBox end; exports {A} MsgBox, {B} MsgBox name 'MessageBox'; procedure DLLEntryPoint (dwReason: DWord); begin case dwReason of DLL_PROCESS_ATTACH: Buffer: = AllocMem (BUFFER_SIZE); DLL_PROCESS_DETACH: FreeMem (Buffer); end; end; begin DllProc: = @DLLEntryPoint; DLLEntryPoint (DLL_PROCESS_ATTACH); end.

Все що експортуються функції повинні бути оголошені з модифікатором stdcall або cdecl. Якщо жоден з цих модифікаторів не вказано, за замовчуванням Delphi використовує угоду fastcall в першу чергу використовує для передачі ці установки не були стек, а регістри процесора, що без сумніву призведе до помилки в роботі з переданими параметрами на етапі виклику зовнішніх функції DLL.

Секція begin end містить стандартний код ініціалізації обробника подій DLL. Callback-процедура DLLEntryPoint буде викликана при приєднанні і від'єднанні від викликав її процесу. Ці події можна використовувати для коректного управління динамічною пам'яттю, що виділяється для власних потреб, як показано в прикладі.

Виклик для MQL5:

#import "dll_mql5.dll" void MsgBox (void); void MessageBox (void); #import MsgBox (); 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

Як видно з таблиці, для всіх типів даних крім datetime, в Delphi існує повний аналог.

Тепер розглянемо два способи передачі параметрів: за значенням і за посиланням. Формат оголошення параметрів для обох варіантів наведено в таблиці 2.

Спосіб передачі параметрів
Оголошення для MQL5
Оголошення для Delphi
Примітка за значенням
int func (int a); func (a: Integer): Integer; правильно
int func (int a);
func (var a: Integer): Integer;
помилка: access violation write to <memory address> за посиланням
int func (int & a);
func (var a: Integer): Integer;
правильно, однак рядка передаються без модифікатора var! int func (int & a); func (a: Integer): Integer; помилка: замість значення змінна містить адресу комірки пам'яті

Таблиця 2. Способи передачі параметрів

Тепер перейдемо до розгляду прикладів роботи з переданими параметрами і повертаються значеннями.

3.1 Перетворення дати і часу

Спочатку, розберемося з типом дати і часу, який необхідно конвертувати, тому що тип datetime відповідає TDateTime лише за розміром, але не по формату. Для зручності перетворення, як прийнятого типу даних замість TDateTime використовуйте Int64. Нижче наведені функції для прямого і зворотного перетворення:

uses

SysUtils, DateUtils; // використовується для функції IncSecon, DateTimeToUnix

3.2 Робота з простими типами даних

Подивимося, як передавати прості типи даних на прикладі найбільш затребуваних int, double, bool і datetime.

Виклик для Object Pascal:

function SetParam (var i: Integer; d: Double; const b: Boolean; var dt: Int64): PWideChar;stdcall;begin if (b) then d: = 0;// значення змінної d не змінюється в зухвалій програмі i: = 10;dt: = TDateTime_To_MQL5_Time (Now ());Result: = 'Значення змінних i і dt змінені';end;

Виклик для MQL5:

#import "dll_mql5.dll" string SetParam (int & i, double d, bool b, datetime & dt); #import int i = 5; double d = 2.8; bool b = true; datetime dt = D'05.05.2010 8:31:27 '; s = SetParam (i, d, b, dt); printf ( "% si =% sd =% sb =% 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 до змінної b був застосований модифікатор const.

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; // задаємо нове значення для i data.dt: = TDateTime_To_MQL5_Time (Now ()); // задаємо даний час для dt Result: = 'Значення змінних i, d і dt змінені'; 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 8:31:27 '; s = SetStruct (data); printf ( "% si =% sd =% sb =% 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: = 'Числа Фібоначчі:'; 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); s = SetArray (arr, len); 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 і додатки лежить на програмістові.

Для роботи з пам'яттю, важливо дотримуватися золотого правила, яке звучить приблизно так: "Той, хто виділив пам'ять, той і повинен її звільнити". Тобто ви не повинні намагатися звільняти пам'ять в коді mql 5-програми виділеної в DLL, і навпаки.

Щоб піти від роботи з менеджером пам'яті в сторону самостійного контролю над її виділенням і звільненням, важливо, як говоритися в коментарі до проекту DLL, навчиться використовувати тип PWideChar таким чином, як він використовується в API-функції. У нашому випадку mql 5-програма виділяє пам'ять для буфера, покажчик на цей буфер передає в DLL як PWideChar, а DLL тільки заносить в цей буфер необхідне значення, як показано в наступному прикладі:

Виклик для Object Pascal:

// ------------------------------------------------ ---------- + procedure SetString (const str: PWideChar) stdcall;// ------------------------------------------------ ---------- + begin StrCat (str, 'Часовий пояс GMT');strCat (str, PWideChar (TimeToStr (Now)));end;

Виклик для MQL5:

#import "dll_mql5.dll" void SetString (string & a); #import StringInit (s, 255, 0); SetString (s); printf (s);

результат:

Поточний час: 11: 48: 51

Пам'ять для строкового буфера можна виділити і в DLL декількома способами, як видно з наступного прикладу:

Виклик для Object Pascal:

// ------------------------------------------------ ---------- + function GetStringBuffer (): PWideChar; stdcall; // ------------------------------------------------ ---------- + var StrLocal: WideString; begin // робота через динамічно виділений буффер пам'яті StrPCopy (Buffer, WideFormat ( 'Поточна дата і час:% s', [DateTimeToStr (Now)])); // робота через глобальну змінну типу WideString StrGlobal: = WideFormat ( 'Часовий пояс GMT% s', [TimeToStr (Time)]); // робота через локальну змінну типу WideString StrLocal: = WideFormat ( 'Поточна дата:% s', [DateToStr (Date)]); {A} Result: = Buffer; {B} Result: = PWideChar (StrGlobal); // це рівносильно наступному записі Result: = @StrGlobal [1]; {С} Result: = 'Повернення рядки зберігається в секції коду'; // посилання на пам'ять яка може бути звільнена при виході з функції {D} Result: = @StrLocal [1]; end; Виклик для MQL5: #import "dll_mql5.dll" string GetStringBuffer (void); #import printf (GetStringBuffer ());

результат:

Current Date: 19.05 .2010

Що характерно, працюють всі чотири варіанти. У перших двох варіантах робота з рядком ведеться через глобально виділену пам'ять.

У варіанті А пам'ять розподіляється самостійно а в варіанті B робота з управління пам'яттю бере на себе менеджер пам'яті.

У варіанті З строкова константа зберігається не в пам'яті а в сегменті коду, тому менеджер пам'яті не виділяє для її зберігання обсяг динамічної пам'яті. Варіант D є грубою помилкою програмування, тому що пам'ять виділена під локальну змінну може бути звільнена відразу після виходу з функції.

І хоча менеджер пам'яті не звільняє цю пам'ять миттєво і вона не встигає бути заповненою сміттям, раджу останній варіант виключити з ужитку.

3.5 Використання параметрів за замовчуванням

Поговоримо про використання необов'язкових параметрів. Вони цікаві тим, що їх значення можна не вказувати при виклику процедур і функцій. При цьому, вони повинні бути описані суворо після всіх об'язательно параметрів в оголошенні процедур і функцій, як показано в наступному прикладі:

Виклик для Object Pascal:

function

SetOptional (var a: Integer; b: Integer = 0): PWideChar; stdcall; begin if (b = 0) then Result: = 'Виклик з параметром за замовчуванням' else Result: = 'Виклик без параметрів за замовчуванням'; end; Виклик для MQL5: #import "dll_mql5.dll" string SetOptional (int & a, int b = 0); #import i = 1; s = SetOptional (i); // другий параметр є необов'язковим printf (s);

результат:

Виклик з параметром за замовчуванням

Для зручності налагодження, код з наведеними вище прикладами організований у вигляді скрипта і знаходиться в файлі Testing _DLL. mq 5.

4. Можливі помилки на етапі розробки

Помилка: DLL Loading is not allowed.

Рішення: Зайшовши в налаштування MetaTrader 5 через меню 'Сервіс -> Настройки' необхідно дозволити імпорт функцій DLL, як показано на малюнку 5.

Малюнок 5. Дозвіл імпорту функцій DLL

Помилка: Can not find 'function name' in 'DLL name'.
Рішення: Перевірте, чи вказана викликається, в розділі Exports проекту DLL. Якщо так, то варто перевірити повний збіг імені функції в DLL і mql5-програмою з урахуванням регістру символів!

Помилка: access violation write to [memory address]
Рішення: Необхідно перевірити правильність опису переданих параметрів (дивись таблицю 2). Оскільки найчастіше дана помилка пов'язана з обробкою рядків, важливо дотримуватися рекомендацій по роботі з рядками викладеним в пункті 3.4 цієї статті.

5. Приклад коду DLL

Як наочний приклад використання DLL, розглянемо розрахунок параметрів каналу регресії складається з трьох прямих. Для перевірки правильності побудови каналу будемо використовувати вбудований об'єкт "Канал регресії". Розрахунок апроксимуючої прямої по МНК (метод найменших квадратів) узятий з сайту http://alglib.sources.ru/ , Де знаходиться колекція алгоритмів по обробці даних. Код алгоритмів представлений на декількох мовах програмування включаючи Delphi.

Для розрахунку коефіцієнтів a і b, апроксимуючої прямої y = a + b * x, використовується процедура LRLine описана в файлі linreg.pas.

procedure

LRLine (const XY: TReal2DArray; // двовимірний масив дійсних чисел для X і Y координат N: AlglibInteger; // кількість точок var Info: AlglibInteger; // статус перетворення var A: Double; // коефіцієнти апроксимуючої прямої 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); for info: = 0 to len - 1 do begin arr [info, 0]: = rates [info, 0]; arr [info, 1]: = rates [info, 1]; end; LRLine (arr, len, info, A, B); 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], // масив даних для обробки в форматі ALGLIB a, b, // коефіцієнти апроксимуючої прямої max; // максимальне відхилення ціни від апроксимуючої лінії дорівнює половині ширини каналу int len ​​= period; // кількість точок для розрахунку ArrayResize (arr, len); int j = 0; for (int i = rates_total- 1; i> = rates_total-len; i--) {arr [j] [0] = j; arr [j] [1] = close [i]; j ++; } CalcLRChannel (arr, len, a, b, max);

Код індикатора, який використовує в розрахунках функцію CalcLRChannel, знаходиться в файлі LR_Channel.mq5 і наведено нижче:

#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); int j = 0; for (int i = rates_total- 1; i> = rates_total-len; i--) {arr [j] [0] = j; arr [j] [1] = close [i]; j ++; } CalcLRChannel (arr, len, a, b, max); if (max! = save_max) {save_max = max; line_md.Delete (); line_up.Delete (); line_dn.Delete (); 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); line_up.Color (RoyalBlue); line_dn.Color (RoyalBlue); line_md.Color (RoyalBlue); line_up.Width (2); line_dn.Width (2); line_md.Width (2); } Return (len); } Void OnDeinit (const int reason) {chart.Detach (); delete line_dn; delete line_up; delete line_md; delete chart; }

Результатом роботи індикатора є побудова каналу регресії блакитного кольору, як показано на малюнку 6. Для перевірки правильності побудови каналу, на графік завдано "Канал регресії", з штатного арсеналу інструментів технічного аналізу Metatrader 5, показаний червоним кольором.

Як видно з малюнка, центральні лінії каналу зливаються. При цьому існує невелике розходження в ширині каналу (кілька пунктів), що пояснюється різним підходом до її розрахунку.

Малюнок 6. Порівняння каналів регресії

висновок

У статті розглянуто особливості написання DLL з використанням платформи розробки додатків Delphi.

S = SetParam (i, d, b, dt); printf ( "% si =% sd =% sb =% s dt =% s", s, IntegerToString (i), DoubleToString (d), b?
B?
Категории
  • Биология
  • Математика
  • Краеведению
  • Лечебная
  • Наука
  • Физике
  • Природоведение
  • Информатика
  • Новости

  • Новости
    https://banwar.org/
    Наша взаимовыгодная связь https://banwar.org/. Запустив новый сайт, "Пари Матч" обещает своим клиентам незабываемый опыт и возможность выиграть крупные суммы.


    Наши клиенты
    Клиенты

    Быстрая связь

    Тел.: (044) 587-84-78
    E-mail: [email protected]

    Имя:
    E-mail:
    Телефон:
    Вопрос\Комментарий: