English Русский 中文 Español Deutsch 日本語 한국어 Français Italiano Türkçe
Expondo código C# para MQL5 usando exportações não gerenciadas

Expondo código C# para MQL5 usando exportações não gerenciadas

MetaTrader 5Integração | 14 fevereiro 2014, 15:10
4 027 1
investeo
investeo

Introdução

Eu procurei por um longo tempo encontrar uma solução simples que me permitiria utilizar DLLs C# no modo gerenciado em MQL5. Após ler muitos artigos, eu estava pronto para implementar empacotados C++ para DLL gerenciado quando eu me deparei com uma brilhante solução que salvou-me muitas horas de trabalho.

A solução ofereceu um simples exemplo de exportação de código C# gerenciado a ser consumido por uma aplicação não gerenciada. Neste artigo, eu oferecerei um pano de fundo sobre DLLs no modo gerenciado, descreverei porque eles não podem ser acessados diretamente do MetaTrader, e introduzirei as soluções que encontrei que permitem utilizar código gerenciado a partir do MetaTrader.

Eu fornecerei um exemplo de utilização simples de modelos de exportação não gerenciada e continuarei com tudo que descobri. Isto deve oferecer um pano de fundo sólido para todos que tentem utilizar código DLL C# no MetaTrader 5.


1. Código gerenciado X não gerenciado

Já que a maioria dos leitores podem não estar cientes da diferença entre código gerenciado e não gerenciado, eu a descreverei um poucas frases. Basicamente, o MetaTrader utiliza linguagem MQL para implementar regras de trading, indicadores, expert advisors e scripts. Ele pode, porém, fazer uso de bibliotecas já implementadas em outras linguagens e fazer link delas dinamicamente durante o tempo de execução. Estas bibliotecas são também chamadas de DLLs, ou Dynamic Link Libraries.

As bibliotecas são, na verdade, arquivos binários que contém código-fonte compilado que pode ser invocado por um número de programas externos para executar operações específicas. Por exemplo, a biblioteca de rede neural pode exportar funções para treinamento e teste de rede neural, a biblioteca derivativa pode exportar cálculos de diferentes derivativas, a biblioteca de matriz pode exportar operações de matrizes. DLLs para MetaTrader tornaram-se populares quando eles tornaram possível esconder partes da implementação de indicators ou expert advisors. Uma grande razão, entretanto, para utilizar bibliotecas, é reutilizar código existente sem precisar implementá-lo repetidamente.

Antes de .NET existir, todos os DLLs que eram compilados por Visual Basic, Delphi, VC++, fossem eles COM, Win32, ou simples C++, podiam ser executados diretamente pelo sistema operacional. Nós nos referimos a este código como não administrado ou código nativo. Então .NET surgiu e ofereceu um ambiente muito diferente.

O código é controlado (ou administrado) pelo .NET Common Language Runtime - CLR. Compiladores CLR são requeridos para produzir a partir do código-fonte, que pode ser escrito em várias linguagens diferentes, Metadata e Common Intermediate Language - CIL.

CIL é uma linguagem de nível superior independente de máquina e a Metadata descreve totalmente tipos dos objetos descritos pelo CIL de acordo com Common Type Specification - CTS. Já que o CLR sabe tudo sobre os tipos, ele pode nos oferecer o ambiente de execução administrado. Administrar pode ser considerado como coleta de lixo - administração de memória automática e exclusão de objetos e fornecimento de segurança - proteção contra erros comuns em linguagens nativas que poderiam causar execução de código estranho com privilégios do administrador, ou simplesmente desativação da memória.

Deve ser mencionado que o código CIL nunca é diretamente executado - ele é sempre traduzido em código nativo da máquina pela compilação JIT (Just-In-Time) ou pré-compilando CIL para dentro do conjunto. Para uma pessoa que lê isto pela primeira vez, a noção de código de modo administrado pode ser confusa, portanto, eu vou colar o fluxo geral dentro do CLR, abaixo:

Figura 1. Common Language Runtime


