English Русский 中文 Español Deutsch 日本語 Português 한국어 Italiano Türkçe
Exposer le code C# à MQL5 à l'aide d'exportations non gérées

Exposer le code C# à MQL5 à l'aide d'exportations non gérées

MetaTrader 5Intégration | 12 janvier 2022, 14:35
173 0
investeo
investeo

Introduction

Je cherchais depuis longtemps une solution simple qui me permettrait d'utiliser des DLL C# en mode géré en MQL5. Après avoir lu de nombreux articles, j'étais prêt à implémenter un wrapper C++ pour les DLL gérées lorsque je suis tombé sur une solution brillante qui m'a permis d'économiser de nombreuses heures de travail.

La solution a fourni un exemple simple d'exportation de code C# géré à utiliser par une application non gérée. Dans cet article, je vais fournir un aperçu des DLL en mode géré, décrire pourquoi elles ne sont pas accessibles directement depuis MetaTrader et présenter les solutions que j'ai trouvées qui permettent d'utiliser le code géré de MetaTrader.

Je vais fournir un exemple d'utilisation simple du modèle d'exportations non gérées et poursuivre avec tout ce que j'ai découvert. Cela devrait fournir un fond sonore à toute personne essayant d'utiliser le code DLL C# dans MetaTrader 5.


1. Code géré vs code non géré

Étant donné que la plupart des lecteurs ne sont peut-être pas conscients de la différence entre le code géré et non géré, je vais le décrire en quelques phrases. Fondamentalement, MetaTrader utilise le langage MQL pour implémenter des règles de trading, des indicateurs, des conseillers experts et des scripts. Il peut cependant utiliser des bibliothèques déjà implémentées dans d'autres langages et les lier dynamiquement pendant l'exécution. Ces bibliothèques sont également appelées DLL ou Dynamic Link Libraries.

Les bibliothèques sont en fait des fichiers binaires contenant du code source compilé pouvant être invoqué par un certain nombre de programmes externes pour effectuer des opérations spécifiques. Par exemple, la bibliothèque de réseau neuronal peut exporter des fonctions pour la formation et les tests de réseaux de neurones, la bibliothèque de dérivée peut exporter des calculs de différentes dérivées, la bibliothèque de matrices peut exporter des opérations sur des matrices. Les DLL pour MetaTrader sont devenues de plus en plus populaires car elles permettaient de masquer des parties de l’implémentation d'indicateurs ou d' Expert Advisors L'une des principales raisons d'utiliser les bibliothèques est de réutiliser le code existant sans avoir besoin de l'implémenter encore et encore.

Avant que .NET n'existe, toutes les DLL compilées par Visual Basic, Delphi, VC++, que ce soitCOM, Win32 ou C++ simple, pouvaient être directement exécutées par le système d'exploitation. Nous appelons ce code non géré ou code natif. Ensuite, .NET a vu le jour et a fourni un environnement très différent.

Le code est contrôlé (ou géré) par .NET Common Language Runtime -CLR. Les compilateurs CLR sont tenus de produire à partir du code source, qui peut être écrit dans plusieurs langues différentes, des métadonnées et un langage intermédiaire commun -CIL.

CIL est un langage de niveau supérieur indépendant de la machine et les métadonnées décrivent entièrement les types d'objets décrits par CIL conformément à la spécification de type commun -CTS Étant donné que CLR connait tout sur les types, il peut nous fournir un environnement d'exécution géré. La gestion peut être considérée comme un ramasse-miettes - gestion automatique de la mémoire et suppression d'objets et assurer la sécurité - protection contre les erreurs courantes dans les langues natives qui pourraient provoquer l'exécution de code étranger avec des privilèges d'administrateur ou simplement une surcharge de mémoire.

Il faut mentionner que le code CIL n'est jamais exécuté directement - il est toujours traduit en code machine natif par JIT (Just-In-Time) compilation ou par pré -compilation du CIL en assembleur. Pour une personne qui lit ceci pour la première fois, la notion de code en mode géré peut être déroutante, c'est pourquoi je colle le flux général dans CLR ci-dessous :

 

 

Figure 1. Common Language Runtime 


2. Implémentations possibles de l'accès au code géré à partir de MQL5

