Download MetaTrader 5

Guide to writing a DLL for MQL5 in Delphi

25 June 2010, 13:49
Andrey Voytenko
6 503


The mechanism of writing a DLL will be considered using an example of the development environment of Delphi 2009. This version was selected due to the fact that in MQL5, all lines are stored in Unicode format. In older versions of Delphi, the SysUtils module is missing the function for working with lines in Unicode format.

If you, for whatever reason, are using an earlier version (Delphi 2007 and older), then you have to work with lines in ANSI format, and in order to exchange data with MetaTrader 5, you need to produce direct and reverse conversions to Unicode. To avoid such complications I recommend developing the DLL module for MQL5 in an environment no older than Delphi 2009. A familiarizing 30-day trial version for Delphi can be downloaded from the official website

1. Creating the Project

To create the project we need to run DLL Wizard by choosing the menu item: 'File -> New -> Other ... -> DLL Wizard ' As shown at Figure 1.

Figure 1. Creating a project using DLL Wizard

As a result, we will create an empty DLL project, as shown at Figure 2.

Figure 2. An empty DLL project

The essence of a long commentary in the title of the project is to remind you of a correct connection and the use of a memory manager when working with dynamically allocated memory. This will be discussed in more detail in the section dealing with strings.

Before you begin filling the new DLL with functions, it is important to configure the project.

Open the project properties window from the menu: 'Project -> Options ...' or via the keyboard 'Shift + Ctrl + F11' .

In order to simplify the process of debugging, it is necessary that the DLL file is created directly in the folder '.. \\MQL5\\Libraries' Trade Terminal MetaTrtader5. To do this, on the DelphiCompiler tab set the corresponding property value Output directory , as shown at Figure 3. It will eliminates the need to constantly copy the file, generated by DLL, from the project folder into the folder of the terminal.

 Figure 3. Specify the folder for storing the resulting DLL file

In order to avoid the hooking up of BPL modules during the assembly, without the presence of which in the Windows system folder, the created DLL will not be work in the future, it is important to check that on the tab Packages , the flag Build with runtime packages is unchecked, as shown at Figure 4.

  Figure 4. Exclusion of the BPL modules from the assembly

After completing project configuration, save it to your working folder, the specified name of the project is the future name of the compiled DLL file.

2. Adding the procedures and functions 

Lets consider the general situation when writing the exported procedures and functions in the DLL module, on an example of a procedure without parameters. Announcement and transfer of parameters will be discussed in the next section.

A small digression. When writing the procedures and functions in the Object Pascal language, the programmer has the opportunity to use the built-in library Delphi functions, not to mention the countless components, developed for this environment. For example, for the performance of the same action, like bringing up a display of a modal window with a text message, you can use as an API function - MessageBox as well as a procedure from the VCL library - ShowMessage.

The second option leads to including the Dialogs module and gives the advantage of easy work with standard Windows dialogs. However, the size of the resulting DLL file will increase by approximately 500 KB. Therefore, if you prefer to create small DLL files, which do not take up a lot of disk space, I would not advise you to use the VCL components.

A sample test project with explanations is below:

library dll_mql5;

  Windows, // necessary for the work of the MessageBox function
  Dialogs; // necessary for the work of the ShowMessage procedure from the Dialogs module

var   Buffer: PWideChar;
procedure MsgBox(); stdcall; // 
//to avoid errors, use the stdcall (or cdecl) for the exported functions
    {1} MessageBox(0,'Hello World!','terminal', MB_OK);
    {2} ShowMessage('Hello World!');// alternative to the MessageBox function 

  {A} MsgBox,
  {B} MsgBox name 'MessageBox';// renaming of the exported function

procedure DLLEntryPoint(dwReason: DWord); // event handler
    case dwReason of

      DLL_PROCESS_ATTACH: // DLL attached to the process;
          // allocate memory

      DLL_PROCESS_DETACH: // DLL detached from the process;
          // release memory


    DllProc := @DLLEntryPoint; //Assign event handler

All exported functions must be announced with the modifier stdcall or cdecl. If none of these modifiers is specified, Delphi uses the fastcall agreement by default, which primarily uses CPU registers for passing  parameters, rather than stack. It will undoubtedly lead to an error in working with the passed parameters, at the stage of calling up the external functions DLL.