2. Possíveis implementações do acesso ao código administrado a partir do MQL5

No parágrafo seguinte, eu descreverei os métodos que permitem acessar código administrado a partir de código não administrado.

Eu acho que é valioso mencioná-los, já que pode haver alguém que prefira utilizar outro método ao invés do que estou usando. Os métodos que estão em uso são COM Interop, P/Invoke, C++ IJW, C++/Cli wrapper class e Unmanaged Exports.


2,1. COM Interop

Component Object Model (COM) é uma interface binária padrão introduzida pela Microsoft no começo dos anos noventa. A ideia central desta tecnologia é permitir que objetos criados em diferentes linguagens de programação sejam usados por qualquer outro objeto COM sem conhecer usa implementação interna. Tais requerimentos reforçam a implementação da interface estritamente bem definida do COM, que está completamente separado da implementação.

Na verdade, COM foi substituído por tecnologia .NET e a Microsoft pressiona pelo uso de .NET ao invés de COM. Para oferecer compatibilidade com versões anteriores com código mais antigo, .NET pode cooperar com COM em ambas as direções, ou seja, .NET pode solicitar métodos COM e objeto COM pode fazer uso do código gerenciado .NET.

Esta funcionalidade é chamada interoperabilidade COM ou COM Interop. A API do COM está no namespace administrado System.Runtime.InteropServices.

Figura 2. Modelo de interoperabilidade COM

Figura 2. Modelo de interoperabilidade COM


O seguinte código COM interop solicita uma função simples raw_factorial.

Por favor note as funções CoInitialize() CoCreateInstance() e CoUninitialize() e função de solicitação de 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;
}
  

Para leituras adicionais sobre COM Interop, por favor leia a documentação detalhada em Introdução COM Interop e o exemplo de utilização que eu encontrei no blog msdn: Como solicitar código C++ de administrado, e vice-versa (Interop).


2,2. Reverse P/Invoke

A plataforma Invoke, chamada de P/Invoke permite que o .NET solicite qualquer função em qualquer linguagem não gerenciada, desde que sua assinatura seja redeclarada. Isto é conseguido executando um apontador de função nativa do .NET. A utilização é bem descrita em Tutorial da plataforma Invoke.

A utilização básica é para usar o atributo DllImport para marcar a função importada:

// 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();
    }
}
  

A operação reversa pode ser descrita como fornecer um callback delegado gerenciado ao código não gerenciado.

Isto é chamado de Reverse P/Invoke e é conseguido implementando uma função delegada pública em ambiente gerenciado e importando a função chamadora implementada em DLL nativo:

#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);
      }
}
  

O exemplo de código gerenciado é como se segue:

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);
}
  

O ponto principal desta solução é que isto requer o lado gerenciado para iniciar a interação.

Para referências adicionais, por favor leia Gotchas com Reverse Pinvoke (callbacks de código gerenciado e não gerenciado) e PInvoke-Reverse PInvoke e stdcall - cdecl.


2,3. C++ IJW

C++ interop, chamado de It Just Works (IJW) é um recurso específico do C++, fornecido pelas Extensões gerencias para 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);
} 
  

Esta solução pode ser útil para pessoas que desejam usar seus C++ gerenciados em aplicações não gerenciadas. Para referência completa, por favor leia Interoperabilidade em Extensões Gerenciadas para C++ e Usar IJW em C++ gerenciado.


2,4. Classe de empacotador C++/Cli

A implementação de classe de empacotador C++/Cli recebe seu nome por incorporar ou empacotar a classe gerenciada por uma outra classe escrita em modo C++/Cli. O primeiro passo para escrever o DLL empacotador é escrever a classe C++ que empacota os métodos da classe gerenciada original.

A classe empacotadora deve conter um identificador para o objeto .NET usando o modelo gcroot<> e deve delegar todas as solicitações da classe original. A classe empacotadora é compilada para o formato IL (linguagem intermediária) e, portanto, é uma classe gerenciada.