Dans le paragraphe suivant, je décrirai les méthodes qui permettent d'accéder au code géré à partir du code non géré.

Je pense qu'il vaut la peine de tous les mentionner car il y a peut-être quelqu'un qui préférerait utiliser une autre méthode que celle que j'utilise. Les méthodes en usage sont COM Interop, Reverse P/Invoke, C++ IJW, la classe wrapper C++/Cli et les exportations non gérées.


2.1. COM Interop 

Component Object ModelCOMest une norme d'interface binaire introduite par Microsoft au début des années 90. L'idée centrale de cette technologie est de permettre à un objet créé dans différents langages de programmation d'être utilisé par tout autre objet COM sans connaître son implémentation interne. Une telle exigence impose l’implémentation d'une interface stricte et bien définie du COM qui est totalement distincte de l’implémentation.

En fait, COM a été remplacé par la technologie .NET et Microsoft pousse à utiliser .NET au lieu de COM. Afin de fournir une compatibilité descendante avec le code plus ancien, .NET peut coopérer avec COM dans les deux sens, c'est-à-dire que .NET peut appeler des méthodes COM et que l'objet COM peut utiliser le code géré .NET.

Cette fonctionnalité est appelée COM Interopability ou COM Interop. L'API d'interopérabilité COM se trouve dans l'espace de noms géré System.Runtime.InteropServices.

 

Figure 2. Modèle d'interopérabilité COM

Figure 2. Modèle d'interopérabilité COM 


Le code d'interopérabilité COM suivant appelle une seule fonction raw_factorial.

Veuillez noter les fonctions CoInitialize() CoCreateInstance() et CoUninitialize() et la fonction d'appel d'interface :

#include "windows.h"
#include <stdio.h>
#import "CSDll.tlb" named_guids

int main(int argc, char* argv[])
{
    HRESULT hRes = S_OK;
    CoInitialize(NULL);
    CSDll::IMyManagedInterface *pManagedInterface = NULL;

    hRes = CoCreateInstance(CSDll::CLSID_Class1, NULL, CLSCTX_INPROC_SERVER, 
     CSDll::IID_IMyManagedInterface, reinterpret_cast<void**> (&pManagedInterface));

    if (S_OK == hRes)
    {
        long retVal =0;
        hRes = pManagedInterface->raw_factorial(4, &retVal);
        printf("The value returned by the dll is %ld\n",retVal);
        pManagedInterface->Release();
    }

    CoUninitialize();
    return 0;
}

Pour en savoir plus sur COM Interop, veuillez lire la documentation détaillée sur Introduction à COM Interop et l'exemple d'utilisation que j'ai trouvé sur le blog msdn : Comment appeler du code C++ à partir de Managed, et vice versa (Interop).


2.2. Inverser P/Invoquer

Platform Invoke, appelé P/Invoke, permet à .NET d'appeler n'importe quelle fonction dans n'importe quel langage non géré tant que sa signature est re-déclarée. Ceci est réalisé en exécutant un pointeur natif de fonction à partir de .NET. L'utilisation est bien décrite dansPlatform Invoke Tutorial..

L'utilisation de base est d'utiliser l'attribut DllImport pour marquer la fonction importée :

// PInvokeTest.cs
using System;
using System.Runtime.InteropServices;

class PlatformInvokeTest
{
    [DllImport("msvcrt.dll")]
    public static extern int puts(string c);
    [DllImport("msvcrt.dll")]
    internal static extern int _flushall();

    public static void Main() 
    {
        puts("Test");
        _flushall();
    }
}

L'opération inverse peut être décrite comme fournissant un rappel de délégué géré au code non géré.

Ceci s'appelle Reverse P/Invoke et est réalisé en implémentant une fonction de délégation publique dans un environnement géré et en important la fonction d'appelant implémentée dans la DLL native : 

#include <stdio.h>
#include <string.h>
typedef void (__stdcall *callback)(wchar_t * str);
extern "C" __declspec(dllexport) void __stdcall caller(wchar_t * input, int count, callback call)
{
      for(int i = 0; i < count; i++)
      {
            call(input);
      }
}

L'exemple de code géré est le suivant :