The section "begin end" contains a standard initialization code of a DLL event handler. The DLLEntryPoint callback- procedure will be called when connecting and disconnecting from the process that called it. These events can be used for the correct dynamic memory management, allocated for our own needs, as shown in the example.

Call for MQL5:

#import "dll_mql5.dll"
    void MsgBox(void);
    void MessageBox(void);

// Call of procedure
// If the names of the function coincide with the names of MQL5 standard library function
// use the DLL name when calling the function

3. Passing Parameters to the Function and Returned Values

Before considering the passing of parameters, let's analyze the data correspondency table for MQL5 and Object Pascal.

Data type for MQL5
Data type for Object Pascal (Delphi)
char ShortInt

uint Cardinal
long Int64

float Single
double Double

ushort (символ) WideChar
string PWideChar  
bool Boolean  
datetime TDateTime conversion is required (see below in this section)
color TColor  

Table 1. The data corresondency table for MQL5 and Object Pascal

As you can see from the table, for all data types other than datetime, Delphi has a complete analogue.

Now consider two ways of parameters passing: by value and by reference. The format of parameters declaration for both versions is given in Table 2.

Method of transferring parameters
Announcement for MQL5
Announcement for Delphi
by value
int func (int a); func (a:Integer): Integer;  correct

int func (int a);
func (var a: Integer): Integer;
 Error: access violation write to <memory address>
by link
int func (int &a);
func (var a: Integer): Integer;
 correct, however lines are transmitted without a modifier var!
  int func (int &a);  func (a: Integer): Integer;  error: instead of the value of the variable, contains the address of the memory cell

 Table 2. Methods of parameters passing

Now let us consider the examples of working with passed parameters and returned values.

3.1 Converting date and time

First, let's deal with the type of date and time that you want to convert, because the datetime type corresponds to TDateTime only in its size but not in format. For ease of transformation, use Int64 as the received data type, instead of TDateTime. Below are the functions for direct and inverse transformation:

    SysUtils,  // used for the constant UnixDateDelta 
    DateUtils; // used for the function IncSecon, DateTimeToUnix

Function MQL5_Time_To_TDateTime(dt: Int64): TDateTime;
      Result:= IncSecond(UnixDateDelta, dt);

Function TDateTime_To_MQL5_Time(dt: TDateTime):Int64;
      Result:= DateTimeToUnix(dt);

3.2 Working with simple data types

Let's examine how to transfer simple data types, on the example of the most commonly used ones, int, double, bool, and datetime.

Call for Object Pascal:

function SetParam(var i: Integer; d: Double; const b: Boolean; var dt: Int64): PWideChar; stdcall;
  if (b) then d:=0;                   // the value of the variable d is not changed in the calling program
  i:= 10;                             // assign a new value for i
  dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt
  Result:= 'value of variables i and dt are changed';

Call for MQL5:

#import "dll_mql5.dll"
    string SetParam(int &i, double d, bool b, datetime &dt);

// initialization of variables
   int i = 5;
   double d = 2.8;
   bool b = true;
   datetime dt= D'05.05.2010 08:31:27';
// calling the function
// output of results
   printf("%s i=%s d=%s b=%s dt=%s",s,IntegerToString(i),DoubleToString(d),b?"true":"false",TimeToString(dt));
The values of variables i and dt are changed i =  10  d =  2.80000000  b = true dt =  2009.05 .  05  08 :  42 

The value of d has not changed since it was transferred by value. To prevent the occurrence of changes in the value of a variable, inside a DLL function, a modifier const. was used on the variable b.

3.3 Working with structures and arrays

On several occasions, it is useful to group the parameters of different types into structures, and parameters of one type into arrays. Consider working with all of the transferred parameter of the function SetParam, from the previous example, integrating them into a structure.

Call for Object Pascal: 

StructData = packed record
    i: Integer;
    d: Double;
    b: Boolean;
    dt: Int64;

function SetStruct(var data: StructData): PWideChar; stdcall;
  if (data.b) then data.d:=0;
  data.i:= 10;                                 // assign a new value for i
  data.dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt
  Result:= 'The values of variables i, d and dt are changed';

Call for MQL5:  

   int i;
   double d;
   bool b;
   datetime dt;

