
Guia para escrever uma DLL para MQL5 em Delphi
Introdução
O mecanismo de escrever uma DLL será considerado usando um exemplo do meio de desenvolvimento de 2009 do Delphi. Esta versão foi selecionada devido ao fato de que em MQL5, todas as linhas são armazenadas em formato Unicode. Em versões mais antigas do Delphi, o módulo SysUtils está faltando a função para trabalhar com linhas em formato Unicode.
Se você, por qualquer motivo, estiver usando uma versão anterior (Delphi de 2007 e mais antigas), então você tem que trabalhar com linhas em formato ANSI, e, a fim de trocar dados com MetaTrader 5, você precisa produzir conversões diretas e reversas para Unicode . Para evitar tais complicações, eu recomendo desenvolver o módulo DLL para MQL5 em um ambiente não mais antigo que o Delphi de 2009. A versão de teste de 30 dias de familiarizamento para Delphi pode ser baixada a partir do site oficial http://embarcadero.com.
1. Criando o projeto
Para criar o projeto, precisamos executar o Assistente DLL, escolhendo o item de menu: 'Arquivo -> Novo -> Outro ... -> Assistente DLL ' Como mostrado na figura 1.
Figura 1. Criando um projeto usando o Assistente de DLL
Como resultado, criaremos um projeto de DLL vazio, como mostrado na figura 2.
Figura 2. Um projeto de DLL vazio
A essência de um longo comentário no título do projeto é para lembrá-lo de uma conexão correta e o uso de um gerenciador de memória quando se trabalha com a memória alocada dinamicamente. Isto será discutido em mais detalhe na seção que trata de strings.
Antes de você começar a preencher a nova DLL com funções, é importante configurar o projeto.
Abra a janela de propriedades do projeto a partir do menu: 'Projeto -> Opções ...' ou através do teclado pelo comando 'Shift + Ctrl + F11' .
A fim de simplificar o processo de depuração, é necessário que o arquivo da DLL seja criado diretamente na pasta '.. \\MQL5\\Bibliotecas' Trade Terminal MetaTrtader5. Para fazer isso, na guia DelphiCompiler, definir a propriedade de diretório de saída de valor correspondente, como mostrado na figura 3. Isto eliminará a necessidade de copiar constantemente o arquivo, gerado pela DLL, a partir da pasta do projeto para a pasta do terminal.
Figura 3. Especifique a pasta para armazenar o arquivo DLL resultante
A fim de evitar a junção de módulos BPL durante a montagem, sem a presença do que está na pasta de sistema do Windows, se a DLL criada não será trabalhada no futuro, é importante verificar se os Pacotes de guia, o sinalizador Construir com pacotes de tempo de execução não está selecionado, como mostrado na figura 4.
Figura 4. Exclusão dos módulos de BPL da montagem
Depois de concluir a configuração do projeto, salve-o em sua pasta de trabalho, o nome especificado do projeto é o nome futuro do arquivo DLL compilado.
2. Adicionando os procedimentos e funções
Vamos considerar a situação geral ao escrever os procedimentos e funções exportadas no módulo DLL, em um exemplo de um procedimento sem parâmetros. O anúncio e a transferência de parâmetros serão discutidos na próxima seção.
Uma pequena digressão. Ao escrever os procedimentos e funções da linguagem Object Pascal, o programador tem a oportunidade de usar as funções incorporadas da biblioteca do Delphi, para não mencionar os inúmeros componentes desenvolvidos para esse ambiente. Por exemplo, para o desempenho da mesma ação, como trazer uma exibição de uma janela modal com uma mensagem de texto, você pode usar uma função da API - MessageBox, bem como um procedimento da biblioteca VCL - ShowMessage.
A segunda opção leva a incluir o módulo diálogos e dá vantagem para trabalhar de forma fácil com caixas de diálogo padrão do Windows. No entanto, o tamanho do arquivo DLL resultante irá aumentar em cerca aproximadamente 500 KB. Portanto, se você preferir criar arquivos DLL pequenos, que não ocupam muito espaço em disco, eu não aconselharia usar os componentes VCL.
Um projeto de teste da amostra com explicações segue abaixo:
library dll_mql5; uses Windows, // necessary for the work of the MessageBox function Dialogs; // necessary for the work of the ShowMessage procedure from the Dialogs module var Buffer: PWideChar; //------------------------------------------------------+ procedure MsgBox(); stdcall; // //to avoid errors, use the stdcall (or cdecl) for the exported functions //------------------------------------------------------+ begin {1} MessageBox(0,'Hello World!','terminal', MB_OK); {2} ShowMessage('Hello World!');// alternative to the MessageBox function end; //----------------------------------------------------------+ exports //----------------------------------------------------------+ {A} MsgBox, {B} MsgBox name 'MessageBox';// renaming of the exported function //----------------------------------------------------------+ procedure DLLEntryPoint(dwReason: DWord); // event handler //----------------------------------------------------------+ begin case dwReason of DLL_PROCESS_ATTACH: // DLL attached to the process; // allocate memory Buffer:=AllocMem(BUFFER_SIZE); DLL_PROCESS_DETACH: // DLL detached from the process; // release memory FreeMem(Buffer); end; end; //----------------------------------------------------------+ begin DllProc := @DLLEntryPoint; //Assign event handler DLLEntryPoint(DLL_PROCESS_ATTACH); end. //----------------------------------------------------------+
Todas as funções exportadas devem ser anunciadas com o modificador stdcall ou cdecl. Se nenhum destes modificadores é especificado, o Delphi usa o acordo fastcall proposto, que utiliza principalmente registradores da CPU para a passagem de parâmetros, ao invés da memória de armazenamento. Será, sem dúvida, levar um erro do trabalho com os parâmetros transmitidos, na fase de convocar as funções externas da DLL.
A seção "inicial final" contém um código de inicialização padrão de um manipulador de eventos da DLL. O procedimento de retorno de chamada DLLEntryPoint será chamado ao conectar e desconectar do processo que o chamou. Estes eventos podem ser utilizados para a gestão de memória dinâmica correta, atribuída para as nossas próprias necessidades, como mostrado no exemplo.
Chamada para MQL5:
#import "dll_mql5.dll" void MsgBox(void); void MessageBox(void); #import // Call of procedure MsgBox(); // If the names of the function coincide with the names of MQL5 standard library function // use the DLL name when calling the function dll_mql5::MessageBox();
3. Passando parâmetros para a função e valores retornados
Antes de considerar a passagem de parâmetros, vamos analisar a tabela de correspondência de dados para MQL5 e Object Pascal.
Tipo de dado para MQL5 |
Tipo de dado para Object Pascal (Delphi) |
Nota |
---|---|---|
tipo | ShortInt | |
uchar |
Byte | |
curto |
SmallInt | |
ushort |
Word |
|
int |
Inteiro |
|
uint | Cardeal | |
long | Int64 | |
ulong |
UInt64 |
|
float | Single | |
double | Double |
|
ushort (символ) | WideChar | |
sequência | PWideChar | |
bool | Boolean | |
datetime | TDateTime | conversão necessária (veja abaixo nesta seção) |
cor | TColor |
Tabela 1. A tabela de correspondência de dados para MQL5 e Object Pascal
Como você pode ver na tabela, para todos os outros tipos de dados de data e hora, Delphi tem um análogo completo.
Agora, considere duas formas de passagem de parâmetros: por valor e por referência. O formato da declaração de parâmetros para ambas as versões é dada na tabela 2.
Método de transferência de parâmetros |
Anúncio para MQL5 |
Anúncio para Delphi |
Nota |
---|---|---|---|
por valor |
função interna (int a); | função (a:Inteira): Inteira; | correto |
função interna (int a); |
função (var a: Inteira): Inteira; |
Erro: violação de acesso escrita ao | |
por link |
função interna (int &a); |
função (var a: Inteira): Inteira; |
corretas, porém as linhas são transmitidas sem var modificador! |
função interna (int &a); | função (a: Inteira): Inteira; | erro: em vez do valor da variável, contém o endereço da célula de memória |
Tabela 2. Métodos de passagem de parâmetros
Agora vamos considerar os exemplos de trabalhar com parâmetros passados e os valores devolvidos.
3.1 Conversão de data e hora
Primeiro, vamos lidar com o tipo de data e hora que você deseja converter, porque o tipo datetime corresponde a TDateTime somente em seu tamanho, mas não em formato. Para facilidade de transformação, use Int64 como o tipo de dado recebido, em vez de TDateTime. Abaixo estão as funções para transformação direta e inversa:
uses SysUtils, // used for the constant UnixDateDelta DateUtils; // used for the function IncSecon, DateTimeToUnix //----------------------------------------------------------+ Function MQL5_Time_To_TDateTime(dt: Int64): TDateTime; //----------------------------------------------------------+ begin Result:= IncSecond(UnixDateDelta, dt); end; //----------------------------------------------------------+ Function TDateTime_To_MQL5_Time(dt: TDateTime):Int64; //----------------------------------------------------------+ begin Result:= DateTimeToUnix(dt); end;
3.2 Trabalhando com tipos de dados simples
Vamos examinar como transferir os tipos de dados simples, a exemplo dos mais comumente usados, int, double, bool, e datetime.
Chamada para Object Pascal:
//----------------------------------------------------------+ function SetParam(var i: Integer; d: Double; const b: Boolean; var dt: Int64): PWideChar; stdcall; //----------------------------------------------------------+ begin if (b) then d:=0; // the value of the variable d is not changed in the calling program i:= 10; // assign a new value for i dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt Result:= 'value of variables i and dt are changed'; end;
Chamada para MQL5:
#import "dll_mql5.dll" string SetParam(int &i, double d, bool b, datetime &dt); #import // initialization of variables int i = 5; double d = 2.8; bool b = true; datetime dt= D'05.05.2010 08:31:27'; // calling the function s=SetParam(i,d,b,dt); // output of results printf("%s i=%s d=%s b=%s dt=%s",s,IntegerToString(i),DoubleToString(d),b?"true":"false",TimeToString(dt));Resultado:
The values of variables i and dt are changed i = 10 d = 2.80000000 b = true dt = 2009.05 . 05 08 : 42
O valor não foi alterado desde que foi transferido por valor. Para evitar a ocorrência de alterações do valor de uma variável, no interior de uma função de DLL, um modificador constante foi usado na variável b.
3.3 Trabalhando com estruturas e matrizes
Em várias ocasiões, é útil para agrupar os parâmetros de diferentes tipos nas estruturas, e os parâmetros de um tipo nas matrizes. Considere trabalhar com todos os parâmetros transferido da função SetParam, a partir do exemplo anterior, integrando-os numa estrutura.
Chamada para Object Pascal:
type StructData = packed record i: Integer; d: Double; b: Boolean; dt: Int64; end; //----------------------------------------------------------+ function SetStruct(var data: StructData): PWideChar; stdcall; //----------------------------------------------------------+ begin if (data.b) then data.d:=0; data.i:= 10; // assign a new value for i data.dt:= TDateTime_To_MQL5_Time(Now()); // assign the current time for dt Result:= 'The values of variables i, d and dt are changed'; end;
Chamada para MQL5:
struct STRUCT_DATA { int i; double d; bool b; datetime dt; }; #import "dll_mql5.dll" string SetStruct(STRUCT_DATA &data); #import STRUCT_DATA data; data.i = 5; data.d = 2.8; data.b = true; data.dt = D'05.05.2010 08:31:27'; s = SetStruct(data); printf("%s i=%s d=%s b=%s dt=%s", s, IntegerToString(data.i),DoubleToString(data.d), data.b?"true":"false",TimeToString(data.dt));Resultado:
The values of variables i, d and dt are changed i = 10 d = 0.00000000 b = true dt = 2009.05 . 05 12 : 19
é necessário observar uma diferença significativa a partir do resultado do exemplo anterior. Desde que a estrutura é transferida através de uma referência, torna-se impossível proteger os campos selecionados de serem editados na função chamada. A tarefa de monitoramento da integridade dos dados, neste caso, encontra-se totalmente no programador.
Considere trabalhar com matrizes, em um exemplo de preenchimento da matriz com a sequência de números de Fibonacci:
Chamada para Object Pascal:
//----------------------------------------------------------+ function SetArray(var arr: IntegerArray; const len: Cardinal): PWideChar; stdcall; //----------------------------------------------------------+ var i:Integer; begin Result:='Fibonacci numbers:'; if (len < 3) then exit; arr[0]:= 0; arr[1]:= 1; for i := 2 to len-1 do arr[i]:= arr[i-1] + arr[i-2]; end;
Chamada para MQL5:
#import "dll_mql5.dll" string SetArray(int &arr[],int len); #import int arr[12]; int len = ArraySize(arr); // passing the array by reference to be filled by data in DLL s = SetArray(arr,len); //output of result for(int i=0; i<len; i++) s = s + " " + IntegerToString(arr[i]); printf(s);Resultado:
Fibonacci numbers 0 1 1 2 3 5 8 13 21 34 55 89
3.4 Trabalhando com strings
Vamos retornar para o gerenciador de memória. Na DLL é possível operar o seu próprio gerenciador de memória. No entanto, devido a DLL e o programa que lhe chama, serem muitas vezes escritos em diferentes linguagens de programação, e seus próprios gerenciadores de memória ao invés da memória do sistema geral serem usados no trabalho, todo o peso da responsabilidade pela veracidade da operação da memória na junção DLL e na aplicação, repousa sobre o programador.
Para trabalhar com a memória, é importante cumprir com a regra de ouro, o que soa algo como: "Aqueles que alocam a memória, devem ser os únicos a libertá-la." Ou seja, você não deve tentar liberar a memória no programa de código mql 5, alocados na DLL, e vice-versa.
Vamos considerar um exemplo de gerenciamento de memória em um estilo de chamadas de função API do Windows. No nosso caso, o programa mql5 atribui memória ao tampão, e um ponteiro para o buffer, passado à DLL como PWideChar , e a DLL apenas preenche este tampão com o valor desejado, como mostrado no exemplo a seguir:
Chamada para Object Pascal:
//----------------------------------------------------------+ procedure SetString(const str:PWideChar) stdcall; //----------------------------------------------------------+ begin StrCat(str,'Current time:'); strCat(str, PWideChar(TimeToStr(Now))); end;
Chamada para MQL5:
#import "dll_mql5.dll" void SetString(string &a); #import // the string must be initialized before the use // the size of the buffer must be initially larger or equal to the string length StringInit(s,255,0); //passing the buffer reference to DLL SetString(s); // output of result printf(s);
Resultado:
Current Time: 11: 48:51
A memória para o buffer de linha pode ser selecionada na DLL de várias formas, como pode ser visto no exemplo a seguir:
Chamada para Object Pascal:
//----------------------------------------------------------+ function GetStringBuffer():PWideChar; stdcall; //----------------------------------------------------------+ var StrLocal: WideString; begin // working through the dynamically allocated memory buffer StrPCopy(Buffer, WideFormat('Current date and time: %s', [DateTimeToStr(Now)])); // working through the global varialble of WideString type StrGlobal:=WideFormat('Current time: %s', [TimeToStr(Time)]); // working through the local varialble of WideString type StrLocal:= WideFormat('Current data: %s', [DateToStr(Date)]); {A} Result := Buffer; {B} Result := PWideChar(StrGlobal); // it's equal to the following Result := @StrGlobal[1]; {С} Result := 'Return of the line stored in the code section'; // pointer to the memory, that can be released when exit from the function {D} Result := @StrLocal[1]; end;Chamada para MQL5:
#import "dll_mql5.dll" string GetStringBuffer(void); #import printf(GetStringBuffer());
Resultado:
Current Date: 19.05.2010
O que é significativo é que todas as quatro opções trabalham. Nas duas primeiras opções, o trabalho com a linha é feito através de uma memória alocada globalmente.
Na opção A, a memória é alocada de forma independente, e na opção B, o trabalho com gerenciamento de memória é assumido pelo gerenciador de memória.
Na opção C, a constante de linha não é armazenada na memória, mas no segmento de código, de modo que o gestor de memória não atribuir a memória dinâmica para o seu armazenamento. A opção D é um erro audaz na programação, porque a memória alocada para a variável local pode ser liberada imediatamente após sair da função.
E, embora o gerenciador de memória não libera essa memória instantaneamente, e não há tempo para ele se encher de lixo, eu recomendo excluir a última opção de uso.
3.5 Usando os parâmetros propostos
Vamos falar sobre o uso de parâmetros opcionais. Eles são interessantes porque seus valores não precisam ser especificados ao convocar procedimentos e funções. Enquanto isso, eles devem ser descritos, exclusivamente depois de todos os parâmetros obrigatórios, na declaração de procedimentos e funções, como se mostra no exemplo a seguir:
Chamada para Object Pascal:
//----------------------------------------------------------+ function SetOptional(var a:Integer; b:Integer=0):PWideChar; stdcall; //----------------------------------------------------------+ begin if (b=0) then Result:='Call with default parameters' else Result:='Call without default parameters'; end;Chamada para MQL5:
#import "dll_mql5.dll" string SetOptional(int &a, int b=0); #import i = 1; s = SetOptional(i); // second parameter is optional printf(s);
Resultado:
Chamada com os parâmetros padrão
Para facilitar a depuração, o código a partir dos exemplos acima é organizado como script, e está localizado no arquivo Testing_DLL.mq5.
4. Possíveis erros na fase de concepção
Erro: O carregamento da DLL não é permitido.
Solução: Ir para as configurações do MetaTrader 5 através do menu de "Ferramentas-Opções" e permitir a importação da função da DLL, como mostrado na figura 5.
Figura 5. Permissão para importar as funções da DLL
Erro: Não foi possível encontrar 'nome da função' em 'nome da DLL'.
Solução: Verifique se a função de retorno é especificada na seção de exportações do projeto DLL. Se for, você deve verificar a concordância total dos nomes das funções na DLL e no programa MQL5, considerando que ele é o caráter sensível!
Erro: Violação de acesso escrita ao [endereço de memória]
Solução: Você precisa verificar a exatidão da descrição dos parâmetros de transmissão (ver tabela 2). Como normalmente esse erro está associado ao processamento de linhas, é importante seguir as recomendações para trabalhar com linhas, estabelecidos no parágrafo 3.4 deste artigo.
5. Exemplo de código DLL
Como um exemplo visual do uso da DLL, considerar os cálculos dos parâmetros do canal de regressão, que consiste em três linhas. Para verificar a regularidade da construção do canal, vamos usar o objeto incorporado da "regressão do canal". O cálculo da linha de aproximação para LS (método dos quadrados mínimos) é feito a partir do site http://alglib.sources.ru/, onde existe uma coleção de algoritmos de processamento de dados. O código de algoritmos é apresentado em várias linguagens de programação, incluindo Delphi.
Para o cálculo dos coeficientes de a e b, pela linha de aproximação y = a + b * x, use o procedimento descrito no arquivo LRLine linreg.pas.
procedure LRLine ( const XY: TReal2DArray; / / Two-dimensional array of real numbers for X and Y coordinates N : AlglibInteger; // number of points var Info : AlglibInteger; // conversion status var A: Double; / / Coefficients of the approximating line var B: Double);
Para o cálculo dos parâmetros do canal, utilize a função CalcLRChannel.
Chamada para Object Pascal:
//----------------------------------------------------------+ function CalcLRChannel(var rates: DoubleArray; const len: Integer; var A, B, max: Double):Integer; stdcall; //----------------------------------------------------------+ var arr: TReal2DArray; info: Integer; value: Double; begin SetLength(arr,len,2); // copy the data to a two-dimensional array for info:= 0 to len - 1 do begin arr[info,0]:= rates[info,0]; arr[info,1]:= rates[info,1]; end; // calculation of linear regression coefficients LRLine(arr, len, info, A, B); // find the maximal deviation from the approximation line found // and determine the width of the channel max:= rates[0,1] - A; for info := 1 to len - 1 do begin value:= Abs(rates[info,1]- (A + B*info)); if (value > max) then max := value; end; Result:=0; end;
Chamada para MQL5:
#import "dll_mql5.dll" int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max); #import double arr[][2], //data array for processing in the ALGLIB format a, b, // Coefficients of the approximating line max; // maximum deviation from the approximating line is equal to half the width of the channel int len = period; //number of points for calculation ArrayResize(arr,len); // copying the history to a two-dimensional array int j=0; for(int i=rates_total-1; i>=rates_total-len; i--) { arr[j][0] = j; arr[j][1] = close[i]; j++; } // calculation of channel parameters CalcLRChannel(arr,len,a,b,max);
O código indicador, que usa a função CalcLRChannel para os cálculos, está localizado no arquivo LR_Channel.mq5 a seguir:
//+------------------------------------------------------------------+ //| LR_Channel.mq5 | //| Copyright 2009, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2009, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" #property indicator_chart_window #include #include #import "dll_mql5.dll" int CalcLRChannel(double &rates[][2],int len,double &A,double &B,double &max); #import input int period=75; CChart *chart; CChartObjectChannel *line_up,*line_dn,*line_md; double arr[][2]; //+------------------------------------------------------------------+ int OnInit() //+------------------------------------------------------------------+ { if((chart=new CChart)==NULL) {printf("Chart not created"); return(false);} chart.Attach(); if(chart.ChartId()==0) {printf("Chart not opened");return(false);} if((line_up=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} if((line_dn=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} if((line_md=new CChartObjectChannel)==NULL) {printf("Channel not created"); return(false);} return(0); } //+------------------------------------------------------------------+ int OnCalculate(const int rates_total, const int prev_calculated, const datetime &time[], const double &open[], const double &high[], const double &low[], const double &close[], const long &tick_volume[], const long &volume[], const int &spread[]) //+------------------------------------------------------------------+ { double a,b,max; static double save_max; int len=period; ArrayResize(arr,len); // copying of history to a two-dimensional array int j=0; for(int i=rates_total-1; i>=rates_total-len; i--) { arr[j][0] = j; arr[j][1] = close[i]; j++; } // procedure of calculating the channel parameters CalcLRChannel(arr,len,a,b,max); // if the width of the channel has changed if(max!=save_max) { save_max=max; // Delete the channel line_md.Delete(); line_up.Delete(); line_dn.Delete(); // Creating a channel with new coordinates line_md.Create(chart.ChartId(),"LR_Md_Line",0, time[rates_total-1], a, time[rates_total-len], a+b*(len-1) ); line_up.Create(chart.ChartId(),"LR_Up_Line",0, time[rates_total-1], a+max, time[rates_total-len], a+b*(len-1)+max); line_dn.Create(chart.ChartId(),"LR_Dn_Line",0, time[rates_total-1], a-max, time[rates_total-len], a+b*(len-1)-max); // assigning the color of channel lines line_up.Color(RoyalBlue); line_dn.Color(RoyalBlue); line_md.Color(RoyalBlue); // assigning the line width line_up.Width(2); line_dn.Width(2); line_md.Width(2); } return(len); } //+------------------------------------------------------------------+ void OnDeinit(const int reason) //+------------------------------------------------------------------+ { // Deleting the created objects chart.Detach(); delete line_dn; delete line_up; delete line_md; delete chart; }
O resultado do trabalho do indicador é a criação de um canal de regressão azul, como mostrado na figura 6. Para verificar a regularidade da construção do canal, o gráfico mostra um "Canal de Regressão", a partir do arsenal do grupo MetaTrader 5 de instrumentos de análise técnica, marcada em vermelho.
Como pode ser visto na figura, as linhas centrais do canal se unem. Entretanto, há uma pequena diferença na largura do canal (alguns pontos), que são devido às diferentes abordagens no seu cálculo.
Figura 6. Comparação dos canais de regressão
Conclusão
Este artigo descreve as características de escrever uma DLL, usando uma plataforma de desenvolvimento de aplicações Delphi.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/96





- Aplicativos de negociação gratuitos
- 8 000+ sinais para cópia
- Notícias econômicas para análise dos mercados financeiros
Você concorda com a política do site e com os termos de uso
Novo artigo Guia para escrever uma DLL para MQL5 em Delphi foi publicado:
Autor: Andrey Voytenko
Se você tiver trabalhando com versões anteriores do delphi, como eu, o D7, pode utilizar funções de conversão ou utilizar a biblioteca da jcl la tem unicode para vc usar, é um pacote de componente free é so entrar no sourcefoge.net e buscar por jcl baixar e instalar