avatar

Andres Jaimes

Calling a DLL from MQL

By Andres Jaimes

- 9 minutes read - 1764 words

There are times when we want access to additional data structures and function libraries than the ones provided by MetaTrader’s MQL. Besides native open-source MQL options, we can create our own Dynamic Link Library (DLL) in C# or C++ and link it to our programs.

In this article, we research how to connect to DLLs created with these languages. We must keep in mind that the code generated by C# is managed code running on the .NET framework, while C++'s code is native. We are going to use Visual Studio 2022 for this exercise.

Creating a DLL with C#

To create a DLL with C#, we need the Unmanaged Exports library by Robert Giesecke or RGiesecke library for short. This library gives our functions the ability to interact with native (unmanaged) executables.

Start by installing Microsoft Build Tools 2015 and .NET 3.5 as recommended in this StackOverflow article. They are necessary to use the RGiesecke library. Restart Visual Studio and create a new C# Class Library (.NET Framework) project. Right-click on the project and select Properties. Make sure to do it on the project, not on the solution. Click on Build and select x86 for Platform target. MQL can only interact with 32-bit DLLs, hence the need to do this change.

Use x86 to create a 32 bit DLL

On the top bar, look for Configuration Manager,

Configuration manager

and click on it and add a new x86 Project context.

Project context

Add the RGiesecke library reference to the project by right-clicking on References and selecting Manage Nuget Packages. Look for unmanaged exports and click the Install button.

Unmanaged exports

Add the following functions to the Class1.cs file.

using RGiesecke.DllExport;
using System.Runtime.InteropServices;

namespace MyDll
{
    public class Class1
    {
        [DllExport("Add", CallingConvention = CallingConvention.StdCall)]
        public static int Add(int a, int b)
        {
            return a + b;
        }

        [DllExport("AddDouble", CallingConvention = CallingConvention.StdCall)]
        public static double AddDouble(double a, double b)
        {
            return a + b;
        }
    }
}

Original version from How To MT4 Call C# DLL Function

The DllExport annotation is provided by the RGiesecke library. It defines the name of the exported function, or in other words, the name that an external program will use to call it. We are exporting two functions in this example.

Build the solution. This process, if successful, will generate a DLL file in the Debug folder of the solution. The full path to it is displayed by the build process. Copy the resulting DLL to MetaTrader’s Data folder > MQL4 > Libraries directory.

We will now create the MetaTrader code that will call the functions in our DLL.

On MetaTrader, open the MetaQuotes Language Editor. Create a new Indicator and add the following code to it.

#import "MyDll.dll"
    int Add(int, int);
    double AddDouble(double, double);
#import

int OnInit() {
    Print("Add: " + IntegerToString(Add(1, 2)));
    Print("Add Double: " + DoubleToString(AddDouble(1.5, 1.5)));
    return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) { }

void OnTick() { }

The #import code section tells MetaTrader the name of the DLL that we will use and the function declarations as defined in the DLL. And then, we simply call them, in this example, on the OnInit function.

Compile the indicator and add it to a MetaTrader chart. During the configuration, make sure to select the Allow DLL imports box.

Allow DLL imports

If everything goes ok, the result of these operations will be displayed in MetaTrader’s Experts tab.

Our last step is to install the corresponding .NET framework on the production computer and our Indicator and DLL files to the MetaTrader Data folder. Make sure the .NET version matches the one used for development.

See Exposing c# code to mql5 using unmanaged exports for additional examples, including passing strings as parameters and return values.

Creating a DLL with C++

It is simpler to create a C++ library because we do not need any external libraries.

Check that Visual Studio has the Desktop Development with C++ component installed by running the Visual Studio Installer application and selecting Modify.

Create a new C++ Dynamic-Link Library (DLL) project. Use the Configuration Manager to create a new 32-bit configuration. This is required by MQL4.

MQL4 uses 32-bit DLL’s, while MQL5 uses 64-bit ones. The compilation section of this example targets MQL4, but it should be easy to target x64 from here.

Select Configuration Manager

Add the 32-bit configuration:

x86 configuration

Add a new header file called myfunctions.h with the following content:

#pragma once

#define EXPORT __declspec(dllexport)

extern "C" EXPORT int add(int a, int b);
extern "C" EXPORT double addDouble(double a, double b);
extern "C" EXPORT void greet(char* name, char* response, unsigned int size);

We have to prepend extern "C" to function declarations to avoid name mangling when using C++.

Add a new source file called myfunctions.cpp with the following content:

#include "pch.h"
#include "myfunctions.h"

#include <string>

int add(int a, int b)
{
  return a + b;
}

double addDouble(double a, double b)
{
  return a + b;
}