#import "dll_mql5.dll"
    string SetStruct(STRUCT_DATA &data);

   STRUCT_DATA data;
   data.i = 5;
   data.d = 2.8;
   data.b = true;
   data.dt = D'05.05.2010 08:31:27';
   s = SetStruct(data);
   printf("%s i=%s d=%s b=%s dt=%s", s, IntegerToString(data.i),DoubleToString(data.d), 
The values of variables i,  d  and dt are changed i =  10  d =  0.00000000  b = true dt =  2009.05 .  05  12 :  19 

It's necessary to note one significant difference from the result of the previous example. Since the structure is transferred through a reference, it makes it impossible to protect the selected fields from being edited in the called function. The task of monitoring the integrity of the data, in this case, lies fully upon the programmer.

Consider working with arrays, on an example of filling the array with the sequence of Fibonacci numbers:

Call for Object Pascal:

function SetArray(var arr: IntegerArray; const len: Cardinal): PWideChar; stdcall;
var i:Integer;
  Result:='Fibonacci numbers:';
  if (len < 3) then exit;
  arr[0]:= 0;
  arr[1]:= 1;
  for i := 2 to len-1 do
    arr[i]:= arr[i-1] + arr[i-2];

Call for MQL5: 

#import "dll_mql5.dll"
    string SetArray(int &arr[],int len);
   int arr[12];
   int len = ArraySize(arr);
// passing the array by reference to be filled by data in DLL
   s = SetArray(arr,len);
//output of result
   for(int i=0; i<len; i++) s = s + " " + IntegerToString(arr[i]);
Fibonacci numbers 0 1 1 2 3 5 8 13 21 34 55 89

3.4 Working with strings

Let's return to the memory management. In DLL it is possible to operate your own memory manager. However, because DLL and the program which calls it, are often written in different programming languages, and their own memory managers, rather than general system memory, are used in work, the entire burden of responsibility for the correctness of the operation of memory at the junction DLL and the application, rests on the programmer.

To work with the memory, it is important to comply with the golden rule, which sounds something like: "Those who allocated memory, must be the ones to free it." I.e. you should not try to release the memory in the code mql5- program, allocated in DLL, and vice versa.

Lets consider an example of memory management in a style of Windows API function calls. In our case the mql5-program allocates memory for the buffer, a pointer to the buffer passed to DLL as PWideChar and DLL only fills in this buffer with the desired value, as shown in the following example:

Call for Object Pascal:

procedure SetString(const str:PWideChar) stdcall;
  StrCat(str,'Current time:');
  strCat(str, PWideChar(TimeToStr(Now)));

Call for MQL5: 

#import "dll_mql5.dll"
    void SetString(string &a);

// the string must be initialized before the use
// the size of the buffer must be initially larger or equal to the string length
//passing the buffer reference to DLL 
// output of result 


Current Time: 11: 48:51 

Memory for the line buffer can be selected in DLL in several ways, as seen from the following example:

Call for Object Pascal: 

function GetStringBuffer():PWideChar; stdcall;
var StrLocal: WideString;
     // working through the dynamically allocated memory buffer
     StrPCopy(Buffer, WideFormat('Current date and time: %s', [DateTimeToStr(Now)]));
     // working through the global varialble of WideString type
     StrGlobal:=WideFormat('Current time: %s', [TimeToStr(Time)]);
     // working through the local varialble of WideString type
     StrLocal:= WideFormat('Current data: %s', [DateToStr(Date)]);

{A}  Result := Buffer;

{B}  Result := PWideChar(StrGlobal);
     // it's equal to the following
     Result := @StrGlobal[1];

{С}  Result := 'Return of the line stored in the code section';

     // pointer to the memory, that can be released when exit from the function
{D}  Result := @StrLocal[1];
Call for MQL5: 
#import "dll_mql5.dll"
    string GetStringBuffer(void);



Current Date: 19.05.2010

What is significant is that all four options work. In the first two options, the work with the line is done through a globally allocated memory.

In option A, the memory is allocated independently, and in option B, the work with memory management is assumed by the memory manager.

In option C, the line constant is stored not in the memory, but in the code segment, so the memory manager does not allocate dynamic memory for its storage. Option D is a bold error in programming, because the memory, allocated for the local variable, can be released immediately after exiting the function.

And although the memory manager does not release this memory instantly, and there is no time for it to fill up with trash, I recommend excluding the latter option from use.

3.5 Using the default parameters

Let's talk about the use of optional parameters. They are interesting because their values do not need to be specified when calling up procedures and functions. Meanwhile, they must be described, strictly after all of the obligatory parameters in the declaration of procedures and functions, as shown in the following example:

Call for Object Pascal: 

function SetOptional(var a:Integer; b:Integer=0):PWideChar; stdcall;
    if (b=0) then Result:='Call with default parameters'
    else          Result:='Call without default parameters';
Call for MQL5:
#import "dll_mql5.dll"
    string SetOptional(int &a, int b=0);

  i = 1;
  s = SetOptional(i); // second parameter is optional


Call with default parameters

For ease of debugging, the code from the above examples is organized as script, it located in the file Testing_DLL.mq5.

4. Possible errors at the design stage

Error: DLL Loading is not allowed.

Solution: Go to the MetaTrader 5 settings through the menu ' Tools-Options' and allow the import of DLL function, as shown in Figure 5.

  Figure 5. Permission to import DLL functions

 Figure 5. Permission to import DLL functions

Error: Cannot find 'function name' in 'DLL name'.
Solution: Check whether the callback function is specified in the Exports section of the DLL project. If it is, you should check the full match of the function's name in DLL and in the mql5 program -considering that it is character sensitive!

Error: Access violation write to [memory address]
Solution: You need to check the correctness of the description of the transmitted parameters (see table 2). Since usually this error is associated with the processing of lines, it is important to follow the recommendations for working with lines, laid out in paragraph 3.4 of this article.

5. Example of DLL code

As a visual example of the use of DLL, consider the calculations of parameters of the regression channel, consisting of three lines. To verify the correctness of the construction of the canal, we will use the built-in object "Canal regression". The calculation of the approximating line for LS (least squares method) is taken from the site, where there is a collection of algorithms for data processing. The algorithms code is presented in several programming languages, including Delphi.

To calculate the coefficients of a and b, by the approximating line y = a + b * x, use the procedure, described in the file LRLine linreg.pas.

 procedure  LRLine (  const  XY: TReal2DArray;  / / Two-dimensional array of real numbers for X and Y coordinates 
                           N : AlglibInteger;  // number of points
                     var Info : AlglibInteger; // conversion status
                       var  A: Double;  / / Coefficients of the approximating line 
                        var  B: Double);

To calculate the parameters of the channel, use the function CalcLRChannel.

Call for Object Pascal:  

function CalcLRChannel(var rates: DoubleArray; const len: Integer;
                          var A, B, max: Double):Integer; stdcall;
var arr: TReal2DArray;
    info: Integer;
    value: Double;

    // copy the data to a two-dimensional array
    for info:= 0 to len - 1 do
      arr[info,0]:= rates[info,0];
      arr[info,1]:= rates[info,1];

    // calculation of linear regression coefficients
    LRLine(arr, len, info, A,  B);

    // find the maximal deviation from the approximation line found
    // and determine the width of the channel 
    max:= rates[0,1] - A;
    for info := 1 to len - 1 do
      value:= Abs(rates[info,1]- (A + B*info));
      if (value > max) then max := value;


Call for MQL5:

#import "dll_mql5.dll"
    int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max);

   double arr[][2], //data array for processing in the ALGLIB format
              a, b,  // Coefficients of the approximating line  
              max; // maximum deviation from the approximating line is equal to half the width of the channel
   int len = period; //number of points for calculation

