Como trocar dados: um DLL para o MQL5 em 10 minutos
Por falar nisso, não há muitos desenvolvedores que lembram exatamente como escrever uma simples biblioteca DLL e quais são os recursos dos diferentes sistemas de ligação.
Usando vários exemplos, vou tentar mostrar todo o processo da criação de um simples DLL em 10 minutos, bem como discutir alguns detalhes técnicos da nossa implementação de ligação. Vamos usar o Visual Studio 2005/2008; suas versões Express são gratuitas e podem ser baixadas no site da Microsoft.
1. Criação de um projeto de DLL em C++ no Visual Studio 2005/2008
Execute o Win32 Application Wizard usando o menu 'File -> New', selecione o tipo de projeto como 'Visual C++', escolha o template 'Win32 Console Application' e defina o nome do projeto (por exemplo, 'MQL5DLLSamples'). Escolha um diretório raiz para armazenar 'Location' de projeto, ao invés do padrão oferecido, desative a caixa de opção de 'Create directory for solution' e clique em' OK':
Fig. 1. Win32 Application Wizard, criação de projeto DLL
No próximo passo, pressione "Next" para ir para a página de configurações:
Fig. 2. Win32 Application Wizard, configurações do projeto
Na página final, selecione o tipo de aplicativo 'DLL', deixando outros campos vazios como estão e clique em 'Finish'. Não defina a opção 'Export symbols', se não quiser remover o código de demonstração adicionado automaticamente:
Fig. 3. Win32 Application Wizard, Configuração da aplicação
Como resultado você terá um projeto vazio:
Fig. 4. O projeto DLL vazio preparado pelo Assistente
Para simplificar o teste, é melhor especificar nas opções 'Output Directory' a saída de arquivos DLL diretamente para as '...\MQL5\Libraries' do terminal de cliente - mais adiante, economizará mais tempo:
Fig. 5. Diretório de saída DLL
2. Preparando para adicionar funções
Adicionar macro '_DLLAPI' no final do arquivo stdafx.h, para que você possa descrever funções exportadas de um modo conveniente fácil:
//+------------------------------------------------------------------+ //| MQL5 DLL Samples | //| Copyright 2001-2010, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #pragma once #include "targetver.h" #define WIN32_LEAN_AND_MEAN // Exclude rarely-used stuff from Windows headers #include //--- #define _DLLAPI extern "C" __declspec(dllexport) //+------------------------------------------------------------------+
As funções importadas DLL exigidas no MQL5 devem ter a convenção de chamada stdcall e cdecl. Embora stdcall e cdecl difiram na forma de extração de parâmetros de um pilha, o ambiente runtime do MQL5 pode usar de forma segura ambas versões devido ao wrapper especial das chamadas do DLL.
O compilador C++ utiliza __cdecl de chamada por padrão, mas eu recomendei especificar explicitamente o modo __stdcall para funções exportadas.
Uma função de exportação escrita corretamente deve ter o seguinte formato:
_DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2) { return(0); }
Em um programa MQL5, a função deve ser definida e chamada como a seguir:
#import "MQL5DLLSamples.dll" int fnCalculateSpeed(int &res1,double &res2); #import //--- call speed=fnCalculateSpeed(res_int,res_double);
Após a compilação do projeto, este stdcall será exibido na tabela de exportação como _fnCalculateSpeed@8, onde o compilador adiciona um underline e número de bytes, transmitidos através do empilhamento. Tal decoração permite controlar melhor a segurança das chamadas de funções DLL devido ao fato de que o chamador sabe exatamente quantos (mas não do tipo!) de dados que devem ser colocados no empilhamento.
Se o tamanho final do bloco de parâmetros tiver um erro na descrição de importação da função DLL, a função não será chamada, e a nova mensagem aparecerá no jornal: 'Não foi possível encontrar'CrashTestParametersStdCall' em 'MQL5DLLSamples.dll'. Em tais casos, é necessário verificar cuidadosamente todos os parâmetros tanto no protótipo da função como na fonte DLL.
A busca pela descrição simplificada sem decoração é usada para compatibilidade no caso da tabela de exportação não conter o nome completo da função. Nomes como fnCalculateSpeed são criados se as funções forem definidas no formato__cdecl._DLLAPI int fnCalculateSpeed(int &res1,double &res2) { return(0); }
3. Métodos para passar parâmetros e trocar dados
Vamos considerar diversas variantes de parâmetros passados:
- Recebimento e passagem de variáveis simples
O caso das variáveis simples é fácil - elas podem ser passadas pelo valor ou pela referência usando &._DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2) { int res_int=0; double res_double=0.0; int start=GetTickCount(); //--- simple math calculations for(int i=0;i<=10000000;i++) { res_int+=i*i; res_int++; res_double+=i*i; res_double++; } //--- set calculation results res1=res_int; res2=res_double; //--- return calculation time return(GetTickCount()-start); }
Chamada do MQL5:#import "MQL5DLLSamples.dll" int fnCalculateSpeed(int &res1,double &res2); #import //--- calling the function for calculations int speed=0; int res_int=0; double res_double=0.0; speed=fnCalculateSpeed(res_int,res_double); Print("Time ",speed," msec, int: ",res_int," double: ",res_double);
A saída é:MQL5DLL Test (GBPUSD,M1) 19:56:42 Time 16 msec, int: -752584127 double: 17247836076609
- Recebimento e passagem de um banco de dados com preenchimento de elementos
Ao contrário de outros programas MQL5, a passagem de banco de dados é realizada através da referência direta para o buffer de dados sem acesso a informações exclusivas sobre dimensões e tamanhos. é por isso que a dimensão e tamanho do banco de dados devem ser passados separadamente.
_DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size) { //--- check for the input parameters if(arr==NULL || arr_size<1) return; //--- fill array with values for(int i=0;i<arr_size;i++) arr[i]=i; }
Chamada do MQL5:#import "MQL5DLLSamples.dll" void fnFillArray(int &arr[],int arr_size); #import //--- call for the array filling int arr[]; string result="Array: "; ArrayResize(arr,10); fnFillArray(arr,ArraySize(arr)); for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" "; Print(result);
A saída é:MQL5DLL Test (GBPUSD,M1) 20:31:12 Array: 0 1 2 3 4 5 6 7 8 9
- Passagem e modificação de cadeias
As cadeias unicode são passadas usando referências diretas a seus endereços de buffer sem passar qualquer informação adicional._DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to) { wchar_t *cp; //--- parameters check if(text==NULL || from==NULL || to==NULL) return; if(wcslen(from)!=wcslen(to)) return; //--- search for substring if((cp=wcsstr(text,from))==NULL) return; //--- replace it memcpy(cp,to,wcslen(to)*sizeof(wchar_t)); }
Chamada do MQL5:#import "MQL5DLLSamples.dll" void fnReplaceString(string text,string from,string to); #import //--- modify the string string text="A quick brown fox jumps over the lazy dog"; fnReplaceString(text,"fox","cat"); Print("Replace: ",text);
O resultado é:MQL5DLL Test (GBPUSD,M1) 19:56:42 Replace: Uma raposa pequena e marrom pula por cima de um cão preguiçoso
O resultado é que a linha não tinha mudado! Este é um erro comum de iniciantes quando enviam cópias de objetos (uma cadeia é um objeto), ao invés de referir-se a eles. A cópia da string 'text' foi automaticamente criada que foi modificada no DLL, e depois foi removida automaticamente se afetar a original.
Para remediar essa situação, é necessário passar uma string por referência. Para fazê-lo, basta modificar o bloco de importação, adicionando & ao parâmetro "texto":#import "MQL5DLLSamples.dll" void fnReplaceString(string &text,string from,string to); #import
Após a compilação e início vamos obter o resultado correto:MQL5DLL Test (GBPUSD,M1) 19:58:31 Replace: Um gato pequeno e marrom pula por cima de um cão preguiçoso
4. Captura de exceções em funções DLL
Para evitar que o terminal trave, cada chamada DLL é protegida automaticamente pelo Unhandled Exception Wrapping. Este mecanismo permite proteger da maioria dos erros padrão (erros de acesso à memória, divisão por zero, etc.).
Para ver como o mecanismo funciona, vamos criar o seguinte código:
_DLLAPI void __stdcall fnCrashTest(int *arr) { //--- wait for receipt of a zero reference to call the exception *arr=0; }
e chamá-lo do terminal de cliente:
#import "MQL5DLLSamples.dll" void fnCrashTest(int arr); #import //--- call for the crash (the execution environment will catch the exception and prevent the client terminal crush) fnCrashTest(NULL); Print("You won't see this text!"); //---
Como resultado, ele vai tentar escrever para o endereço zero e gerar uma exceção. O terminal do cliente vai capturá-lo, registrá-lo ao lançamento e continuar o seu trabalho:
MQL5DLL Test (GBPUSD,M1) 20:31:12 Access violation write to 0x00000000
5. Pacote de chamada DLL e perda de velocidade sobre chamadas
Como já descrito acima, todas as chamadas de funções DLL são envoltas em um pacote especial, a fim de garantir a segurança. Esta ligação mascara o código básico, substitui o empilhamento, suporta acordos stdcall/cdecl e monitora exceções dentro das funções chamadas.
Este volume de trabalhos não leva a um atraso significativo da função chamada.
6. A construção final
Vamos recolher todos os exemplos acima de funções DLL em arquivo 'MQL5DLLSamples.cpp' e exemplos MQL5 no script 'MQL5DLL Test.mq5. O projeto final para o Visual Studio 2008 e o script em MQL5 estão anexos ao artigo.
//+------------------------------------------------------------------+ //| MQL5 DLL Samples | //| Copyright 2001-2010, MetaQuotes Software Corp. | //| https://www.metaquotes.net | //+------------------------------------------------------------------+ #include "stdafx.h" //+------------------------------------------------------------------+ //| Passing and receving of simple variables | //+------------------------------------------------------------------+ _DLLAPI int __stdcall fnCalculateSpeed(int &res1,double &res2) { int res_int=0; double res_double=0.0; int start=GetTickCount(); //--- simple math calculations for(int i=0;i<=10000000;i++) { res_int+=i*i; res_int++; res_double+=i*i; res_double++; } //--- set calculation results res1=res_int; res2=res_double; //--- return calculation time return(GetTickCount()-start); } //+------------------------------------------------------------------+ //| Filling the array with values | //+------------------------------------------------------------------+ _DLLAPI void __stdcall fnFillArray(int *arr,const int arr_size) { //--- check input variables if(arr==NULL || arr_size<1) return; //--- fill array with values for(int i=0;i<arr_size;i++) arr[i]=i; } //+------------------------------------------------------------------+ //| The substring replacement of the text string | //| the string is passed as direct reference to the string content | //+------------------------------------------------------------------+ _DLLAPI void fnReplaceString(wchar_t *text,wchar_t *from,wchar_t *to) { wchar_t *cp; //--- parameters checking if(text==NULL || from==NULL || to==NULL) return; if(wcslen(from)!=wcslen(to)) return; //--- search for substring if((cp=wcsstr(text,from))==NULL) return; //--- replace it memcpy(cp,to,wcslen(to)*sizeof(wchar_t)); } //+------------------------------------------------------------------+ //| Call for the crush | //+------------------------------------------------------------------+ _DLLAPI void __stdcall fnCrashTest(int *arr) { //--- wait for receipt of a zero reference to call the exception *arr=0; } //+------------------------------------------------------------------+
//+------------------------------------------------------------------+ //| MQL5DLL Test.mq5 | //| Copyright 2010, MetaQuotes Software Corp. | //| https://www.mql5.com | //+------------------------------------------------------------------+ #property copyright "2010, MetaQuotes Software Corp." #property link "https://www.mql5.com" #property version "1.00" //--- #import "MQL5DLLSamples.dll" int fnCalculateSpeed(int &res1,double &res2); void fnFillArray(int &arr[],int arr_size); void fnReplaceString(string text,string from,string to); void fnCrashTest(int arr); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { //--- calling the function for calculations int speed=0; int res_int=0; double res_double=0.0; speed=fnCalculateSpeed(res_int,res_double); Print("Time ",speed," msec, int: ",res_int," double: ",res_double); //--- call for the array filling int arr[]; string result="Array: "; ArrayResize(arr,10); fnFillArray(arr,ArraySize(arr)); for(int i=0;i<ArraySize(arr);i++) result=result+IntegerToString(arr[i])+" "; Print(result); //--- modifying the string string text="A quick brown fox jumps over the lazy dog"; fnReplaceString(text,"fox","cat"); Print("Replace: ",text); //--- and finally call a crash //--- (the execution environment will catch the exception and prevent the client terminal crush) fnCrashTest(NULL); Print("You won't see this text!"); //--- } //+------------------------------------------------------------------+
Obrigado pelo seu interesse! Estou pronto para responder a quaisquer perguntas.
Traduzido do russo pela MetaQuotes Ltd.
Artigo original: https://www.mql5.com/ru/articles/18
- 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