Articles
To post a new article, please log in or register
Do you like the article?

Share it with others -
post a link to it!

How to Exchange Data: A DLL for MQL5 in 10 Minutes [ ru ]

Renat Fatkhullin | 27 January, 2010

As a matter of fact, there are not many developers who remember exactly how to write a simple DLL library and what are features of binding different systems.

Using several examples, I will try to show the entire process of the simple DLL's creation in 10 minutes, as well as to discuss some technical details of our binding implementation. We will use Visual Studio 2005/2008; its Express versions are free and can be downloaded from the Microsoft website.

1. Creating a DLL project in C++ in Visual Studio 2005/2008

Run the Win32 Application Wizard using the 'File -> New' menu, select the project type as 'Visual C++', choose 'Win32 Console Application' template and define the project name (for example, 'MQL5DLLSamples'). Select a root directory for storing project 'Location', instead of the default offered one, disable the checkbox of 'Create directory for solution' and click 'OK':

Fig. 1. Win32 Application Wizard, DLL project creation

On the next step press 'Next' to go to the settings page:

Fig. 2. Win32 Application Wizard, project settings

On the final page, select the 'DLL' application type, leaving other fields empty as they are, and click 'Finish'. Don't set the 'Export symbols' option, if you don't want to remove the demonstration code added automatically:

Fig. 3. Win32 Application Wizard, Application settings

As a result you will have an empty project:

Fig. 4. The empty DLL project prepared by Wizard

To simplify testing, it's better to specify in 'Output Directory' options the output of DLL files directly to '...\MQL5\Libraries' of the client terminal - further, it will save you much time:

Fig. 5. DLL output directory


2. Preparing to Add Functions

Add '_DLLAPI' macro at end of the stdafx.h file, so that you can conveniently and easily describe exported functions:

//+------------------------------------------------------------------+
//|                                                 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)
//+------------------------------------------------------------------+

The DLL imported functions calls in MQL5 should have the stdcall and cdecl calling convention. Though stdcall and cdecl differ in the ways of parameter extracting from a stack, the MQL5 runtime environment can safely use both versions because of the special wrapper of DLL calls.

The C++ compiler uses  __cdecl calling by default, but I recommend explicitly specifying the __stdcall mode for exported functions.

A correctly written export function must have the following form:

_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  }

In a MQL5 program, the function should be defined and called as follows:

#import "MQL5DLLSamples.dll"
int  fnCalculateSpeed(int &res1,double &res2);
#import

//--- call
   speed=fnCalculateSpeed(res_int,res_double);

After the project compilation, this stdcall will be displayed in the export table as _fnCalculateSpeed@8, where the compiler adds an underscore and number of bytes, transmitted through the stack. Such a decoration allows to better control the security of DLL functions calls due to the fact that the caller knows exactly how many (but not the type of!) data that should be placed in the stack.

If the final size of the parameter block has an error in the DLL function import description, the function won't be called, and the new message will appear in the journal: 'Cannot find 'fnCrashTestParametersStdCall' in 'MQL5DLLSamples.dll'. In such cases it is necessary to carefully check all the parameters both in the function prototype and in the DLL source.

The search for the simplified description without decoration is used for compatibility in case the export table does not contain the full function name. Names like fnCalculateSpeed are created if functions are defined in the __cdecl format.
_DLLAPI int fnCalculateSpeed(int &res1,double &res2)
  {
   return(0);
  }


3. Methods to Pass Parameters and Exchange Data