// copying the history to a two-dimensional array
   int j=0;
   for(int i=rates_total-1; i>=rates_total-len; i--)
      arr[j][0] = j;
      arr[j][1] = close[i];

// calculation of channel parameters

The indicator code, which uses the function CalcLRChannel for calculations, is located in the file LR_Channel.mq5 and below: 

//|                                                   LR_Channel.mq5 |
//|                        Copyright 2009, MetaQuotes Software Corp. |
//|                                     |
#property copyright "2009, MetaQuotes Software Corp."
#property link      ""
#property version   "1.00"
#property indicator_chart_window

#include <Charts\Chart.mqh>
#include <ChartObjects\ChartObjectsChannels.mqh>

#import "dll_mql5.dll"
int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max);

input int period=75;

CChart               *chart;
CChartObjectChannel  *line_up,*line_dn,*line_md;
double                arr[][2];
int OnInit()

   if((chart=new CChart)==NULL)
     {printf("Chart not created"); return(false);}

     {printf("Chart not opened");return(false);}

   if((line_up=new CChartObjectChannel)==NULL)
     {printf("Channel not created"); return(false);}

   if((line_dn=new CChartObjectChannel)==NULL)
     {printf("Channel not created"); return(false);}

   if((line_md=new CChartObjectChannel)==NULL)
     {printf("Channel not created"); return(false);}

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[])

   double a,b,max;
   static double save_max;
   int len=period;


