Inclusión de bibliotecas; #import de funciones

Las funciones se importan de módulos MQL5 compilados (archivos *.ex5) y de módulos de bibliotecas dinámicas de Windows (archivos *.dll). El nombre del módulo se especifica en la directiva #import, seguida de las descripciones de los prototipos de las funciones importadas. Un bloque de este tipo debe terminar con otra directiva #import, además, puede ser sin nombre y simplemente cerrar el propio bloque, o se puede especificar el nombre de otra biblioteca en la directiva, y así el siguiente bloque de importación comienza al mismo tiempo. Una serie de bloques de importación debe terminar siempre con una directiva sin nombre de biblioteca.

En su forma más simple, la directiva tiene el siguiente aspecto:

#import "[path] module_name [.extension]"
  function_type function_name([parameter_list]);
  [function_type function_name([parameter_list]);]
   ... 
#import

El nombre del archivo de biblioteca puede especificarse sin la extensión: entonces se asume por defecto la DLL. Se necesita la extensión ex5.

El nombre puede ir precedido de la ruta de ubicación de la biblioteca. Por defecto, si no hay ruta, las bibliotecas se buscan en la carpeta MQL5/Libraries o en la carpeta junto al programa MQL donde está conectada la biblioteca. En caso contrario, se aplican reglas diferentes para buscar las bibliotecas en función de si el tipo es DLL o EX5. Estas normas se tratan en una sección aparte.

He aquí un ejemplo de bloques de importación secuencial de dos bibliotecas:

#import "user32.dll"
   int     MessageBoxW(int hWndstring szTextstring szCaptionint nType); 
   int     SendMessageW(int hWndint Msgint wParamint lParam); 
#import "lib.ex5" 
   double  round(double value); 
#import

Con estas directivas, las funciones importadas se pueden llamar desde el código fuente de la misma manera que las funciones definidas directamente en el propio programa MQL. Todas las cuestiones técnicas relacionadas con la carga de bibliotecas y la redirección de llamadas a módulos de terceros son gestionadas por el entorno de ejecución del programa MQL.

Para que el compilador emita correctamente la llamada a la función importada y organice el paso de parámetros, se requiere una descripción completa: con el tipo de resultado, con todos los parámetros, modificadores y valores por defecto, si están presentes en el código fuente.

Dado que las funciones importadas están fuera del módulo compilado, el compilador no puede comprobar la corrección de los parámetros pasados y los valores de retorno. Cualquier discrepancia entre el formato de los datos esperados y los recibidos dará lugar a un error durante la ejecución del programa, y esto puede manifestarse como una parada crítica del programa, o un comportamiento inesperado.

Si la biblioteca no se pudo cargar o no se encontró la función importada llamada, el programa MQL termina con un mensaje correspondiente en el registro. El programa no podrá ejecutarse hasta que se resuelva el problema: por ejemplo, modificando y recompilando, colocando la biblioteca necesaria en uno de los lugares de la ruta de búsqueda o permitiendo el uso de la DLL (sólo para DLL).

Cuando compartas varias bibliotecas (no importa si son DLL o EX5), recuerde que deben tener nombres diferentes, independientemente de sus directorios de ubicación. Todas las funciones importadas obtienen un ámbito que coincide con el nombre del archivo de la biblioteca, es decir, es una especie de espacio de nombres asignado implícitamente a cada biblioteca incluida.

Las funciones importadas pueden tener cualquier nombre, incluidos los que coinciden con los nombres de las funciones integradas (aunque no se recomienda). Además, es posible importar simultáneamente funciones con los mismos nombres de distintos módulos. En estos casos, la operación permisos contextuales debe aplicarse para determinar a qué función se debe llamar.

Por ejemplo:

#import "kernel32.dll"
   int GetLastError();
#import "lib.ex5" 
   int GetLastError();
#import
  
class Foo
{
public
   int GetLastError() { return(12345); }
   void func() 
   { 
      Print(GetLastError());           // call a class method 
      Print(::GetLastError());         // calling the built-in (global) MQL5 function 
      Print(kernel32::GetLastError()); // function call from kernel32.d 
      Print(lib::GetLastError());      // function call from lib.ex5 
   }
};
   
void OnStart()
{
   Foo foo
   foo.func(); 
}

Veamos un ejemplo sencillo del script LibRandTest.mq5, que utiliza funciones de la librería EX5 creada en la sección anterior.

#include <MQL5Book/LibRand.mqh>

En los parámetros de entrada, puede seleccionar el número de elementos del array de números, los parámetros de distribución, así como el paso del histograma, que calcularemos para asegurarnos de que la distribución corresponde aproximadamente a la ley normal.

input int N = 10000;
input double Mean = 0.0;
input double Sigma = 1.0;
input double HistogramStep = 0.5;
input int RandomSeed = 0;

La inicialización del generador de números aleatorios integrados en MQL5 (distribución uniforme) se realiza por el valor de RandomSeed o, si aquí se deja 0, se elige GetTickCount (nuevo en cada inicio).

Para construir un histograma, utilizamos MapArray y QuickSortStructT (ya hemos trabajado con ellos en las secciones sobre indicadores multidivisa y sobre ordenación de arrays, respectivamente). El mapa acumulará contadores de aciertos de números aleatorios en las celdas del histograma con un paso de HistogramStep.