O próximo passo é escrever a classe C++ nativa com diretiva não gerenciada #pragma que empacota a classe IL e delega todas as solicitações com a diretiva __declspec(dllexport). Estas etapas farão DLL C++ nativo que pode ser usado por qualquer aplicação não gerenciada.

Por favor, veja o exemplo abaixo. A primeira etapa é implementar o código C#.

A classe calculadora de exemplo contém dois métodos públicos:

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

A próxima etapa é escrever um empacotador gerenciado que irá delegar todos os métodos da classe calculadora:

#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;
    }
};
  

Por favor, note que a referência à classe calculadora original é armazenada utilizando a instrução gcnew e armazenada como modelo gcroot<>. Todos os métodos podem possuir o mesmo nome que os originais e os parâmetros e valores de retorno são precedidos por __Param e __ReturnVal, respectivamente.

Agora a classe C++ não gerenciada que empacota o C++/Cli e exporta métodos de DLL C++ nativos deve ser implementada.

O arquivo de cabeçalho contém definição de classe com diretiva __declspec(dllexport) e armazena o apontador para as classes empacotadoras.

#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);
};
  

E sua implementação:

#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;
}
  

Um guia passo a passo para fazer esta classe empacotadora é descrito em Ponte .NET a C++.

Uma referência completa para criação de empacotadores está disponível em Misturar .NET e código nativo e para informações gerais sobre declarar identificadores em tipos nativos, por favor leia Como: Declarar identificadores em tipos nativos.


2,5. Exportações não gerenciadas

Esta técnica é totalmente descrita pelo livro Expert .NET 2.0 IL Assembler, que eu recomendo para todos que desejam ler sobre os detalhes do compilador .NET. A ideia principal é expor métodos gerenciados como exportações não gerenciadas de um DLL gerenciado, descompilando módulos já compilados em código IL, utilizando ILDasm, alterando as tabelas VTable e VTableFixup do módulo e recompilando o DLL usando ILAsm.

Esta tarefa pode parecer intimidante, mas o resultado desta operação será a produção de um DLL que pode ser utilizado desde dentro de qualquer aplicação não gerenciada. é preciso lembrar que ela ainda é um conjunto gerenciado, portanto, o .NET framework tem que estar instalado. Um tutorial passo a passo está disponível em Exportar código gerenciado como não gerenciado.

Após descompilar o DLL usando ILDasm, nós obtemos o código-fonte em linguagem IL. Por favor, observe um simples exemplo de código IL com exportação não gerenciada, colada abaixo:

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
}
    

As linhas do código-fonte da IL responsáveis por implementar exportações não gerenciadas são:

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

e

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

A primeira parte é responsável por adicionar a entrada de função na tabela VTableFixup e definir o endereço virtual VT_01 à função. A segunda parte especifica qual VTEntry deve ser usada para esta função e exporta apelido para a função a ser exportada.

Os prós dessa solução são que durante a fase de implementação do DLL, nós não precisamos implementar nenhum código adicional além do DLL C# gerenciado usual e, como afirmado pelo livro, este método abre totalmente o mundo gerenciado com toda a sua segurança e bibliotecas de classe aos clientes não gerenciados

A desvantagem é que obter a linguagem de conjunto .NET não é adequado para todas as pessoas. Eu estava convencido que escreveria a classe de empacotador c++ no lugar, até que eu encontrei o modelo de exportações não gerenciadas por Robert Giesecke: http://sites.google.com/site/robertgiesecke/ que permite utilizar exportações não gerenciadas sem nenhuma necessidade de entrar no código IL.


3. Modelo de exportações não gerenciadas C#

O modelo para projetos de exportações não gerenciadas C# de R.Giesecke usa MSBuild task que automaticamente adiciona os VT-fixups apropriados após a construção, portanto, não há nenhuma necessidade de alterar o código IL. O pacote modelo somente precisa ser baixado como arquivo zip e copiado à pasta ProjectTemplates do Visual Studio.