// copying of history to a two-dimensional array
   int j=0;
   for(int i=rates_total-1; i>=rates_total-len; i--)
      arr[j][0] = j;
      arr[j][1] = close[i];

// procedure of calculating the channel parameters

// if the width of the channel has changed

      // Delete the channel

      // Creating a channel with new coordinates
      line_md.Create(chart.ChartId(),"LR_Md_Line",0, time[rates_total-1],     a, time[rates_total-len], a+b*(len-1)    );
      line_up.Create(chart.ChartId(),"LR_Up_Line",0, time[rates_total-1], a+max, time[rates_total-len], a+b*(len-1)+max);
      line_dn.Create(chart.ChartId(),"LR_Dn_Line",0, time[rates_total-1], a-max, time[rates_total-len], a+b*(len-1)-max);

      // assigning the color of channel lines     

      // assigning the line width

void OnDeinit(const int reason)
// Deleting the created objects

   delete line_dn;
   delete line_up;
   delete line_md;
   delete chart;

The result of the indicator's work is the creation of a blue regression channel, as shown in Figure 6. To verify the correctness of the channel's construction, the chart shows a "Regression Canal", from the MetaTrader 5 staffing arsenal of instruments of technical analysis, marked in red.

As can be seen in the figure, the central lines of the channel merge together. Meanwhile, there is a slight difference in the width of the channel (a few points), which are due to the different approaches in its calculation. 

 Figure 6. Comparison of regression channels

Figure 6. Comparison of regression channels


This article describes the features of writing a DLL, using a Delphi application development platform.

Translated from Russian by MetaQuotes Software Corp.
Original article:

Attached files | (276.43 KB) (453.47 KB)
Last comments | Go to discussion (2)
bernd | 10 Jul 2010 at 00:12

It should be mentioned that there is an alternative to using Delphi.

If you are not already a Delphi user you should consider using Lazarus/FPC, it is open source, has almost the same features as Delphi (and even some more), is largely compatible with Delphi source code and I would even bet that all of the above examples compile in Lazarus without any modifications.

If you prefer open source over proprietary software (which is something you should do anyways) then Lazarus is what you are looking for and not a commercial trial version of Delphi.

KRTRADE | 22 Jan 2011 at 02:20

DLL help

Can anyone help guide me .. Can we use MT4 DLL file on MT5 and if yes... where do you instill it in MT5 and any thing else i need to know.


Also which folder do we store the DLL file in ??? 

I do not have the code for the DLL anymore so i really cant rewrite it anymore.



any suggestion and help will be appreciated.  

An Example of a Trading System Based on a Heiken-Ashi Indicator An Example of a Trading System Based on a Heiken-Ashi Indicator

In this article we look into the question of using a Heiken-Ashi indicator in trading. Based on this indicator, a simple trading system is considered and an MQL5 Expert Advisor is written. Trading operations are implemented on the bases of classes of the Standard class library. The testing results of the reviewed trading strategy, are based on the history, and obtained using the built-in MetaTrader 5 strategy tester, are provided in the article.

Using WinInet.dll for Data Exchange between Terminals via the Internet Using WinInet.dll for Data Exchange between Terminals via the Internet

This article describes the principles of working with the Internet via the use of HTTP requests, and data exchange between terminals, using an intermediate server. An MqlNet library class is presented for working with Internet resources in the MQL5 environment. Monitoring prices from different brokers, exchanging messages with other traders without exiting the terminal, searching for information on the Internet – these are just some examples, reviewed in this article.

A DLL-free solution to communicate between MetaTrader 5 terminals using Named Pipes A DLL-free solution to communicate between MetaTrader 5 terminals using Named Pipes

The article describes how to implement Interprocess Communication between MetaTrader 5 client terminals using named pipes. For the use of the named pipes, the CNamedPipes class is developed. For the test of its use and to measure the connection throughput, the tick indicator, the server and client scripts are presented. The use of named pipes is sufficient for real-time quotes.

Genetic Algorithms - It's Easy! Genetic Algorithms - It's Easy!

In this article the author talks about evolutionary calculations with the use of a personally developed genetic algorithm. He demonstrates the functioning of the algorithm, using examples, and provides practical recommendations for its usage.