Let's consider several variants of passed parameters:

  1. Receiving and passing of simple variables
    The case of simple variables is easy - they can be passed by value or by reference using &.
    _DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
      {
       int    res_int=0;
       double res_double=0.0;
       int    start=GetTickCount();
    //--- simple math calculations
       for(int i=0;i<=10000000;i++)
         {
          res_int+=i*i;
          res_int++;
          res_double+=i*i;
          res_double++;
         }
    //--- set calculation results
       res1=res_int;
       res2=res_double;
    //--- return calculation time 
       return(GetTickCount()-start);
      }
        
    Call from MQL5:
    #import "MQL5DLLSamples.dll"
    int  fnCalculateSpeed(int &res1,double &res2);
    #import
    
    //--- calling the function for calculations
       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);
    
    The output is:
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Time  16  msec, int:  -752584127  double:  17247836076609
  2. Receiving and passing of an array with elements filling

    Unlike other MQL5 programs, array passing is performed through the direct reference to the data buffer without access to proprietary information about the dimensions and sizes. That's why the array dimension and size should be passed separately.

    _DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size)
      {
    //--- check for the input parameters
       if(arr==NULL || arr_size<1) return;
    //--- fill array with values
       for(int i=0;i<arr_size;i++) arr[i]=i;
      }
        
    Call from MQL5:
    #import "MQL5DLLSamples.dll"
    void fnFillArray(int &arr[],int arr_size);
    #import
    
    //--- call for the array filling
       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);
    
    The output is:
    MQL5DLL Test (GBPUSD,M1) 20:31:12 Array: 0 1 2 3 4 5 6 7 8 9 
  3. Passing and modification of strings
    The unicode strings are passed using direct references to its buffer addresses without passing of any additional information.
    _DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
      {
       wchar_t *cp;
    //--- parameters check
       if(text==NULL || from==NULL || to==NULL) return;
       if(wcslen(from)!=wcslen(to))             return;
    //--- search for substring
       if((cp=wcsstr(text,from))==NULL)         return;
    //--- replace it
       memcpy(cp,to,wcslen(to)*sizeof(wchar_t));
      }
    
    Call from MQL5:
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string text,string from,string to);
    #import
    
    //--- modify the string
       string text="A quick brown fox jumps over the lazy dog"; 
       
       fnReplaceString(text,"fox","cat");
       Print("Replace: ",text);
    The result is:
    MQL5DLL Test (GBPUSD,M1) 19:56:42 Replace:  A quick brown fox jumps over the lazy dog
    It's turned out that the line hadn't changed! This is a common mistake of newbies when they transmit copies of objects (a string is an object), instead of referring to them. The copy of the string 'text' has been automatically created that has been modified in the DLL, and then it has been removed automatically without affecting the original.

    To remedy this situation, it's necessary to pass a string by reference. To do it, simply modify the block of importing by adding & to the "text" parameter:
    #import "MQL5DLLSamples.dll"
    void fnReplaceString(string &text,string from,string to);
    #import
    After compilation and start we will get the right result:
    MQL5DLL Test (GBPUSD,M1) 19:58:31 Replace:  A quick brown cat jumps over the lazy dog

4. Catching of exceptions in DLL functions

To prevent the terminal crushes, each DLL call is protected automatically by Unhandled Exception Wrapping. This mechanism allows protecting from the most of standard errors (memory access errors, division by zero, etc.)

To see how the mechanism works, let's create the following code:

_DLLAPI void __stdcall fnCrashTest(int *arr)
  {
//--- wait for receipt of a zero reference to call the exception
   *arr=0;
  }

and call it from the client terminal:

#import "MQL5DLLSamples.dll"
void fnCrashTest(int arr);
#import

//--- call for the crash (the execution environment will catch the exception and prevent the client terminal crush)
   fnCrashTest(NULL);
   Print("You won't see this text!");
//---

As s result, it will try to write to the zero address and generate an exception. The client terminal will catch it, log it to the journal and continue its work:

MQL5DLL Test (GBPUSD,M1) 20:31:12 Access violation write to 0x00000000

5. DLL calls wrapper and loss of speed on calls

As already described above, every call of DLL functions is wrapped into a special wrapper in order to ensure safety. This binding masks the basic code, replaces the stack, supports stdcall / cdecl agreements and monitors exceptions within the functions called.

This volume of works doesn't lead to a significant delay of function calling.