Após compilar o projeto, o arquivo DLL resultante pode ser perfeitamente importado pelo MetaTrader, eu fornecerei os exemplos nas próximas sessões.


4. Exemplos

Foi uma tarefa bastante desafiadora descobrir como passar variáveis, vetores e estruturas entre o MetaTrader e C# usando o método Marshalling correto e eu acho que as informações fornecidas aqui irão lhe economizar bastante tempo. Todos os exemplos foram compilados no Windows Vista com .NET 4.0 e Visual C# Express 2010. Eu vou também anexar um DLL de exemplo com código MQL5 que invoca funções do C# DLL ao artigo.


4,1. Exemplo 1. Adicionar duas variáveis inteiras, duplas ou flutuantes na função DLL e retornar o resultado ao 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;
        }

    }
}
      

Como você deve ter notado, cada função exportada é precedida pela diretiva DllExport. O primeiro parâmetro descreve o apelido da função exportada e o segundo parâmetro a convenção de solicitação, para o MetaTrader, nós devemos usar o CallingConvention.StdCall.

O código MQL5 que importa e usa as funções exportadas do DLL é direto e não difere-se de nenhum outro DLL escrito em C++ nativo. Primeiramente, é preciso declarar funções importadas dentro do bloco #import, e indicar quais funções do DLL podem ser futuramente usadas a partir do código 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));
     }
  }
//+------------------------------------------------------------------+

Resultado

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. Exemplo 2. Acesso ao vetor unidimensional

        [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];
        }

Para preparar um vetor unidimensional, a diretiva MarshalAs deve passar UnmanagedType.LPArray como o primeiro parâmetro e SizeParamIndex como segundo parâmetro. SizeParamIndex indica qual parâmetro (contando a partir de 0) é o parâmetro contendo o tamanho do vetor.

Nos exemplos acima, i é o tamanho do vetor e idx é o índice do elemento a retornar.

Código MQL5 de exemplo, usando acesso de vetor abaixo:

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

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

Resultado

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. Exemplo 3. Povoar o vetor unidimensional e retorná-lo ao 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;
        }
      

Este exemplo usa dois vetores de entrada para comparar a convenção de parâmetro de entrada. Se elementos alterados devem ser retornados ao MetaTrader (passando por referência), é suficiente colocar atributos [In, Out,] antes do atributo 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]));
      
  }
//+------------------------------------------------------------------+
      

Resultado

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. Exemplo 4. Acesso ao vetor bidimensional

        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;
        }
      

O vetor bidimensional não é tão simples de organizar, mas eu usei um truque - ou seja, passar vetor 2D como unidimensional e acessar os elementos do vetor pela função idx auxiliar.

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

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

Resultado

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. Exemplo 5. Substituir conteúdos de string

  	 [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;
        }
        

Este exemplo é curto, mas me tomou um bom tempo para implementá-lo já que tentei usar parâmetro string usando atributos [In, Out] ou com palavras-chave ref ou out sem sucesso.

A solução é usar StringBuilder ao invés de variável string.

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

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

        

Resultado

	 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;
        }

A estrutura MqlTick é passada como referência, marcada pela palavra-chave ref. A estrutura MqlTick em si, precisa ser precedida pelo atributo [StructLayout (LayoutKind.Sequential, Pack =1)].

O parâmetro pack descreve o alinhamento de dados na estrutura, por favor, leia StructLayoutAttribute.Pack Field para detalhes.

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

Resultado

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
        

Conclusão

Neste artigo eu apresentei diferentes métodos de interação entre o código MQL5 e o código C# gerenciado.

Eu também ofereci vários exemplos de como organizar estruturas MQL5 contra C# e como invocar funções de DLL exporta em scripts MQL5. Eu acredito que forneci exemplos que podem servir como uma base para pesquisas futuras sobre escrever DLLs em código gerenciado.

Este artigo também abre portas para o MetaTrader usar muitas bibliotecas que já estão implementadas em C#. Para referências adicionais, por favor, leia os artigos que estão nos links da seção Referências.


