Creación de bibliotecas ex5; export de funciones

Para describir una biblioteca, añada la directiva #property library al código fuente del módulo principal (compilado) (normalmente, al principio del archivo).

#property library

La especificación de esta directiva en cualquier otro archivo incluido en el proceso de compilación a través de #include no tiene ningún efecto.

La propiedad library informa al compilador de que el archivo ex5 dado es una biblioteca: una marca sobre esto se almacena en el encabezado del archivo ex5.

Una carpeta separada MQL5/Libraries está reservada para las bibliotecas en MetaTrader 5. Puede organizar una jerarquía de carpetas anidadas en ella, al igual que para otros tipos de programas en MQL5.

Las bibliotecas no participan directamente en el manejo de eventos, y por lo tanto el compilador no requiere la presencia de ningún manejador estándar en el código. No obstante, puede llamar a las funciones exportadas de la librería desde los manejadores de eventos del programa MQL al que está conectada la librería.

Para exportar una función de una biblioteca, basta con marcarla con una palabra clave especial export. Este modificador debe colocarse al final del encabezado de la función.

result_type function_id ( [ parameter_type parameter_id
                          [ = default_value] ...] ) export
{
   ...
}

Los parámetros deben ser tipos simples o cadenas, estructuras con campos de dichos tipos o sus arrays. Los punteros y las referencias están permitidos para los tipos de objeto MQL5 (para conocer las restricciones sobre la importación de DLL, consulte la sección correspondiente).

Veamos algunos ejemplos. El parámetro es un número primo:

double Algebraic2(const double xexport
{
   return x / sqrt(1 + x * x); 
}

Los parámetros son un puntero a un objeto y una referencia a un puntero (lo que permite asignar un puntero dentro de la función).

class X
{
public:
   X() { Print(__FUNCSIG__); }
};
void setObject(const X *objexport { ... }
void getObject(X *&objexport { obj = new X(); }

El parámetro es una estructura:

struct Data
{
   int value;
   double data[];
   Data(): value(0) { }
   Data(const int i): value(i) { ArrayResize(datai); }
};
   
void getRefStruct(const int iData &dataexport { ... }

Sólo se pueden exportar funciones, pero no clases o estructuras enteras. Algunas de estas limitaciones pueden evitarse con la ayuda de punteros y referencias, de los que hablaremos con más detalle más adelante.

Las plantillas de funciones no pueden declararse con la palabra clave export y en la directiva #import.

El modificador export indica al compilador que incluya la función en la tabla de funciones exportadas dentro del ejecutable ex5 dado. Gracias a ello, dichas funciones pasan a estar disponibles («visibles») desde otros programas MQL, donde pueden utilizarse tras importarlas con una directiva especial #import.

Todas las funciones que vayan a exportarse deben marcarse con el modificador export. Aunque no es necesario que el programa principal los importe todos, ya que sólo puede importar los necesarios.

Si olvida exportar una función pero la incluye en la directiva de importación del programa MQL principal, al iniciar este último se producirá un error:

cannot find 'function' in 'library.ex5'
unresolved import function call

Un problema similar surgirá si hay discrepancias en la descripción de la función exportada y su prototipo importado. Esto puede ocurrir, por ejemplo, si olvida recompilar una biblioteca o un programa principal después de realizar cambios en la interfaz de programación, que suele describirse en un archivo de encabezado independiente.

La depuración de bibliotecas no es posible, por lo que si es necesario, debe tener un script auxiliar u otro programa MQL que se construye a partir de los códigos fuente de la biblioteca en modo depurador y se puede ejecutar con puntos de interrupción o paso a paso. Por supuesto, esto requerirá emular llamadas a funciones exportadas utilizando algunos datos reales o artificiales.

En el caso de las DLL, la descripción de las funciones exportadas se realiza de forma diferente, dependiendo del lenguaje de programación en el que se hayan creado. Busque más detalles en la documentación de los entornos de desarrollo que haya elegido.

Consideremos el ejemplo de una biblioteca sencilla MQL5/Libraries/MQL5Book/LibRand.mq5, de la que se exportan varias funciones con distintos tipos de parámetros y resultados. La biblioteca está diseñada para generar datos aleatorios:

  • de datos numéricos con una distribución pseudonormal;
  • de cadenas con caracteres aleatorios de los conjuntos dados (puede ser útil para contraseñas).

En concreto, puede obtener un número aleatorio utilizando la función PseudoNormalValue, en la que el valor esperado y la varianza se establecen como parámetros.

double PseudoNormalValue(const double mean = 0.0const double sigma = 1.0,
   const bool rooted = falseexport
{
   // use ready-made sqrt for mass generation in a cycle in PseudoNormalArray
   const double s = !rooted ? sqrt(sigma) : sigma
   const double r = (rand() - 16383.5) / 16384.0// [-1,+1] excluding borders
   const double x = -(log(1 / ((r + 1) / 2) - 1) * s) / M_PI * M_E + mean;
   return x;
}

La función PseudoNormalArray rellena el array con valores aleatorios en una cantidad determinada (n) y con la distribución requerida.

bool PseudoNormalArray(double &array[], const int n,
   const double mean = 0.0const double sigma = 1.0export
{
   bool success = true;
   const double s = sqrt(fabs(sigma)); // passing ready sqrt when calling PseudoNormalValue
   ArrayResize(arrayn);
   for(int i = 0i < n; ++i)
   {
      array[i] = PseudoNormalValue(meanstrue);
      success = success && MathIsValidNumber(array[i]);
   }
   return success;
}

Para generar una cadena aleatoria, escribimos la función RandomString, que «selecciona» del conjunto de caracteres suministrado (pattern) una cantidad determinada (length) de caracteres arbitrarios. Cuando el parámetro pattern está en blanco (por defecto), se asume un conjunto completo de letras y números. Para obtenerlo se utilizan las funciones auxiliares StringPatternAlpha y StringPatternDigit; estas funciones también son exportables (no aparecen en el libro, véase el código fuente).

string RandomString(const int lengthstring pattern = NULLexport
{
   if(StringLen(pattern) == 0)
   {
      pattern = StringPatternAlpha() + StringPatternDigit();
   }
   const int size = StringLen(pattern);
   string result = "";
   for(int i = 0i < length; ++i)
   {
      result += ShortToString(pattern[rand() % size]);
   }
   return result;
}

En general, para trabajar con una biblioteca, es necesario publicar un archivo de encabezado que describa todo lo que debe estar disponible en ella desde el exterior (y los detalles de la implementación interna pueden y deben ocultarse). En nuestro caso, ese archivo se llama MQL5Book/LibRand.mqh. En concreto, describe tipos definidos por el usuario (en nuestro caso, la enumeración STRING_PATTERN) y prototipos de funciones.

Aunque aún no conocemos la sintaxis exacta del bloque #import, esto no debería afectar a la claridad de las declaraciones que contiene: los encabezados de las funciones exportadas se repiten aquí pero sin la palabra clave export.

enum STRING_PATTERN
{
   STRING_PATTERN_LOWERCASE = 1// lowercase letters only
   STRING_PATTERN_UPPERCASE = 2// capital letters only
   STRING_PATTERN_MIXEDCASE = 3  // both registers
};
   
#import "MQL5Book/LibRand.ex5"
string StringPatternAlpha(const STRING_PATTERN _case = STRING_PATTERN_MIXEDCASE);
string StringPatternDigit();
string RandomString(const int lengthstring pattern = NULL);
void RandomStrings(string &array[], const int nconst int minlength,
   const int maxlengthstring pattern = NULL);
void PseudoNormalDefaultMean(const double mean = 0.0);
void PseudoNormalDefaultSigma(const double sigma = 1.0);
double PseudoNormalDefaultValue();
double PseudoNormalValue(const double mean = 0.0const double sigma = 1.0,
   const bool rooted = false);
bool PseudoNormalArray(double &array[], const int n,
   const double mean = 0.0const double sigma = 1.0);
#import

Escribiremos un script de prueba que utilice esta biblioteca en la próxima sección, después de estudiar la directiva #import.