using System.Runtime.InteropServices;
public class foo
{
    public delegate void callback(string str);
    public static void callee(string str)
    {
        System.Console.WriteLine("Managed: " +str);
    }
    public static int Main()
    {
        caller("Hello World!", 10, new callback(foo.callee));
        return 0;
    }
    [DllImport("nat.dll",CallingConvention=CallingConvention.StdCall)]
    public static extern void caller(string str, int count, callback call);
}

Le point principal de cette solution est que cela nécessite que le côté géré entame l'interaction.

Pour plus d'informations, veuillez lire Gotchas with Reverse Pinvoke (unmanaged to managed code callbacks) et PInvoke-Reverse PInvoke et  stdcall - cdecl.


2.3. C++ IJW

L'interopérabilité C++, appelée It Just Works (IJW)) est une fonctionnalité spécifique à C++, fournie parManaged Extensions for C++ :

#using <mscorlib.dll>
using namespace System;
using namespace System::Runtime::InteropServices;

#include <stdio.h>

int main()
{
   String * pStr = S"Hello World!";
   char* pChars = (char*)Marshal::StringToHGlobalAnsi(pStr).ToPointer(); 
   
   puts(pChars);
   
   Marshal::FreeHGlobal(pChars);
} 

Cette solution peut être utile pour les personnes souhaitant utiliser leur C++ géré dans une application non gérée. Pour une référence complète, veuillez lire Interoperability in Managed Extensions for C++ and Using IJW in Managed C++.


2.4. Classe wrapper C++/Cli

L'implémentation de la classe wrapper C++/Cli tire son nom de l'incorporation ou de l'encapsulation d'une classe gérée par une autre classe écrite en mode C++/Cli. La première étape pour écrire la DLL wrapper consiste à écrire la classe C++ qui encapsule les méthodes de la classe gérée d'origine.

La classe wrapper doit comprendre un handle vers un objet .NET à l'aide du modèle gcroot<> et doit déléguer tous les appels de la classe d'origine. La classe wrapper est compilée au format IL (langage intermédiaire) et est donc gérée.

L'étape suivante consiste à écrire la classe C++ native avec l’instruction #pragma non gérée qui enveloppe la classe IL et délègue tous les appels avec __declspec(dllexport) instruction Ces étapes créeront une DLL C++ native pouvant être utilisée par n'importe quelle application non gérée.

Veuillez consulter l'exemple d'implémentation. La première étape consiste à implémenter le code C#.

L'exemple de classe de calculatrice comporte deux méthodes publiques : 

public class Calculator
{
    public int Add(int first, int second)
    {
        return first + second;
    }
    public string FormatAsString(float i)
    {
        return i.ToString();
    }
}

L'étape suivante consiste à écrire un wrapper géré qui déléguera toutes les méthodes de la classe calculatrice :

#pragma once
#pragma managed

#include <vcclr.h>

class ILBridge_CppCliWrapper_Calculator {
private:
    //Aggregating the managed class
    gcroot<CppCliWrapper::Calculator^> __Impl;
public:
    ILBridge_CppCliWrapper_Calculator() {
        __Impl = gcnew CppCliWrapper::Calculator;
    }
    int Add(int first, int second) {
        System::Int32 __Param_first = first;
        System::Int32 __Param_second = second;
        System::Int32 __ReturnVal = __Impl->Add(__Param_first, __Param_second);
        return __ReturnVal;
    }
    wchar_t* FormatAsString(float i) {
        System::Single __Param_i = i;
        System::String __ReturnVal = __Impl->FormatAsString(__Param_i);
        wchar_t* __MarshaledReturnVal = marshal_to<wchar_t*>(__ReturnVal);
        return __MarshaledReturnVal;
    }
};

Veuillez noter que la référence à la classe Calculator d'origine est stockée à l'aide de l'instruction gcnew et stockée en tant que modèle gcroot<>. Toutes les méthodes encapsulées peuvent avoir le même nom que les méthodes d'origine et les paramètres et les valeurs de retour sont respectivement précédés de __Param et __ReturnVal.

Maintenant, la classe C++ non gérée qui enveloppe le C++/Cli et exporte les méthodes DLL C++ natives doit être implémentée.

Le fichier d'en-tête doit comprendre la définition de classe avec l’instruction __declspec(dllexport) et stocker le pointeur vers la classe wrapper.

