
Как за 10 минут написать DLL библиотеку для MQL5 и обмениваться данными?
Так уж сложилось, что сейчас мало кто из разработчиков помнит, как написать простую DLL библиотеку и в чем особенности связывания разнородных систем.
Я постараюсь за 10 минут на примерах показать весь процесс создания простых DLL библиотек и раскрою некоторые технические детали нашей реализации связывания. Демонстрация будет на примере Visual Studio 2005 / 2008, бесплатные Express-версии которых можно свободно скачать с сайта Microsoft.
1. Создание проекта DLL на С++ в Visual Studio 2005/2008
Запустите визард через меню 'File -> New', выберите тип проекта 'Visual C++', шаблон 'Win32 Console Application' и укажите имя проекта (например, 'MQL5DLLSamples'). Выберите отдельный корневой каталог хранения проектов 'Location' вместо предлагаемого по умолчанию, отключите галочку 'Create directory for solution' и нажмите на кнопку 'OK':
Рис 1. Win32 Application Wizard, создание проекта DLL
На следующем шаге просто нажмите на кнопку 'Next' для перехода на страницу настроек:
Рис 2. Win32 Application Wizard, параметры проекта
На финальной странице выберите тип 'DLL', оставив остальные поля пустыми как есть, и нажмите на 'Finish'. Ставить галочку на 'Export symbols' не нужно, чтобы потом не удалять автоматически добавленный демонстрационный код:
Рис 3. Win32 Application Wizard, настройка свойств приложения
В результате получите пустой проект:
Рис 4. Пустой проект DLL
Для удобства тестирования лучше всего прямо в настройках 'Output Directory' указать выкладку DLL файлов напрямую в каталог '...\MQL5\Libraries'
клиентского терминала. Это сэкономит много времени в последующей работе:
Рис 5. Каталог выкладки DLL файлов
2. Подготовка к добавлению функций
Добавьте макрос '_DLLAPI' в конец файла stdafx.h, чтобы можно было удобно и просто описывать экспортируемые функции:
//+------------------------------------------------------------------+ //| MQL5 DLL Samples | //| Copyright 2001-2010, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #pragma once #include "targetver.h" #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include <windows.h> //--- #define _DLLAPI extern "C" __declspec(dllexport) //+------------------------------------------------------------------+
В вызовах функций MQL5 используется соглашение о связях __stdcall и __cdecl. Хотя вызовы stdcall и cdecl отличаются вариантами извлечения параметров со стека, но исполняющая среда MQL5 позволяет безболезненно использовать оба варианта за счет специального враппера DLL вызовов.
По умолчанию в настройках компилятора С++ для функций используется __cdecl, но я рекомендую для экспортируемых функций явным образом указывать режим __stdcall.
Правильно оформленная экспортная функции должна иметь следующий вид:
_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2) { return(0); }
а в MQL5 программе описываться и вызываться так:
#import "MQL5DLLSamples.dll" int fnCalculateSpeed(int &res1,double &res2); #import //--- вызов speed=fnCalculateSpeed(res_int,res_double);
После сборки проекта DLL эта stdcall функция будет видна в таблице экспорта под именем _fnCalculateSpeed@8, где компилятором добавляются знак подчеркивания и количество передаваемых через стек данных в байтах. Такое декорирование позволяет лучше контролировать безопасность вызовов DLL функций за счет того, что вызывающая сторона точно знает, сколько (но не каких!) данных нужно помещать в стек.
Если при описании импорта DLL функции будет ошибка в итоговом размере блока параметров, то функция не будет вызвана, а в журнале появится сообщение вида 'Cannot find 'fnCrashTestParametersStdCall' in 'MQL5DLLSamples.dll''. В этом случае надо тщательно перепроверить все параметры как в протопите функции, так и в самой DLL.
При отсутствии полного имени функции в таблице экспорта для совместимости используется поиск упрощенного описания без декорирования. Такие имена вида fnCalculateSpeed создаются при описаниях функции в формате __cdecl:
_DLLAPI int fnCalculateSpeed(int &res1,double &res2) { return(0); }
3. Способы передачи параметров и обмен данными
Давайте посмотрим на несколько вариантов передаваемых параметров:
- Прием и передача простых переменных
С простыми переменными все просто - их можно передавать по значению или по ссылке через &._DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2) { int res_int=0; double res_double=0.0; int start=GetTickCount(); //--- быстро посчитаем простую математику for(int i=0;i<=10000000;i++) { res_int+=i*i; res_int++; res_double+=i*i; res_double++; } //--- вернем результаты назад res1=res_int; res2=res_double; //--- вернем занятое время return(GetTickCount()-start); }
Вызов из MQL5:#import "MQL5DLLSamples.dll" int fnCalculateSpeed(int &res1,double &res2); #import //--- вызовем просчет int speed=0; int res_int=0; double res_double=0.0; speed=fnCalculateSpeed(res_int,res_double); Print("Time ",speed," msec, int: ",res_int," double: ",res_double);
Результат:MQL5DLL Test (GBPUSD,M1) 19:56:42 Time 16 msec, int: -752584127 double: 17247836076609
- Прием и передача массива с заполнением элементов
Передача массива в DLL, в отличие от других MQL5-программ, происходит через прямую ссылку на буфер с данными без доступа к служебной информации о размерностях и размерах. Поэтому размерности и размеры массива нужно передавать отдельно.
_DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size) { //--- проверим входящие параметры if(arr==NULL || arr_size<1) return; //--- заполним значениями for(int i=0;i<arr_size;i++) arr[i]=i; }
Вызов из MQL5:#import "MQL5DLLSamples.dll" void fnFillArray(int &arr[],int arr_size); #import //--- вызовем заполнением массива int arr[]; string result="Array: "; ArrayResize(arr,10); fnFillArray(arr,ArraySize(arr)); for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" "; Print(result);
Результат:MQL5DLL Test (GBPUSD,M1) 20:31:12 Array: 0 1 2 3 4 5 6 7 8 9
- Передача и модификация строк
Строки (unicode) также передаются через прямые ссылки на рабочие буферы без служебной информации. Обратите внимание, что функция для примера описана в формате cdecl:_DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to) { wchar_t *cp; //--- проверка параметров if(text==NULL || from==NULL || to==NULL) return; if(wcslen(from)!=wcslen(to)) return; //--- поищем подстроку if((cp=wcsstr(text,from))==NULL) return; //--- заменим memcpy(cp,to,wcslen(to)*sizeof(wchar_t)); }
Вызов из MQL5:#import "MQL5DLLSamples.dll" void fnReplaceString(string text,string from,string to); #import //--- модифицируем строку string text="A quick brown fox jumps over the lazy dog"; fnReplaceString(text,"fox","cat"); Print("Replace: ",text);
Результат:MQL5DLL Test (GBPUSD,M1) 19:56:42 Replace: A quick brown fox jumps over the lazy dog
Оказалось, что строка не изменилась! Это обычная ошибка начинающих программистов, когда они передают копии объектов (а string - это объект) вместо ссылки на них. Для строки 'text' была автоматически создана копия, которая была модифицирована в DLL, а затем копия также автоматически удалилась, не затронув оригинал.
Чтобы исправить ситуацию, надо передавать строку по ссылке. Для этого просто модифицируем блок импорта, добавив знак & к параметру text:#import "MQL5DLLSamples.dll" void fnReplaceString(string &text,string from,string to); #import
После перекомпиляции и запуска получим правильный результат:MQL5DLL Test (GBPUSD,M1) 19:58:31 Replace: A quick brown cat jumps over the lazy dog
4. Перехват исключений в DLL функциях
Чтобы избежать падения самого терминала, каждый вызов функций DLL автоматически защищается оберткой Unhandled Exception. Этот механизм позволяет уберечься от большинства стандартных ошибок (обращения в недоступную память, деления на ноль и т.д.)
Для проверки работоспособности этого механизма создадим следующий код:
_DLLAPI void __stdcall fnCrashTest(int *arr) { //--- ожидаем получение нулевой ссылки, чтобы вызвать исключение *arr=0; }
и вызовем его из терминала:
#import "MQL5DLLSamples.dll" void fnCrashTest(int arr); #import //--- вызовем креш (среда исполнения перехватит исключение и не даст упасть терминалу) fnCrashTest(NULL); Print("Этого текста не увидите!"); //---
В результате произойдет попытка записи в нулевой адрес с генерацией исключения. Терминал его перехватит, сообщит в журнале и продолжит работу:
MQL5DLL Test (GBPUSD,M1) 20:31:12 Access violation write to 0x00000000
5. Враппер DLL вызовов и потери скорости на вызовах
Как уже было рассказано выше, для обеспечения безопасности каждый вызов DLL функции оборачивается в специальный враппер. Эта обвязка маскирует основной код, подменяет стек, поддерживает stdcall/cdecl соглашения и контролирует исключения внутри вызываемых функций.
Такой объем выполняемой работы не приводит к существенному замедлению вызова функций.
6. Финальная сборка
Соберите все вышеприведенные примеры DLL функций в файле 'MQL5DLLSamples.cpp', а MQL5 примеры в скрипт 'MQL5DLL Test.mq5'. Готовый проект для Visual Studio 2008 и скрипт на MQL5 приложены к статье.
//+------------------------------------------------------------------+ //| MQL5 DLL Samples | //| Copyright 2001-2010, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #include "stdafx.h" //+------------------------------------------------------------------+ //| Передача и прием простых переменных | //+------------------------------------------------------------------+ _DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2) { int res_int=0; double res_double=0.0; int start=GetTickCount(); //--- быстро посчитаем простую математику for(int i=0;i<=10000000;i++) { res_int+=i*i; res_int++; res_double+=i*i; res_double++; } //--- вернем результаты назад res1=res_int; res2=res_double; //--- вернем занятое время return(GetTickCount()-start); } //+------------------------------------------------------------------+ //| Заполнение массива значениями | //+------------------------------------------------------------------+ _DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size) { //--- проверим входящие параметры if(arr==NULL || arr_size<1) return; //--- заполним значениями for(int i=0;i<arr_size;i++) arr[i]=i; } //+------------------------------------------------------------------+ //| В текстовой строке заменяем подстроку на подстроку | //| string передается в виде прямой ссылки на контент строки | //+------------------------------------------------------------------+ _DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to) { wchar_t *cp; //--- проверка параметров if(text==NULL || from==NULL || to==NULL) return; if(wcslen(from)!=wcslen(to)) return; //--- поищем подстроку if((cp=wcsstr(text,from))==NULL) return; //--- заменим memcpy(cp,to,wcslen(to)*sizeof(wchar_t)); } //+------------------------------------------------------------------+ //| Устроим падение | //+------------------------------------------------------------------+ _DLLAPI void __stdcall fnCrashTest(int *arr) { //--- ожидаем получение нулевой ссылки, чтобы вызвать исключение *arr=0; } //+------------------------------------------------------------------+
//+------------------------------------------------------------------+ //| MQL5DLL Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- #import "MQL5DLLSamples.dll" int fnCalculateSpeed(int &res1,double &res2); void fnFillArray(int &arr[],int arr_size); void fnReplaceString(string text,string from,string to); void fnCrashTest(int arr); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- вызовем просчет int speed=0; int res_int=0; double res_double=0.0; speed=fnCalculateSpeed(res_int,res_double); Print("Time ",speed," msec, int: ",res_int," double: ",res_double); //--- вызовем заполнением массива int arr[]; string result="Array: "; ArrayResize(arr,10); fnFillArray(arr,ArraySize(arr)); for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" "; Print(result); //--- модифицируем строку string text="A quick brown fox jumps over the lazy dog"; fnReplaceString(text,"fox","cat"); Print("Replace: ",text); //--- а вконце вызовем креш (среда исполнения перехватит исключение и не даст упасть терминалу) fnCrashTest(NULL); Print("Этого текста не увидите!"); //--- } //+------------------------------------------------------------------+
Спасибо за внимание! Буду рад ответить на вопросы.





- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
Так всё таки покажите как это работает?
#include <ctime>
А __time64_t - это тип данных, то же самое, что и datetime.А __time64_t - это тип данных, то же самое, что и datetime.
Значит нужно подключить #include <ctime> и перед каждой переменной писать __time64_t ?
В общем ребята работает это так
Print() - это функция, доступная только в mql4 и mql5. Заменить напрямую невозможно, потому что устройство вывода для нее - файл журнала терминала. Но ее можно вызвать опосредованно, если направить эксперту(индикатору, скрипту), к которому подключена dll, соответствующую команду. Зависит от того, как устроен обмен данными между dll и экспертом.
Также, если есть задача что-то где-то напечатать, не обязательно в журнале терминала, то для этого существует огромное количество возможностей: файл, принтер, дисплей в конце концов.
Ой, не прав ты!
Ой, не прав ты!
Я пробовал в консоль распечатывать материал со стороны с++ не работает в момент выполнения программы консоль не открывалась, возможно не всё так просто как кажется, а что касается того чтобы распечатать в файл я так понял нужно со стороны писать функцию которая создаст файл и будет туда заносить данные, этот вариант не пробовал, знаний мало пока ещё с файлами не работал.
Если кто делал такое отпишитесь, возможно ли со стороны dll распечатать информацию в файл для поиска неверных значений или нет.
Как работать с цветом и строками. Я читал что со строками используют wchar_t, но если внутри функции записать такое выражение