/*
 * Sample function to receive and return a string.
 * `response` is an initialized char array provided by MQL.
 */
void greet(char* name, char* buffer, unsigned int size)
{
  std::string str(name);
  std::string result = "hello " + str;
  strcpy_s(buffer, size, result.c_str());
}

We are creating an additional function for our C++ example. This function receives and returns a char array. Note that we convert the provided char array into a std::string for internal manipulation and then turn it back into a char array to return it to MQL. The size parameter contains the buffer size to avoid overflows.

Build the solution. This process will generate a DLL file in the directory displayed by the build phase. Copy the resulting file into the MetaTrader’s Data folder > MQL4 > Libraries directory.

On MetaTrader, open the MetaQuotes Language Editor. Create a new Indicator and add the following code to it.

#import "c-test.dll"
    int add(int, int);
    double addDouble(double, double);
    void greet(char& [], char& [], unsigned int size);
#import

int OnInit()
  {
//---
   Print("Add: " + IntegerToString(add(1, 2)));
   Print("Add Double: " + DoubleToString(addDouble(1.5, 1.5)));
   
   char buffer1[1024]; // to store name
   StringToCharArray("joe doe", buffer1);
   char buffer2[1024]; // to store result
   greet(buffer1, buffer2, ArraySize(buffer2));
   string result = CharArrayToString(buffer2);
   Print("Greet: " + result);
//---
   return(INIT_SUCCEEDED);
}

void OnDeinit(const int reason) { }

void OnTick() { }

As we can see, passing simple values like ints and doubles is similar to our C# example. However, passing a string involves some additional steps:

  • char buffer1[1024]; reserves a buffer to store the input string, in this case a name.
  • StringToCharArray("joe doe", buffer1); converts an MQL string into a char array.
  • char buffer2[1024]; reserves a buffer to store the resulting string.
  • greet(buffer1, buffer2, ArraySize(buffer2)); calls the DLL function.
  • string result = CharArrayToString(buffer2); converts the char array stored in the resulting buffer to an MQL string.
  • Print("Greet: " + result); we use the string.

All char buffers will be automatically removed once we leave the function scope (unless we create them with new).

Compile the indicator and add it to a MetaTrader chart. During the configuration, make sure to select the Allow DLL imports box.

Allow DLL imports

If everything goes ok, the result of these operations will be displayed in MetaTrader’s Experts tab.

Unlike our C# example, we do not need to install any .NET library on the production computer when we are ready to deploy.

Troubleshooting

Long fields

I had some issues dealing with long fields in MQL.

MQL uses 64-bits for longs. The equivalent standard C++ type is long long. Visual C++ offers the non-standard __int64, while gcc allows you to import <stdint.h> and use int64_t as an alternative. Any of them will work fine with MQL.

Strangely, MQL5 was fine using an initial dll version with long instead of long long, but MQL4 crashed as soon as I tried to use the library. Once I updated my dll code to use long long, everything worked fine on both platforms.

Be careful because this issue may remain hidden until you work with functions that use multiple parameters.

The following code shows how to use long long in MQL and C++:

// .h
extern "C" __declspec(dllexport) void set_chart_id(long long chart_id);
// .cpp
void set_chart_id(long long chart_id) {
    long long id = chart_id;
    // do something else...
}
// .mql
#import "my-library.dll"
    void set_chart_id(long chart_id);
#import

See how we use long in MQL and long long in C++.

Read more about the MQL4 and MQL5 data types.

Unicode Strings

I haven’t tested this, but seems like to use Unicode strings in MQL we need to use the wchar_t type. See the following example:

// cpp
void WINAPI fnReplaceString(wchar_t * input, wchar_t * output) {
    wchar_t replaceText[] = L"987654321";
    memcpy(output, replaceText, wcslen(replaceText) * sizeof(wchar_t));
}
// mql
#import "MyDll.dll"
  void fnReplaceString(string, ushort&[]);
#import

ushort outputBuffer[];
ArrayResize(outputBuffer, <whatever>);
fnReplaceString("Input string", outputBuffer);
string strOutput = ShortArrayToString(outputBuffer);

Things to note:

  1. We need to use wchar_t in our C++ code.
  2. We need to use ushort in our MQL code.
  3. We need to use ShortArrayToString to convert the ushort array into a string.
  4. We are not converting the input string into a wchar_t array.

I’ll try to test this in the near future and update this section.

Additional resources

The following resources contain libraries that may be useful for C++ development:

Additional references

Bonus: Creating a shared library for macOS

This is a quick tutorial to create C++ Shared Library on macOS. Not a file we can use from MQL, but still somewhat related to what we do in this article.

PostgreSQL resources

An additional benefit of connecting a DLL is that we have access to any database. These are some valuable resources for Postgres:

MySQL resources