English 中文 Español Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Как за 10 минут написать DLL библиотеку для MQL5 и обмениваться данными?

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

MetaTrader 5Примеры | 27 января 2010, 12:28
19 060 115
MetaQuotes
Renat Fatkhullin

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

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

  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. |
//|                                        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("Этого текста не увидите!");
//---
  }
//+------------------------------------------------------------------+

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

Прикрепленные файлы |
mql5dllsamples.zip (4.66 KB)
mql5dll_test.mq5 (1.8 KB)
Последние комментарии | Перейти к обсуждению на форуме трейдеров (115)
Ihor Herasko
Ihor Herasko | 7 февр. 2020 в 15:08
Seric29:


Так всё таки покажите как это работает?

#include <ctime>
А __time64_t - это тип данных, то же самое, что и datetime.
Seric29
Seric29 | 7 февр. 2020 в 18:10
Ihor Herasko:
А __time64_t - это тип данных, то же самое, что и datetime.

Значит нужно подключить #include <ctime> и перед каждой переменной писать __time64_t ?

Seric29
Seric29 | 7 февр. 2020 в 20:42

В общем ребята работает это так 

#include <iostream>
#include <ctime>

#define _DLLAPI extern "C" __declspec(dllexport)
_DLLAPI __time64_t __stdcall Data_t(){return 3600;}
//результат вычисления 1970.01.01 00:00:00 + количество секунд(return 3600)
//получается 1970.01.01 01:00:00
Алексей Тарабанов
Алексей Тарабанов | 7 февр. 2020 в 21:13
Ihor Herasko:

Print() - это функция, доступная только в mql4 и mql5. Заменить напрямую невозможно, потому что устройство вывода для нее - файл журнала терминала. Но ее можно вызвать опосредованно, если направить эксперту(индикатору, скрипту), к которому подключена dll, соответствующую команду. Зависит от того, как устроен обмен данными между dll и экспертом.

Также, если есть задача что-то где-то напечатать, не обязательно в журнале терминала, то для этого существует огромное количество возможностей: файл, принтер, дисплей в конце концов.

Ой, не прав ты! 

Seric29
Seric29 | 8 февр. 2020 в 16:44
Алексей Тарабанов:

Ой, не прав ты! 

Я пробовал в консоль распечатывать материал со стороны с++ не работает в момент выполнения программы консоль не открывалась, возможно не всё так просто как кажется, а что касается того чтобы распечатать в файл я так понял нужно со стороны писать функцию которая создаст файл и будет туда заносить данные, этот вариант не пробовал, знаний мало пока ещё с файлами не работал.

Если кто делал такое отпишитесь, возможно ли со стороны dll распечатать информацию в файл для поиска неверных значений или нет.

Как работать с цветом и строками. Я читал что со строками используют wchar_t, но если внутри функции записать такое выражение

_DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
  {
   wchar_t Text1="Text1";//не работает значит для строк wchar_t 
//не подходит

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