• Главная
  • Карта сайта
Не найдено

Як відкрити світ C # з MQL5 шляхом експорту некерованого коду

  1. Вступ
  2. 1. Керований і некерований код
  3. Висновки

Вступ

Довгий час я шукав просте рішення, яке дозволило б мені використовувати в MQL5 керовані (managed) DLL, написані на C #. Після читання безлічі статей, коли я вже був готовий реалізувати обгортку (wrapper) для керованої DLL на С ++, я натрапив на блискуче рішення, заощадити мені багато годин роботи.

Пропоноване рішення є простим прикладом експорту керованого C #-коду для некерованого (unmanaged) додатки. У даній статті я розгляну основи роботи керованих DLL, поясню причини, через які вони не можуть бути використані безпосередньо в MetaTrader 5, і запропоную знайдені мною рішення, які дозволять використовувати керований код з MetaTrader 5.

Я приведу приклад найпростішого використання шаблонів експорту некерованого коду (unmanaged exports) і розгляну моменти, які я виявив. Це може послужити основою для всіх, хто намагається використовувати в MetaTrader 5 бібліотеки DLL, написані на C #.

1. Керований і некерований код

Оскільки більшість читачів можуть бути не знайомі з відмінностями між керованим і некерованим кодом, коротко опишемо їх. Для реалізації торгівлі, індикаторів, радників і скриптів в MetaTrader 5 в основному використовується мова MQL5. Крім того, існує можливість використання (і динамічного завантаження) готових бібліотек, написаних на інших мовах. Ці бібліотеки також відомі як DLL або спільні бібліотеки .

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

Зростаючий інтерес до використання DLL в MetaTrader 5 обумовлений можливістю приховування деяких частин реалізації індикаторів або радників. Основна причина використання бібліотек - можливість повторного використання коду без необхідності його постійного переписування.

До появи платформи .NET всі існуючі DLL-бібліотеки компілювалися за допомогою Visual Basic, Delphi, VC ++, були реалізовані в вигляді COM , Win32, при цьому вони могли виконуватися безпосередньо операційною системою. Далі такий код ми будемо називати некерованим (unmanaged) або нативним (native) кодом.

Потім з'явилася платформа .NET з абсолютно іншим оточенням. Код контролюється (або управляється) загальномовного виконуючого середовищем (Common Language Runtime, CLR ). З вихідного коду, який може бути написаний на декількох різних мовах, CLR компілятори генерують код і метадані в проміжному мовою CIL (Common Intermediate Language).

CIL є машинно-незалежним мовою високого рівня, а метадані повністю описують типи об'єктів, оперованих CIL в форматі загальної специфікації типів (Common Type Specification, CTS ). Оскільки CLR відомо все про типах, він може надати нам кероване оточення виконання. Управління можна розглядати як збірку сміття - автоматичне керування пам'яттю, видалення об'єктів і забезпечення безпеки від загальних помилок native-мов, які можуть бути обумовлені виконанням чужорідного коду з привілеями адміністратора або простим перекриттям областей пам'яті.

Необхідно відзначити, що код на CIL ніколи не виконується безпосередньо, він завжди транслюється в native-код в режимі компіляції "на льоту" ( JIT , Just-In-Time) або попередньої компіляцією CIL в native-код:


Малюнок 1. Загальномовне керуюча середовище (CLR, Common Language Runtime)


2. Можливі варіанти реалізації доступу до керованого коду з MQL5

У наступних параграфах я розгляну методи, що дозволяють отримати доступ до керованого коду з некерованого коду.

Я вважаю, варто згадати всі відомі методи, можливо хтось вважатиме за краще використовувати інший спосіб, відмінний від того, який використовую я. Відомі методи: COM Interop, Reverse P / Invoke, C ++ IJW, C ++ / Cli class wrapper і Unmanaged Exports.

2.1. COM Interop (COM-взаємодія)