#pragma once
#pragma unmanaged

#ifdef THISDLL_EXPORTS
#define THISDLL_API __declspec(dllexport)
#else
#define THISDLL_API __declspec(dllimport)
#endif

//Forward declaration for the bridge
class ILBridge_CppCliWrapper_Calculator;

class THISDLL_API NativeExport_CppCliWrapper_Calculator {
private:
    //Aggregating the bridge
    ILBridge_CppCliWrapper_Calculator* __Impl;
public:
    NativeExport_CppCliWrapper_Calculator();
    ~NativeExport_CppCliWrapper_Calculator();
    int Add(int first, int second);
    wchar_t* FormatAsString(float i);
};

Et son implémentation :

#pragma managed
#include "ILBridge_CppCliWrapper_Calculator.h"
#pragma unmanaged
#include "NativeExport_CppCliWrapper_Calculator.h"

NativeExport_CppCliWrapper_Calculator::NativeExport_CppCliWrapper_Calculator() {
    __Impl = new ILBridge_CppCliWrapper_Calculator;
}
NativeExport_CppCliWrapper_Calculator::~NativeExport_CppCliWrapper_Calculator()
{
    delete __Impl;
}
int NativeExport_CppCliWrapper_Calculator::Add(int first, int second) {
    int __ReturnVal = __Impl->Add(first, second);
    return __ReturnVal;
}
wchar_t* NativeExport_CppCliWrapper_Calculator::FormatAsString(float i) {
    wchar_t* __ReturnVal = __Impl->FormatAsString(i);
    return __ReturnVal;
}

Un guide étape par étape pour créer cette classe wrapper est décrit sur.NET to C++ Bridge.

Une référence complète pour la création de wrappers est disponible surMixing .NET et le code natif et pour des informations générales sur la déclaration des handles dans les types natifs, veuillez lireComment : Déclarez les handles dans les types natifs.


2.5. Exportations non gérées 

Cette technique est entièrement décriteExpert .NET 2.0 IL Assembleur que je recommande à tous ceux qui souhaitent en savoir plus sur les détails du compilateur .NET. L'idée principale est d'exposer les méthodes gérées en tant qu'exportations non gérées d'une DLL gérée en décompilant le module déjà compilé en code IL à l'aide d' ILDasm, en modifiant les tables VTable et VTableFixup du module et en recompilant la DLL en utilisant ILAsm.

Cette tâche peut sembler intimidante, mais le résultat de cette opération sera de produire une DLL pouvant être utilisée à partir de n'importe quelle application non gérée. Il faut se rappeler qu'il s'agit toujours d'un assemblage géré, donc le cadre de travail .NET doit être installé.  Un didacticiel pas à pas pour ce faire est disponible surExporter le code géré en tant que non géré.

Après avoir décompilé la DLL à l'aide d' ILDasm, nous obtenons le code source en langage IL. Veuillez observer un exemple simple de code IL avec exportation non gérée collé ci-dessous :

assembly extern mscorlib {}
..assembly UnmExports {}
..module UnmExports.dll
..corflags 0x00000002
..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)
..method public static void foo()
{
..vtentry 1:1
..export [1] as foo
ldstr "Hello from managed world"
call void [mscorlib]System.Console::WriteLine(string)
ret
}

Les lignes de code source IL responsables de l’implémentation des exportations non gérées sont :

..vtfixup [1] int32 fromunmanaged at VT_01
..data VT_01 = int32(0)

et

..vtentry 1:1
..export [1] as foo

La première partie est chargée d'ajouter l'entrée de fonction dans la table VTableFixup et de définir l'adresse virtuelle VT_01 pour la fonction. La deuxième partie indique quel VTEntry doit être utilisé pour cette fonction et l'alias d'exportation pour la fonction à exporter. 

Les avantages de cette solution sont que pendant la phase d'implémentation de la DLL, nous n'avons pas besoin d'implémenter de code supplémentaire en dehors de la DLL C# gérée habituelle et, comme indiqué dans le livre, que cette méthode ouvre complètement le monde géré avec toutes ses bibliothèques de sécurité et de classes aux non gérés. clientes.

