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:
- 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
- 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
- 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.
Speaking about questions, I hope Renat Fatkhullin, would like to answer pfx question of " I mean seriously what sort of statement is that ", coz Renat wrote on final line of the article, "Thank you for your interest! I am ready to answer any questions", or at least the change of the paragraph.
I do not know when Renat wrote this article, but Microsoft are no longer offering Visual Studio 2005. They offering Visual studio 2010 and 2011 beta and it looks like about to dump 2008.
Click here for Microsoft Visual Studio 2010 , and click here for the 2008. The installer is web installer which is annoying if you have slow internet connection,. They offer offline installer as ISO image but that also contain Visual Studio C# and Visual Basic. After installing, both Studio require some updates which is big as well in MB size.
For those who doesn't like installing Visual Studio, there are other alternatives which you can look at http://en.wikipedia.org/wiki/Comparison_of_integrated_development_environments. My favorites is NetBeans and Code::Block. NetBeans is supported by Oracle and base for their Oracle Solaris Studio. However NetBeans does not comes with compiler so you have to figure the Cygwin or MinGW compiler which is maybe annoying for a newbie. Latest stable of famous DevC++ was 7 year ago, now it's website is full of dead links, looks like no one taking care of them anymore, so I won't recommend it.
It's better to use Visual Studio though, coz there's plenty of options you can play with - if you know how.
...
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.
...
Actually it's good that pfx brought and complained about it and MetaQuotes should resolve this issue, coz they know MetaTrader, better than the rest of us (BTW, Renat Fatkhullin, the author of this article, is also CEO of MetaQuotes). Just because someone brought that up that doesn't make him/her has a the solutions to the issue.
And I agree, that it is not making any sense saying "It's better to make infrequent calls", when .dll is actually being called on every tick. And it's good to hear that from MetaQuotes CEO, honesty is good for a change these days.
@ pfx, looks like they change the paragraph you quoted from ...
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 work inevitably leads to delay of the calling function.
Therefore, it isn't recommended to perform very frequent DLL function calls (hundreds or thousands of times per second) for small operations.
It's better to make infrequent calls.
..to this one ...
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.
I hope they really solve this lost of .dll call speed issue, and not just changing some paragraph of the article to avoid questions.
Speaking about questions, I hope Renat Fatkhullin, would like to answer pfx question of " I mean seriously what sort of statement is that ", coz Renat wrote on final line of the article, "Thank you for your interest! I am ready to answer any questions", or at least the change of the paragraph.
We interested to hear your answer too :).
I'll write more about this when I have time.
I've got this working with a standard int array, but is it possible to pass a CArrayObj / CArrayDouble into Visual Studio C++?
Hello everyone,
great article !
but I was just wondering can anyone help me with doing thing described in this article kind of backwards : getting data (ticks or bars data) into C++ program from metatrader ??