Скачать MetaTrader 5

Как за 10 минут написать DLL библиотеку для MQL5 и обмениваться данными?

27 января 2010, 12:28
MetaQuotes
92
9 790

Так уж сложилось, что сейчас мало кто из разработчиков помнит, как написать простую 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. |
//|                                        http://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. Способы передачи параметров и обмен данными

Давайте посмотрим на несколько вариантов передаваемых параметров:

  1. Прием и передача простых переменных
    С простыми переменными все просто - их можно передавать по значению или по ссылке через &.
    _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
  2. Прием и передача массива с заполнением элементов

    Передача массива в 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 
  3. Передача и модификация строк
    Строки (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. |
//|                                        http://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. |
//|                                              http://www.mql5.com |
//+------------------------------------------------------------------+
#property copyright "2010, MetaQuotes Software Corp."
#property link      "http://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("Этого текста не увидите!");
//---
  }
//+------------------------------------------------------------------+

Спасибо за внимание! Буду рад ответить на вопросы.

Прикрепленные файлы |
mql5dllsamples.zip (4.66 KB)
mql5dll_test.mq5 (1.8 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (92)
Alexander Dubovik
Alexander Dubovik | 22 апр 2016 в 14:10

Сейчас занимаюсь привязкой ZeroMQ к MT4 через DLL.

Очень плохо как-то. Терминал крошится сплошь и рядом при запуске индикатора или при выгрузке. Причем это не проявляется в режиме отладки, так что вообще не понятно, что происходит.

EsEr
EsEr | 30 июл 2016 в 23:03

Добрый день.

Почему-то у меня при запуске тестового примера скрипт отрубается при первом же обращении к импортируемой функции

2016.07.30 23:30:14.088    Scripts    script mql5dll_test (EURUSD,H1) removed

Что я делаю не так?

Alexey Volchanskiy
Alexey Volchanskiy | 31 июл 2016 в 01:00
EsEr:

Добрый день.

Почему-то у меня при запуске тестового примера скрипт отрубается при первом же обращении к импортируемой функции

2016.07.30 23:30:14.088    Scripts    script mql5dll_test (EURUSD,H1) removed

Что я делаю не так?

статья 6-летней давности, что-то изменилось, скорее всего

код ошибки какой? 

EsEr
EsEr | 31 июл 2016 в 21:48

Да в том-то и дело, что скрипт просто вылетал, как после этого смотреть код ошибки? Или при следующем запуске его можно сразу считать?

В принципе, проблема решилась компиляцией dll под 64x. Правда, потом уже пошли другие ошибки, но это уже не важно, мне нужно было понять, как вообще собирать и линковать библиотеки на c/с++ к mql5. Свой пример сейчас работает нормально.

xFFFF
xFFFF | 28 янв 2017 в 00:32
Будет ли обновление статьи? Появились ли какие-нибудь изменения в работе с dll?
Инструмент "Ценовая гистограмма" (Рыночный профиль) и его реализация на MQL5 Инструмент "Ценовая гистограмма" (Рыночный профиль) и его реализация на MQL5

Рыночный профиль был разработан Питером Стидлмайером (Peter Steidlmayer), который предложил использовать альтернативное представление информации как о горизонтальном, так и о вертикальном движении рынка, что дает полностью отличный набор моделей. Он предположил, что у рынка существует основной рыночный пульс, или фундаментальная модель, которая называется цикл равновесия и неравновесия (cycle of equilibrium and disequilibrium). В данной статье я сделаю попытку дать общие понятия об упрощенной модели Рыночного профиля (Market Profile) – Ценовой Гистограмме (Price Histogram) и расскажу, как реализовал данный инструмент на MQL5.

Передача данных между индикаторами - простое решение наболевшей проблемы Передача данных между индикаторами - простое решение наболевшей проблемы

Мы хотим создать среду, которая предоставляла бы возможность обращения к показаниям индикаторов, присоединенных к тому или иному графику терминала, и обладала бы следующими свойствами: отсутствие копирования данных; минимальное вмешательство в код уже имеющихся инструментов при необходимости их «подключения»; реализация преимущественно средствами MQL (естественно, механизм DLL нам все же потребуется, однако, как мы увидим, его использование будет ограничиваться не более чем десятком строк на С++). В статье объясняется, как можно довольно просто создать в терминале MetaTrader программную среду, обеспечивающую средства для доступа к буферам индикаторов из других MQL-программ.

Индикатор от индикатора в MQL5 Индикатор от индикатора в MQL5

При написании индикатора, который использует краткую форму вызова функции OnCalculate(), можно упустить то обстоятельство, что индикатор может рассчитываться не только на ценовых данных, но и на данных другого индикатора (встроенного или пользовательского - не имеет значения). Вы хотите улучшить индикатор, чтобы он правильно считался не только на ценовых данных, но и значениях другого индикатора? В этой статье мы по шагам пройдем все необходимые этапы такой модификации и выведем дополнительные полезные правила для правильного написания индикатора.

МetaTrader 5. Экспорт котировок в .NET приложение, используя WCF сервисы МetaTrader 5. Экспорт котировок в .NET приложение, используя WCF сервисы

Вам необходимо организовать трансляцию котировок из MetaTrader 5 в собственное приложение? Связка MQL5-DLL позволяет создавать подобные решения. В статье продемонстрирован один из способов трансляции котировок из MetaTrader 5 в приложения, написанные на .NET. Мне было рациональнее, интереснее и проще реализовать экспорт котировок именно с использованием этой платформы. К сожалению, с выходом "пятерки" поддержки .Net также не появилось, поэтому по старинке будем использовать как прослойку win32 dll с поддержкой .NET.