L'inconvénient est que le langage assembleur .NET ne convient pas à tout le monde. J'étais convaincu que j'écrirais plutôt une classe wrapper c++ jusqu'à ce que je trouve un modèle d'exportation non géré de Robert Gieseckehttp://sites.google.com/site/robertgiesecke/ qui permet d'utiliser des exportations non gérées sans avoir besoin d'entrer dans le code IL.


3. Modèle C# d'exportations non gérées

Le modèle pour les projets d'exportations non gérées C# de R.Giesecke utilise la tâche MSBuild tâche qui ajoute automatiquement les correctifs VT appropriés après la génération, il n'est donc inutile de modifier le code IL. Le package de modèle doit uniquement être téléchargé sous forme de fichier zip et copié dans le dossier ProjectTemplates de Visual Studio.

Après avoir compilé le projet, le fichier DLL subséquent peut être importé sans problème par MetaTrader, je fournirai les exemples dans les sections suivantes.


4. Exemples

C'était une tâche assez difficile de comprendre comment passer des variables, des tableaux et des structures entre MetaTrader et C# en utilisant la méthode de assemblage correcte et je pense que les informations fournies ici vous feront gagner beaucoup de temps. Tous les exemples ont été compilés sur Windows Vista avec .NET 4.0 et Visual C# Express 2010. Je joins également un exemple de DLL avec du code MQL5 qui évoque des fonctions de C# DLL à l'article.


4.1. Exemple: Ajout de deux variables entières, doubles ou flottantes dans la fonction DLL et retour du résultat à MetaTrader

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

namespace Testme
{
    class Test
    {

        [DllExport("Add", CallingConvention = CallingConvention.StdCall)]
        public static int Add(int left, int right)
        {
            return left + right;
        }

        [DllExport("Sub", CallingConvention = CallingConvention.StdCall)]
        public static int Sub(int left, int right)
        {
            return left - right;
        }

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

        [DllExport("AddFloat", CallingConvention = CallingConvention.StdCall)]
        public static float AddFloat(float left, float right)
        {
            return left + right;
        }

    }
}

Comme vous l'avez peut-être remarqué, chaque fonction exportée est précédée de l’instruction DllExport. Le premier paramètre décrit l'alias de la fonction exportée et le deuxième paramètre la convention d'appel, pour MetaTrader nous devons utiliser CallingConvention.StdCall.

Le code MQL5 qui importe et utilise les fonctions exportées à partir de la DLL est simple et ne diffère pas des autres DLL écrites en C++ natif. Au début, il faut déclarer les fonctions importées dans le bloc #import et indiquer quelles fonctions de la DLL peuvent être utilisées ultérieurement à partir du code MQL5 :

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample1.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Add(int left,int right);
   int Sub(int left,int right);
   float AddFloat(float left,float right);
   double AddDouble(double left,double right);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   for(int i=0; i<3; i++)
     {
      Print(Add(i,666));
      Print(Sub(666,i));
      Print(AddDouble(666.5,i));
      Print(AddFloat(666.5,-i));
     }
  }
//+------------------------------------------------------------------+

Résultat 

2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 664
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 668
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 665
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 667
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.50000
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666.5
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666
2011.01.30 21:28:18     UnmanagedExportsDLLExample1 (EURUSD,M1) 666