Об'єктна модель компонентів (Component Object Model, COM ) - це стандарт інтерфейсу, запропонований Microsoft на початку 90-х. Ключова ідея цієї технології полягає в можливості використання об'єктів, написаних на різних мовах програмування будь-яким іншим COM-об'єктом без знання його внутрішньої реалізації. Такі вимоги призводять до використання строго заданого інтерфейсу COM, повністю відокремленого від реалізації.

Фактично COM був замін технологією .NET, а Microsoft підштовхує до використання .NET замість COM. Для забезпечення зворотної сумісності зі старим кодом .NET може здійснювати взаємодію з COM в обох напрямках: .NET може викликати методи COM об'єкта, а COM об'єкт може використовувати керований код .NET.

Ця функціональність називається COM-взаємодія (COM Interoperability) або COM Interop. Програмний інтерфейс (API) COM-взаємодії знаходиться в просторі імен (namespace) System.Runtime.InteropServices.


Малюнок 2. Модель COM-взаємодії (COM Interoperability)

Наведений нижче код 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 і приклад використання How to call C ++ code from Managed, and vice versa (Interop) , Знайдений мною на блозі MSDN.

2.2. Reverse P / Invoke (передзвонити некерованого коду)

Виклик некерованого коду (Platform Invoke, P / Invoke) дозволяє .NET викликати будь-яку функцію некерованого мови відповідно до її сигнатурою. Це здійснюється виконанням нативной функції з .NET. Використання добре пояснено в статті Platform Invoke Tutorial .

В основі лежить використання атрибута DllImport для маркування імпортованої функції:

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 (); }}

Зворотна дія може бути здійснено через керовану callback-функцію в некерований код.

Це називається зворотним викликом некерованого коду (Reverse P / Invoke) і досягається реалізацією публічного делегата (delegate) з керованою середовища і імпортом викликається функції, реалізованої в нативної 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 ++ interop), також відомий як IJW (It Just Works) є спеціальною можливістю Managed Extensions for 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 ++ код в native-додатках. Для подальші вивчення подивіться статті Interoperability in Managed Extensions for C ++ і Using IJW in Managed C ++ .

2.4. C ++ / Cli wrapper class (Клас обгортки C ++ / Cli)

Реалізація класу за допомогою обгортки C ++ / Cli полягає у вкладенні (embedding) або "обгортці" (wrapping) керованого класу іншим класом, написаним в режимі C ++ / Cli.

Першим кроком до написання DLL-обгортки є написання класу на C ++, який обгортає методи вихідного керованого класу. Клас обгортки повинен містити хендл об'єкта .NET за допомогою шаблону gcroot <> і повинен делегувати всі виклики з оригінального класу. Клас обгортки є керованим, оскільки він компілюється в форматі IL (проміжного мови).

Наступним кроком є написання native-класу на С ++ з директивою "#pragma unmanaged", яка обгортає клас в IL-коді і делегує всі виклики за допомогою директиви __declspec (dllexport) . Ці кроки створять native-DLL, яка може бути використана будь-яким native-додатком.

Будь ласка, подивіться приклад реалізації. Першим кроком є реалізація коду на C #.

Цей приклад класу Calculator містить два public-методу:

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

Наступний крок - написання керованої обгортки (managed wrapper), яка буде делегувати всі методи класу Calculator:

#pragma once #pragma managed #include <vcclr.h> class ILBridge_CppCliWrapper_Calculator {private: 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 ++ (некерований код), який обгортає C ++ / Cli і експортує native-методи в DLL.

Файл заголовка повинен містити визначення класу з директивою __declspec (dllexport) і зберігати покажчик на клас обгортки.

#pragma once #pragma unmanaged #ifdef THISDLL_EXPORTS #define THISDLL_API __declspec (dllexport) #else #define THISDLL_API __declspec (dllimport) #endif class ILBridge_CppCliWrapper_Calculator; class THISDLL_API NativeExport_CppCliWrapper_Calculator {private: 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 . З загальною інформацією про декларації хендлом в native-типах можна ознайомитися в статті How to: Declare Handles in Native Types .

2.5. Unmanaged exports (Експорт некерованого коду)