6. The final build

Let's collect all the above DLL functions examples in the 'MQL5DLLSamples.cpp' file, and MQL5 examples in the script 'MQL5DLL Test.mq5'. The final project for Visual Studio 2008 and the script in MQL5 are attached to the article.

//+------------------------------------------------------------------+
//|                                                 MQL5 DLL Samples |
//|                   Copyright 2001-2010, MetaQuotes Software Corp. |
//|                                        http://www.metaquotes.net |
//+------------------------------------------------------------------+
#include "stdafx.h"

//+------------------------------------------------------------------+
//| Passing and receving of simple variables                         |
//+------------------------------------------------------------------+
_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2)
  {
   int    res_int=0;
   double res_double=0.0;
   int    start=GetTickCount();
//--- simple math calculations
   for(int i=0;i<=10000000;i++)
     {
      res_int+=i*i;
      res_int++;
      res_double+=i*i;
      res_double++;
     }
//--- set calculation results
   res1=res_int;
   res2=res_double;
//--- return calculation time
   return(GetTickCount()-start);
  }
//+------------------------------------------------------------------+
//| Filling the array with values                                    |
//+------------------------------------------------------------------+
_DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size)
  {
//--- check input variables
   if(arr==NULL || arr_size<1) return;
//--- fill array with values
   for(int i=0;i<arr_size;i++) arr[i]=i;
  }
//+------------------------------------------------------------------+
//| The substring replacement of the text string                     |
//| the string is passed as direct reference to the string content   |
//+------------------------------------------------------------------+
_DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to)
  {
   wchar_t *cp;
//--- parameters checking
   if(text==NULL || from==NULL || to==NULL) return;
   if(wcslen(from)!=wcslen(to))             return;
//--- search for substring
   if((cp=wcsstr(text,from))==NULL)         return;
//--- replace it 
   memcpy(cp,to,wcslen(to)*sizeof(wchar_t));
  }
//+------------------------------------------------------------------+
//| Call for the crush                                               |
//+------------------------------------------------------------------+
_DLLAPI void __stdcall fnCrashTest(int *arr)
  {
//--- wait for receipt of a zero reference to call the exception
   *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()
  {
//--- calling the function for calculations
   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);
//--- call for the array filling
   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);
//--- modifying the string
   string text="A quick brown fox jumps over the lazy dog"; 
   
   fnReplaceString(text,"fox","cat");
   Print("Replace: ",text);
//--- and finally call a crash
//--- (the execution environment will catch the exception and prevent the client terminal crush)
   fnCrashTest(NULL);
   Print("You won't see this text!");
//---
  }
//+------------------------------------------------------------------+

Thank you for your interest! I am ready to answer any questions.

Attached files
mql5dll_test.mq5 (1.83 KB)
mql5dllsamples.zip (4.62 KB)
Last comments | Go to discussion (7)
Roman Trchalik
rotr | 26 Jul 2010 at 13:22

Can You show me importing structure to the C++ library?

I tray to add this code to MQL5DLLSample.cpp:
------------------------
struct MqlTick

{
    INT64 Time;
    double Bid;
    double Ask;
    double Last;
    UINT64 Volume;
};

_DLLAPI MqlTick __stdcall MyTick(MqlTick &my)
{
    my.Bid = 1;
    return(my);
}
------------------------

Then, I add this code to MQL5DLL Test.mq5 (import section)

   MqlTick MyTick(MqlTick &tick);

and calling it in OnTick()

   MqlTick tick;
   SymbolInfoTick("GBPUSD", tick);
   MyTick(tick);
   Print("My tick: ",tick.bid);
------------------------

Compilation of the c++ library and EA is without errors.

After calling MyTick(tick) functio in terminal I obtain error: MQL5DLL_Test (EURUSD,M1)    Access violation write to 0x00000008



Alexander
GarF1eld | 30 Apr 2010 at 19:40
ToolMaker:

What is the difference between a C++ dll and a C# dll.

The main difference is design. c# dll assembly is used for storing managed code, and c++ dll contains native code.

But there is a trick that makes assembly importing possible: Inverse P/Invoke

As for me, i'm used to write c++\cli wrapper dll for that purpose or to write all the managed code using c++\cli.

ToolMaker
ToolMaker | 28 Apr 2010 at 22:24

I've been trying to figure out with this dll import functionality, if I will be able to import dlls written in other languages like C#. Is this possible? if not why not?

What is the difference between a C++ dll and a C# dll.


Larry
LEHayes | 28 Apr 2010 at 11:34
pfx:

Good article, but it raises some big concerns.

I am all for adding protection for the application but not at the expense of performance. This is another change from MT4 as exceptions in dlls cause MT4 to crash, but well written code should handle this scenario, I would much rather see articles on writing safe code in dll's to overcome these sorts of problems.  So now due to bad coding habits everyone has to pay a performance cost. 

This is very very bad if you have libraries of algorithms that are called on a tick bases, I would dare say render them useless.   Given alot of the feedback I have seen re MT5 isn't good mostly due to people needing to rewrite indicators etc. this is just another item that causes alot of frustration, and seeing as it does impact performance I would imagine others like myself that user extensive use of DLL's will just stay with MT4 for as long as posible and look for another platform where this isn't a problem.

 Why oh why could Metaquotes just not add a slightly different method for invoking dll's in a safe way. The developer could then choose to load via safe loading or performance loading and ensure they write good code that catches exceptions.

"It's better to make infrequent calls"  I mean seriously what sort of statement is that

 

It's no wonder that this community has difficulty getting decent articles.  Any good writer would not bother spending the time to write here.  Why should they?  They do something decent like show how to incorporate dlls and establish communication, then someone comes along and knocks them right out of the tree. 

Look from what I can see, he did a decent job on the article, so maybe the performance isn't where it should be.  BUT the question or issue here is, if it is so bad and you are a part of this community, why are you not attempting to code a solution to what you have found as an obvious performance issue? 

 It's all well and good that you bring it up, but it does no one anygood to complain about it, if they are not willing to present solutions or even suggestions on how to achieve the goal.

Did the article expose risk, yes, I think we even got a sample of it when another person says, hey I have downloaded it and I get this problem with it.  Which is exactly the problem that was discussed in the article.  I think if I were to expand upon this article, it would be like you said there PFX, show how to safely interact.  However, he did expose the risk, leaving plenty of room for someone like yourself to expand on it by the very concept of the topic of SAFETY. 

 One personal note to the author from me, when writting topics try to refrain from terms like newbie.  It is insulting and degrading, not a professional way to speak of those you wish to have follow your wisdom. 

Ilyas Sharapov
mql5 | 27 Jan 2010 at 09:39
investeo:

I compiled the dll using visual c++ express 2008 but interestingly terminal doesn't catch exception, but crashes inside fnReplaceString.

My terminal build is 239. Any clues?

Can you send me ex5 and dll?
The Price Histogram (Market Profile) and its implementation in MQL5

The Market Profile was developed by trully brilliant thinker Peter Steidlmayer. He suggested to use the alternative representation of information about "horizontal" and "vertical" market movements that leads to completely different set of models. He assumed that there is an underlying pulse of the market or a fundamental pattern called the cycle of equilibrium and disequilibrium. In this article I will consider Price Histogram - a simplified model of Market Profile, and will describe its implementation in MQL5.

Data Exchange between Indicators: It's Easy

We want to create such an environment, which would provide access to data of indicators attached to a chart, and would have the following properties: absence of data copying; minimal modification of the code of available methods, if we need to use them; MQL code is preferable (of course, we have to use DLL, but we will use just a dozen of strings of C++ code). The article describes an easy method to develop a program environment for the MetaTrader terminal, that would provide means for accessing indicator buffers from other MQL programs.