4.2. Exemple 2 Accès à un tableau dimensionnel

        [DllExport("Get1DInt", CallingConvention = CallingConvention.StdCall)]
        public static int Get1DInt([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  int[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DFloat", CallingConvention = CallingConvention.StdCall)]
        public static float Get1DFloat([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  float[] tab, int i, int idx)
        {
            return tab[idx];
        }

        [DllExport("Get1DDouble", CallingConvention = CallingConvention.StdCall)]
        public static double Get1DDouble([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]  double[] tab, int i, int idx)
        {
            return tab[idx];
        }

Afin d’assembler un tableau à une dimension, l’instruction MarshalAs doit transmettre UnmanagedType.LPArray comme premier paramètre et SizeParamIndex comme deuxième paramètre. SizeParamIndex indique quel paramètre (à partir de 0) est le paramètre comportant la taille du tableau.

Dans les exemples ci-dessus, i est la taille du tableau et idx est l'indice de l'élément à renvoyer.

L'exemple de code MQL5 utilisant l'accès au tableau est ci-dessous :

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample2.mq5 |
//|                                      Copyright 2010, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2010, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
   int Get1DInt(int &t[],int i,int idx);
   float Get1DFloat(float &t[],int i,int idx);
   double Get1DDouble(double &t[],int i,int idx);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int tab[3];
   tab[0] = 11;
   tab[1] = 22;
   tab[2] = 33;

   float tfloat[3]={0.5,1.0,1.5};
   double tdouble[3]={0.5,1.0,1.5};

   for(int i=0; i<3; i++)
     {
      Print(tab[i]);
      Print(Get1DInt(tab,3,i));
      Print(Get1DFloat(tfloat,3,i));
      Print(Get1DDouble(tdouble,3,i));

     }
  }
//+------------------------------------------------------------------+

Résultat

2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 33
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 1.00000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 22
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.5
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 0.50000
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11
2011.01.30 21:46:25     UnmanagedExportsDLLExample2 (EURUSD,M1) 11

 

4.3. Exemple: Remplir un tableau à une dimension et le renvoyer à MetaTrader

        [DllExport("SetFiboArray", CallingConvention = CallingConvention.StdCall)]
        public static int SetFiboArray([MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)]
        int[] tab, int len, [In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] res)
        {
            res[0] = 0;
            res[1] = 1;
            
            if (len < 3) return -1;
            for (int i=2; i<len; i++)
                res[i] = res[i-1] + res[i-2];
            return 0;
        }

Cet exemple utilise deux tableaux d'entrée pour comparer la convention des paramètres d'entrée. Si des éléments modifiés doivent être renvoyés à Metatrader (en passant par référence), il suffit de mettre les attributs [In, Out,] avant l'attribut MarshalAs.

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample3.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"  
    int SetFiboArray(int& t[], int i, int& o[]);
#import


//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
int fibo[10];
static int o[10];

   for (int i=0; i<4; i++)
   { fibo[i]=i; o[i] = i; }
   
   SetFiboArray(fibo, 6, o);
   
   for (int i=0; i<6; i++)
      Print(IntegerToString(fibo[i])+":"+IntegerToString(o[i]));
      
  }
//+------------------------------------------------------------------+

 Résultat

2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:5
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:3
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 3:2
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 2:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 1:1
2011.01.30 22:01:39     UnmanagedExportsDLLExample3 (EURUSD,M1) 0:0


4.4. Exemple 4 Accès à un tableau à deux dimensions

        public static int idx(int a, int b) {int cols = 2; return a * cols + b; }
 
        [DllExport("Set2DArray", CallingConvention = CallingConvention.StdCall)]
        public static int Set2DArray([In, Out, MarshalAs(UnmanagedType.LPArray, SizeParamIndex = 1)] int[] tab, int len)
        {
            tab[idx(0, 0)] = 0;
            tab[idx(0, 1)] = 1;
            tab[idx(1, 0)] = 2;
            tab[idx(1, 1)] = 3;
            tab[idx(2, 0)] = 4;
            tab[idx(2, 1)] = 5;
            
            return 0;
        }

Un tableau à deux dimensions n'est pas si simple à assembler, mais j'ai utilisé une astuce - à savoir passer un tableau 2D en tant que tableau unidimensionnel et accéder aux éléments du tableau par la fonction idx auxiliaire.

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample4.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int Set2DArray(int &t[][2],int i);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   int t2[3][2];

   Set2DArray(t2,6);

   for(int row=0; row<3; row++)
      for(int col=0; col<2; col++)
         Print("t2["+IntegerToString(row)+"]["+IntegerToString(col)+"]="+IntegerToString(t2[row][col]));

  }
//+------------------------------------------------------------------+

Résultat

2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][1]=5
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[2][0]=4
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][1]=3
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[1][0]=2
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][1]=1
2011.01.30 22:13:01     UnmanagedExportsDLLExample4 (EURUSD,M1) t2[0][0]=0


4.5. Exemple 5 Remplacement du contenu de la chaîne

  	 [DllExport("ReplaceString", CallingConvention = CallingConvention.StdCall)]
        public static int ReplaceString([In, Out, MarshalAs(UnmanagedType.LPWStr)] StringBuilder str,
        [MarshalAs(UnmanagedType.LPWStr)]string a, [MarshalAs(UnmanagedType.LPWStr)]string b)
        {
            str.Replace(a, b);

            if (str.ToString().Contains(a)) return 1;
            else  return 0;
        }