Ця техніка повністю описана в книзі Expert .NET 2.0 IL Assembler , Яку я рекомендую всім, хто хотів би почитати про деталі роботи компілятора .NET. Основна ідея полягає у відкритті керованих методів як експорт некерованого коду (unmanaged exports) в керованої DLL шляхом декомпіляцію (за допомогою ILDasm) скомпільованого модуля в IL-код, зміни таблиць VTable і VTableFixup модуля і повторна компіляція DLL за допомогою ILAsm.

Це завдання може здаватися дуже складною, але результатом буде створення DLL, яка може бути використана з будь-якого некерованого native-коду. Потрібно пам'ятати про те, що ця збірка все ще є керованою (managed), тому повинна бути встановлена платформа .NET. Покрокове керівництво викладено в статті Export Managed Code as Unmanaged .

Після декомпіляцію DLL за допомогою ILDasm ми отримаємо вихідний код на проміжному мовою 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

Перша частина відповідає за додавання точки входу в функцію в таблиці VTableFixup і установку в VT_01 ВА функції. Друга частина вказує на те, який саме VTEntry буде використовуватися для цієї функції і псевдонім експортованої функції (export alias).

В процесі реалізації DLL у нас не було необхідності реалізовувати ніякої додатковий код за винятком звичайного керованого коду C # в DLL. Як зазначено в книзі Expert .NET 2.0 IL Assembler , Цей метод повністю відкриває світ керованого коду з усією його безпекою та бібліотеками класів для програм, написаних на native-коді.

Недоліком є те, що робота з проміжним IL-мовою підходить не для всіх. Я був упевнений в тому, що мені доведеться писати обгортку у вигляді клас на C ++, поки я не знайшов шаблон Unmanaged exports, створений Robert Giesecke http://sites.google.com/site/robertgiesecke/ , Який дозволяє використовувати експорт некерованого коду (unmanaged exports) без необхідності роботи з IL-кодом.

3. Шаблон Unmanaged exports на C #

У шаблонах проектів по створенню експорту некерованого коду від R.Giesecke використовується MSBuild task , Який автоматично додає створення згаданих вище модифікацій для VT, тому немає необхідності зміни коду на IL. Потрібно тільки завантажити проект шаблону (у вигляді zip-файлу) і скопіювати його вміст в папку ProjectTemplate середовища Visual Studio.

Після компіляції проекту отриману DLL можна без проблем використовувати в MetaTrader 5, в наступних розділах я приведу приклади.

4. Приклади

Завдання з'ясування того, як передавати змінні, масиви і структури між MetaTrader 5 і C # виявилася вельми непростою, тому я вважаю, що інформація, викладена тут, дозволить вам заощадити багато часу. Всі приклади були скомпільовані в Windows Vista з .NET 4.0 і Visual C # Express 2010. До статті додається приклад DLL і коду на MQL5, який викликає функції з DLL, написаної на C #.

4.1. Приклад 1. Додавання двох змінних типу integer, double або float в функції DLL і повернення результату в MetaTrader 5

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. Перший параметр описує псевдонім експортованої функції, а другий параметр задає спосіб виклику (calling convention), для MetaTrader 5 слід використовувати CallingConvention.StdCall.

Код на MQL5, який імпортує і використовує функції, експортовані з DLL, є простим і не відрізняється від коду, написаного в C ++. Спочатку потрібно оголосити імпортовані функції всередині блоку #import для вказівки списку функцій з DLL, які в подальшому будуть викликатися з коду на MQL5:

#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 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. Доступ до одновимірного масиву

[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]; }

Для передачі одновимірного масиву в директиві MarshalAs в якості першого параметра потрібно вказати UnmanagedType.LPArray, а в якості другого - SizeParamIndex. Параметр SizeParamIndex вказує на те, який параметр (починаючи з 0) є параметром, що містить розмір масиву.

У прикладах, описаних вище масив має розмір i, а в якості значення, що повертається функцією, використовується елемент масиву з індексом idx.

Приклад доступу до масиву на MQL5 наведено нижче:

#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 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. Заповнення одновимірного масиву і повернення результату в MetaTrader 5

[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; }