#include <MQL5Book/MapArray.mqh>
#include <MQL5Book/QuickSortStructT.mqh>

Para mostrar un histograma basado en el mapa, es necesario poder ordenar el mapa en orden clave-valor. Para ello, tuvimos que definir una clase derivada.

#define COMMA ,
   
template<typename K,typename V>
class MyMapArraypublic MapArray<K,V>
{
public:
   void sort()
   {
      SORT_STRUCT(Pair<K COMMA V>, arraykey);
   }
};

Tenga en cuenta que la macro COMMA se convierte en una representación alternativa del carácter coma ',' y se utiliza cuando se llama a otra macro SORT_STRUCT. Si no fuera por esta sustitución, la coma dentro del Par<K,V> sería interpretada por el preprocesador como un separador de parámetros de macro normal, como resultado de lo cual se recibirían 4 parámetros a la entrada de SORT_STRUCT en lugar de los 3 esperados, lo que causaría un error de compilación. El preprocesador no sabe nada acerca de la sintaxis MQL5.

Al principio de OnStart, tras la inicialización del generador, comprobamos la recepción de una única cadena aleatoria y un array de cadenas de diferentes longitudes.

void OnStart()
{
   const uint seed = RandomSeed ? RandomSeed : GetTickCount();
   Print("Random seed: "seed);
   MathSrand(seed);
   
   // call two library functions: StringPatternDigit and RandomString
   Print("Random HEX-string: "RandomString(30StringPatternDigit() + "ABCDEF"));
   Print("Random strings:");
   string text[];
   RandomStrings(text51020);         // 5 lines from 10 to 20 characters long
   ArrayPrint(text);
   ...

A continuación, probamos números aleatorios distribuidos normalmente.

   // call another library function: PseudoNormalArray
   double x[];
   PseudoNormalArray(xNMeanSigma);   // filled array x
   
   Print("Random pseudo-gaussian histogram: ");
   
   // take 'long' as key type, because 'int' has already been used for index access
   MyMapArray<long,intmap;
   
   for(int i = 0i < N; ++i)
   {
 // value x[i] determines the cell of the histogram, where we increase the statistics
      map.inc((long)MathRound(x[i] / HistogramStep));
   }
   map.sort();                             // sort by key (i.e. by value)
   
   int max = 0;                            // searching for maximum for normalization
   for(int i = 0i < map.getSize(); ++i)
   {
      max = fmax(maxmap.getValue(i));
   }
   
   const double scale = fmax(max / 801); // the histogram has a maximum of 80 symbols
   
   for(int i = 0i < map.getSize(); ++i)  // print the histogram
   {
      const int p = (int)MathRound(map.getValue(i) / scale);
      string filler;
      StringInit(fillerp, '*');
      Print(StringFormat("%+.2f (%4d)",
         map.getKey(i) * HistogramStepmap.getValue(i)), " "filler);
   }

He aquí el resultado cuando se ejecuta con la configuración por defecto (temporizador de aleatorización: cada ejecución elegirá un nuevo seed).

Random seed: 8859858

Random HEX-string: E58B125BCCDA67ABAB2F1C6D6EC677

Random strings:

"K4ZOpdIy5yxq4ble2" "NxTrVRl6q5j3Hr2FY" "6qxRdDzjp3WNA8xV" "UlOPYinnGd36" "6OCmde6rvErGB3wG"

Random pseudo-gaussian histogram:

-9.50 ( 2)

-8.50 ( 1)

-8.00 ( 1)

-7.00 ( 1)

-6.50 ( 5)

-6.00 ( 10) *

-5.50 ( 10) *

-5.00 ( 24) *

-4.50 ( 28) **

-4.00 ( 50) ***

-3.50 ( 100) ******

-3.00 ( 195) ***********

-2.50 ( 272) ***************

-2.00 ( 510) ****************************

-1.50 ( 751) ******************************************

-1.00 (1029) *********************************************************

-0.50 (1288) ************************************************************************

+0.00 (1457) *********************************************************************************

+0.50 (1263) **********************************************************************

+1.00 (1060) ***********************************************************

+1.50 ( 772) *******************************************

+2.00 ( 480) ***************************

+2.50 ( 280) ****************

+3.00 ( 172) **********

+3.50 ( 112) ******

+4.00 ( 52) ***

+4.50 ( 43) **

+5.00 ( 10) *

+5.50 ( 8)

+6.00 ( 8)

+6.50 ( 2)

+7.00 ( 3)

+7.50 ( 1)

En esta biblioteca, sólo hemos exportado e importado funciones con tipos integrados. Sin embargo, las interfaces de objetos con estructuras, clases y plantillas son mucho más interesantes y demandadas desde un punto de vista práctico. Hablaremos de los matices de su uso en las bibliotecas en una sección aparte.

Cuando se prueban Asesores Expertos e indicadores en el probador, hay que tener en cuenta un punto importante relacionado con las bibliotecas. Las bibliotecas necesarias para el principal programa MQL probado se determinan automáticamente a partir de las directivas #import. No obstante, si se llama a un indicador personalizado desde el programa principal, al que está conectada alguna biblioteca, entonces es necesario indicar explícitamente en las propiedades del programa que depende indirectamente de una biblioteca concreta. Esto se hace con la directiva
 
#property tester_library«path_library_name.extension».