Cet exemple est court mais m'a pris beaucoup de temps à implémenter car j'ai essayé d'utiliser le paramètre de chaîne en utilisant les attributs [In, Out] ou avec des mots-clés ref ou out en vain.

La solution consiste à utiliser StringBuilder au lieu d'une variable de chaîne.

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample5.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"   
   int ReplaceString(string &str,string a,string b);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
//---
   string str="A quick brown fox jumps over the lazy dog";
   string stra = "fox";
   string strb = "cat";


   Print(str);
   Print(ReplaceString(str,stra,strb));
   Print(str);

  }
//+------------------------------------------------------------------+

Résultat

2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown cat jumps over the lazy dog
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) 0
2011.01.30 22:18:36     UnmanagedExportsDLLExample5 (EURUSD,M1) A quick brown fox jumps over the lazy dog


4.6. Exemple 6: Envoi et modification de la structure MqlTick

	 private static List<MqlTick> list;

	 [StructLayout(LayoutKind.Sequential, Pack = 1)]
        public struct MqlTick
        {
            public Int64 Time;
            public Double Bid;
            public Double Ask;
            public Double Last;
            public UInt64 Volume;
        }

        [DllExport("AddTick", CallingConvention = CallingConvention.StdCall)]
        public static int AddTick(ref MqlTick tick, ref double bidsum)
        {
            bidsum = 0.0;

            if (list == null) list = new List<MqlTick>();

            tick.Volume = 666;
            list.Add(tick);

            foreach (MqlTick t in list) bidsum += t.Ask;

            return list.Count;
        }

La structure MqlTick est passée comme référence, marquée par le mot-clé ref. La structure MqlTick elle-même doit être précédée de l'attribut [StructLayout (LayoutKind.Sequential, Pack =1)].

Le paramètre Pack décrit l'alignement des données dans la structure veuillez lire, StructLayoutAttribute.Pack Field.Pack pour plus de détails.  

//+------------------------------------------------------------------+
//|                                  UnmanagedExportsDLLExample6.mq5 |
//|                                      Copyright 2011, Investeo.pl |
//|                                                http:/Investeo.pl |
//+------------------------------------------------------------------+
#property copyright "Copyright 2011, Investeo.pl"
#property link      "http:/Investeo.pl"
#property version   "1.00"

#import "Testme.dll"
   int AddTick(MqlTick &tick, double& bidsum);
#import
//+------------------------------------------------------------------+
//| Custom indicator initialization function                         |
//+------------------------------------------------------------------+
int OnInit()
  {
//--- indicator buffers mapping
   
//---
   return(0);
  }
//+------------------------------------------------------------------+
//| Custom indicator iteration function                              |
//+------------------------------------------------------------------+
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[])
  {
//---
   MqlTick newTick;
   double bidsum;
   
   SymbolInfoTick(Symbol(), newTick);
   
   Print("before = " + IntegerToString(newTick.volume));
   
   Print(AddTick(newTick, bidsum));
   
   Print("after = " + IntegerToString(newTick.volume) + " : " + DoubleToString(bidsum));
   
   
//--- return value of prev_calculated for next call
   return(rates_total);
  }
//+------------------------------------------------------------------+

Résultat

2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 8.167199999999999
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 6
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 6.806
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 5
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 5.4448
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 4
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 4.0836
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 3
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 2.7224
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 2
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) before = 0
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) after = 666 : 1.3612
2011.01.30 23:59:05     TickDLLSend (EURUSD,M1) 1
2011.01.30 23:59:04     TickDLLSend (EURUSD,M1) before = 0

 

Conclusion

Dans cet article, j'ai présenté différentes méthodes d'interaction entre le code MQL5 et le code C# géré.

J'ai également fourni plusieurs exemples sur la façon de rassembler des structures MQL5 contre C# et d'appeler des fonctions DLL exportées dans des scripts MQL5. Je pense que les exemples fournis peuvent servir de base à de futures recherches sur l'écriture de DLL en code géré.