Para testá-lo, por favor, localize os arquivos nas seguintes pastas:

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


Referências

  1. Exportar DLLs .NET com Visual Studio 2005 para serem consumidos por aplicações nativas
  2. Interoperação com código não gerenciado
  3. Introdução ao COM Interop
  4. Modelo do objeto componente (COM)
  5. Exportar de um DLL usando __declspec(dllexport)
  6. Como: Declarar identificadores em tipos nativos
  7. Como solicitar código C++ de administrado, e vice-versa (Interop).
  8. Reverse P/Invoke e exceção
  9. Como solicitar um DLL gerenciado de código Visual C++ nativo no Visual Studio.NET ou no Visual Studio 2005
  10. Tutorial da plataforma Invoke
  11. PInvoke-Reverse PInvoke e__stdcall - __cdecl
  12. Gotchas com Reverse Pinvoke (callbacks de código gerenciado e não gerenciado)
  13. Misturar .NET e código nativo
  14. Exportar código gerenciado como não gerenciado
  15. Entendendo a interoperabilidade COM clássica com aplicações .NET
  16. Extensões gerenciadas para programação C++
  17. Site de Robert Giesecke
  18. Tarefas MSBuild
  19. Common Language Runtime

Traduzido do Inglês pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/en/articles/249

Últimos Comentários | Ir para discussão (1)
Wemerson Guimaraes
Wemerson Guimaraes | 11 set 2016 em 01:49
MetaQuotes Software Corp.:

Novo artigo Expondo código C# para MQL5 usando exportações não gerenciadas foi publicado:

Autor: investeo

Hello investeo, great article!

I've found this article searching on how connect C# DLL on mql5 code... and this helped me a lot! Thanks.

 

Now, i need to get data from a DLL that runs WebSocket connection with a third party server, and need to send the live data that comes from the server to to MQL5,  but i don't found any example of implementation of real time and 2 way communication between MQL5 and a DLL or API.

 

Can you help me? 

Modelo de regressão universal para predição do preço do mercado Modelo de regressão universal para predição do preço do mercado
O preço de mercado é formado pelo estável equilíbrio entre demanda e fornecimento que, por sua vez, depende de uma variedade de fatores econômicos, políticos e psicológicos. As diferenças na natureza também como causas de influência destes fatores dificultam considerar diretamente todos os componentes. Este artigo estabelece uma tentativa de prever o preço de mercado, com base em um modelo de regressão elaborada.
Caminhada aleatória e indicador de tendência Caminhada aleatória e indicador de tendência
A caminhada aleatória parece muito similar com os dados de mercado reais, mas possui alguns recursos significativos. Neste artigo, considerarei as propriedades da Caminhada Aleatória, simulada usando o jogo de cara e coroa. Para estudar as propriedades dos dados, foi desenvolvido o indicador de modismo.
Usando Indicadores MetaTrader 5 com Estrutura de Aprendizado de Máquina ENCOG para Previsão das Séries Temporais Usando Indicadores MetaTrader 5 com Estrutura de Aprendizado de Máquina ENCOG para Previsão das Séries Temporais
Este artigo apresenta a conexão do MetaTrader 5 para ENCOG - Rede neural avançada e estrutura de aprendizado de máquina. Ele contém a descrição e implementação de um simples indicador de rede neural com base em indicadores técnicos padrão e um Expert Advisor baseado em um indicador neural. Todos os códigos fonte, binários compilados, DLLs e uma rede treinada exemplar estão ligados ao artigo.
Implementação de Indicators como classes por exemplos de Zigzag e ATR Implementação de Indicators como classes por exemplos de Zigzag e ATR
O debate sobre uma forma ideal para calcular indicadores é infinito. Onde devemos calcular o indicador - no indicador em si ou dentro da lógica inteira no Expert Advisor que o usa? O artigo descreve uma das variáveis do movimento do código fonte de um indicador personalizado iCustom direto no código de um Expert Advisor ou script com otimização de cálculos e modelagem do valor prev_calculated.