У цьому прикладі використовуються два масиви для ілюстрації позначення вхідних параметрів. Якщо потрібно повернення елементів масиву (переданих за посиланням) в MetaTrader 5, досить вказати атрибути [In, Out,] перед атрибутом MarshalAs.

#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 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; }

Маршаллінг двовимірного масиву не є настільки простим, але я використовував трюк: передавав двовимірний масив як одновимірний і здійснював доступ до елементів масиву за допомогою додаткової функції idx.

#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 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 замість змінної string.

#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 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 .

#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 int OnInit () {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 []) {MqlTick newTick; double bidsum; SymbolInfoTick (Symbol (), newTick); Print ( "before =" + IntegerToString (newTick.volume)); Print (AddTick (newTick, bidsum)); Print ( "after =" + IntegerToString (newTick.volume) + ":" + DoubleToString (bidsum)); 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 #.

Також я підготував кілька прикладів маршалинга структур MQL5 для C # і прикладів виклику експортованих функцій DLL в скриптах на MQL5. Я вважаю, наведені приклади можуть служити основою для подальших досліджень аспектів написання DLL в керованому коді.

Ця стаття також відкриває двері для використання в MetaTrader 5 безлічі бібліотек, вже реалізованих на C #. Для більш глибокого ознайомлення, будь ласка, прочитайте статті в розділі "Література".

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

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. P / Invoke і 64-бітна розробка
  2. Використання P / Invoke: ховаємо кнопку Пуск і панель завдань в Windows
  3. .NET в unmanaged оточенні: platform invoke або що таке LPTSTR
  4. Маршалинга даних при виклику некерованого коду
  5. маршалинга взаємодії
  6. Взаємодія з некерованим кодом
  7. Розділи практичних посібників, що описують взаємодію з некерованим кодом
  8. Exporting .NET DLLs with Visual Studio 2005 to be Consumed by Native Applications
  9. Interoperating with Unmadged Coded
  10. Introduction to COM Interop
  11. Component Object Model (COM)
  12. Exporting from a DLL Using __declspec (dllexport)
  13. How to: Declare Handles in Native Types
  14. How to call C ++ code from Managed, and vice versa (Interop)
  15. Reverse P / Invoke and exception
  16. How to call a managed DLL from native Visual C ++ code in Visual Studio.NET or in Visual Studio 2005
  17. Platform Invoke Tutorial
  18. PInvoke-Reverse PInvoke and __stdcall - __cdecl
  19. Gotchas with Reverse Pinvoke (unmanaged to managed code callbacks)
  20. Mixing .NET and native code
  21. Export Managed Code as Unmanaged
  22. Understanding Classic COM Interoperability With .NET Applications
  23. Managed Extensions for C ++ Programming
  24. Robert Giesecke's site
  25. MSBuild Tasks
  26. Common Language Runtime

Переклад з англійської проведений MetaQuotes Software Corp.
Оригінальна стаття: https://www.mql5.com/en/articles/249

Провайдеры:
  • 08.09.2015

    Batyevka.NET предоставляет услуги доступа к сети Интернет на территории Соломенского района г. Киева.Наша миссия —... 
    Читать полностью

  • 08.09.2015
    IPNET

    Компания IPNET — это крупнейший оператор и технологический лидер на рынке телекоммуникаций Киева. Мы предоставляем... 
    Читать полностью

  • 08.09.2015
    Boryspil.Net

    Интернет-провайдер «Boryspil.net» начал свою работу в 2008 году и на данный момент является одним из крупнейших поставщиков... 
    Читать полностью

  • 08.09.2015
    4OKNET

    Наша компания работает в сфере телекоммуникационных услуг, а именно — предоставлении доступа в сеть интернет.Уже... 
    Читать полностью

  • 08.09.2015
    Телегруп

    ДП «Телегруп-Украина» – IT-компания с 15-летним опытом работы на рынке телекоммуникационных услуг, а также официальный... 
    Читать полностью

  • 08.09.2015
    Софтлинк

    Высокая скоростьМы являемся участником Украинского центра обмена трафиком (UA — IX) с включением 10 Гбит / сек... 
    Читать полностью