Cet article ouvre également la porte à MetaTrader pour utiliser de nombreuses bibliothèques déjà implémentées en C#. Pour plus de références, veuillez lire les articles liés dans la section Références.


Pour le tester, veuillez localiser les fichiers dans les dossiers suivants :

MQL5\Libraries\testme.dll
MQL5\Scripts\unmanagedexportsdllexample1.mq5
MQL5\Scripts\unmanagedexportsdllexample2.mq5
MQL5\Scripts\unmanagedexportsdllexample3.mq5
MQL5\Scripts\unmanagedexportsdllexample4.mq5
MQL5\Scripts\unmanagedexportsdllexample5.mq5
MQL5\Experts\unmanagedexportsdllexample6.mq5


Les références

  1. Exportation de DLL .NET avec Visual Studio 2005 à utiliser par des applications natives
  2. Interoperating with Unmadged Coded
  3. Introduction to COM Interop
  4. Component Object Model (COM)
  5. Exportation à partir d'une DLL à l'aide de __declspec(dllexport)
  6. comment: Déclarer les handles dans les types natifs
  7. Comment appeler un code C++ code depuis Managed, et vice versa (Interop)
  8. Reverse P/Invoke et exception 
  9. Comment appeler une DLL gérée à partir de code Visual C++ natif dans Visual Studio.NET ou dans Visual Studio 2005
  10. Platform Invoke Tutorial
  11. PInvoke-Reverse PInvoke et __stdcall - __cdecl
  12. Gotchas avec Reverse Pinvoke (rappels de code non gérés vers gérés)
  13. Mixage de .NET et de code natif
  14. Exporter le code géré comme non géré
  15. Comprendre l'interopérabilité COM classique avec les applications .NET
  16. Extensions gérées pour la programmation C++
  17. Site de Robert Giesecke
  18. MSBuild Tasks
  19. Exécution du Langage Commun

Traduit de l’anglais par MetaQuotes Ltd.
Article original : https://www.mql5.com/en/articles/249

Modèle de Régression Universel pour la Prévision des Prix du Marché Modèle de Régression Universel pour la Prévision des Prix du Marché
Le prix du marché est formé à partir d’un équilibre stable entre l’offre et la demande qui, à son tour, dépendent d’une variété de facteurs économiques, politiques et psychologiques. Les différences de nature ainsi que les causes d’influence de ces facteurs rendent difficile la prise en compte directe de tous les composants. Cet article présente une tentative de prédire le prix du marché sur la base d’un modèle de régression élaboré.
Marche Aléatoire et l’Indicateur de Tendance Marche Aléatoire et l’Indicateur de Tendance
Marche Aléatoire ressemble beaucoup aux données réelles du marché, mais elle présente des caractéristiques importantes. Dans cet article, nous examinerons les propriétés de Marche Aléatoire, simulées à l’aide du jeu de lancer de pièces. Pour étudier les propriétés des données, l’indicateur de tendance est élaboré.
Utilisation des Indicateurs MetaTrader 5 avec le Cadre d'Apprentissage Automatique ENCOG pour la Prédiction de Séries Chronologiques Utilisation des Indicateurs MetaTrader 5 avec le Cadre d'Apprentissage Automatique ENCOG pour la Prédiction de Séries Chronologiques
Cet article présente la connexion de MetaTrader 5 à ENCOG - Advanced Neural Network and Machine Learning Framework. Il comporte la description et l’implémentation d'un indicateur de réseau neuronal simple axé sur des indicateurs techniques standard et un Expert Advisor axé sur un indicateur neuronal. Tout le code source, les binaires compilés, les DLL et un réseau formé exemplaire sont joints à l'article.
Implémentation  d'Indicateurs en tant que Classes par des Exemples de Zigzag et d' ATR Implémentation d'Indicateurs en tant que Classes par des Exemples de Zigzag et d' ATR
Le débat sur une manière optimale de calculer les indicateurs est sans fin. Où devrions-nous calculer les valeurs de l'indicateur - dans l'indicateur lui-même ou intégrer toute la logique dans un Expert Advisor qui l'utilise ? L'article décrit une des variantes de déplacement du code source d'un indicateur personnalisé iCustom directement dans le code d'un Expert Advisor ou d'un script avec optimisation des calculs et modélisation de la valeur prev_calculated.