English Русский 中文 Deutsch 日本語 Português 한국어 Français Italiano Türkçe
Cómo deshacerse del lastre de las DLL caseras

Cómo deshacerse del lastre de las DLL caseras

MetaTrader 5Ejemplos | 19 marzo 2014, 09:30
936 0
---
---


¿Aún hace sus propias DLL?
¡Entonces le ayudaremos!

Introducción

Siempre llega el momento en que al programador de MQL5 no le basta con la funcional del lenguaje del que dispone, y se ve obligado a recurrir a instrumentos adicionales. Por ejemplo, debe trabajar con bases de datos y utilizar sockets de comunicación, o se ve obligado a trabajar con funciones del sistema operativo. Para ampliar las posibilidades de sus programas MQL5, debe recurrir a diversos API. Pero, a causa de ciertos factores, el programador no puede recurrir a las funciones necesarias directamente desde MQL5, ya que no conoce:

  • Cómo modificar un tipo complejo de datos (por ejemplo, la estructura) hasta una función API;
  • Cómo trabajar con el indicador que devuelve la función API.

Por eso, se ve obligado a usar otro lenguaje de programación y a crear una DLL intermedia para trabajar con el funcional requerido. Y aunque en MQL5 se dispone de un mecanismo de representación de diversos tipos de datos con ayuda de estructuras y de transmisión de los mismos a API , por desgracia, el MQL5 no responde a la cuestión de cómo sacar los datos del indicador adoptado.

En este artículo vamos a poner punto final a esta cuestión, mostrando mecanismos sencillos de transmisión de tipos complejos de datos y cómo trabajar con los índices devueltos.


Contenido

1. La memoria lo es todo

  • Cómo obtener los índices
  • Cómo copiar áreas de la memoria

2. Cómo transferir las estructuras a funciones API

  • Transformación de las estructuras usando MQL5
  • Ejemplo de transferencia de una estructura para sockets

3. Cómo trabajar con los índices de las funciones API

  • Ejemplos para Memory Mapping File,
  • Ejemplo para MySQL

4. Lectura de las strings de C de las funciones API



1. La memoria lo es todo

Como ya sabemos, cualquier variable (incluidas las variables de los tipos complejos de datos) tiene una dirección de memoria concreta desde donde las variables son almacenadas en la memoria. Esta dirección de memoria es un número entero de cuatro bytes (del tipo int), cuyo valor es la dirección del primer byte de esta variable.

Ya que todo se ha concretado, significa que podemos trabajar con esta parte de la memoria. En la biblioteca del lenguaje С (msvcrt.dll) tenemos la función memcpy. Su misión es el elemento extraviado que unifica MQL5 y las diferentes bibliotecas API, y que proporciona al programador grandes posibilidades.


Recurramos a los conocimientos de nuestros ancestros

La función memcpy copia el número especificado de bytes de un buffer a otro y retorna un índice al buffer receptor.

void *memcpy(void *dst, const void *src, int cnt);
dst - índice del buffer receptor
src - índice del buffer fuente
cnt - número de bytes para la copia

En otras palabras, el área de la memoria con un tamaño cnt bytes que comienza desde la dirección src, se copia en al área de la memoria que comienza desde la dirección dst.

Los datos que se encuentran en la dirección src pueden ser de varios tipos. Puede ser una variable de un sólo byte char, de un número de bytes ocho double, matriz, de cualquier estructura, y con cualquier tamaño de memoria. Es decir, que conociendo la dirección y el tamaño, podemos llevar a cabo la transferencia de datos de un área de la memoria a otra.

Cómo funciona

En el esquema número 1 se muestran los tamaños comparados de algunos tipos de datos.

Dimensiones de algunos tipos de datos en MQL5


El cometido de la función memcpy es copiar los datos de un área de la memoria a otra.
En el esquema número 2 tenemos un ejemplo de cómo copiar cuatro bytes.

Ejemplo de copiado de 4 bytes con ayuda de la función memcpy

En el lenguaje MQL5 tendrá el siguiente aspecto.

Ejemplo 1. Uso de memcpy
#import "msvcrt.dll"
  int memcpy(int &dst, int &src, int cnt);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  int dst, src=4, cnt=sizeof(int);
  int adr=memcpy(dst, src, cnt);
  Print("Valor dst="+string(dst)+"   Dirección dst="+string(adr));
}

Es importante recordar que en las áreas designadas por dst y src, se pueden encontrar tipos de datos absolutamente distintos (del mismo tamaño de cnt). Por ejemplo, el índice src puede hacer referencia a la variable double (cnt=8 bytes) y el dst – a la matriz de tamaño análogo char[8] o int[2].

Para la memoria es indiferente qué idea tiene el programador en un momento dado. Sea una matriz char[8] o simplemente una variable long o una estructura { int a1; int a2; }.

Esto significa que se pueden cambiar entre sí, no sólo datos de un mismo tipo, sino también de tipos diferentes. Por ejemplo, se puede transferir una matriz de cinco bytes {int i; char c;} a una estructura o viciversa. Es esta conexión la que abre la posibilidad de trabajar directamente con funciones API.

Vamos a ver por orden las posibilidades de uso de memcpy.


Cómo obtener los índices

En el ejemplo 1 mostramos que la función memcpy retorna la dirección de la variable dst.

Esta propiedad se puede usar para obtener la dirección de cualquier variable (incluido de las matrices y otros tipos complejos). Para ello basta con indicar la misma variable en calidad de parámetros de fuente y receptor. En cnt es posible transferir 0, ya que no es obligatorio efectuar la copia real.

Por ejemplo, obtener la dirección de la variable double y la matriz short:

Ejemplo 2. Cómo obtener los índices de una variable
#import "msvcrt.dll"
  int memcpy(short &dst[], short &src[], int cnt);
  int memcpy(double &dst,  double &src, int cnt);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  short src[5];
  //--- obtenemos la dirección de la matriz src (es decir, la dirección de su primer elemento)
  int adr=memcpy(src, src, 0);
  double var;
  //--- obtenemos la dirección de la variable var
  adr=memcpy(var, var, 0); 
}

La dirección obtenida se puede transferir a la función API requerida o en calidad de parámetro de estructura, así como en calidad de parámetro de la misma función memcpy .


Copia de matrices

Como ya sabemos, una matriz es una porción concreta de memoria. El tamaño de dicha porción depende del tipo y número de sus elementos. Por ejemplo, si los elemento de la matriz son de tipo short y su número es de 10, entonces una matriz así ocupará en la memoria 20 bytes (ya que el tamaño short es de 2 bytes).

Pero estos 20 bytes también se representan como matrices que constan de 20 char o de 5 int. Sea como fuere, ocupan en la memoria los mismos 20 bytes.

Para copiar matrices es necesario:

  • Seleccionar para la memoria dst la cantidad necesaria de elementos (no inferior a los bytes cnt resultantes);
  • Especificar en cnt la cantidad de bytes que se debe copiar desde src.
Ejemplo 3. Copia de matrices
#import "msvcrt.dll"
  int memcpy(double &dst[],  double &src[], int cnt);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  double src[5];
  //--- ¡¡¡Calculando el número de bytes!!!
  int cnt=sizeof(double)*ArraySize(src);
  double dst[]; 
  ArrayResize(dst, 5);
  //--- la matriz ha sido copiada desde src a dst
   memcpy(dst, src, cnt); 
}



2. Cómo transferir las estructuras a funciones API

Supongamos que necesitamos transferir a una función API el índice de una estructura completada. El lenguaje MQL5 nos limita en lo que respecta a la transmisión de estructuras. Ya hemos dicho al comienzo del artículo que la representación de la memoria puede ser diversa. Esto significa que la estructura requerida se puede copiar en al tipo de datos que sostiene MQL5. En gereral, la matriz es el tipo que suele ir bien paras las estructuras. Por eso, en primer lugar deberemos obtener la matriz de la estructura, y después transferir la matriz a la función API.

En el apartado documentación se describe el tipo de copiado de memoria con utilización de estructuras. Dado que no se pueden transferir estructuras en calidad de parámetros, no se podrá utilizar la función memcpy. La copia de estructuras es el único método de trabajo posible.

En el esquema 3 se muestra la representación de una estructura de 5 variables de varios tipos y su análogo en forma de matriz char.

Representación de una estructura de 5 variables de varios tipos y su análogo en forma de matriz char[]

Ejemplo 4. Copia de estructuras con los medios de MQL5
struct str1
{
  double d; // 8 bytes
  long l;   // 8 bytes
  int i[3]; // 3*4=12 bytes
};
struct str2
{
  uchar c[8+8+12]; // tamaño de la estructura str1
};
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  str1 src; 
  src.d=-1;
  src.l=20;
  //--- completando los parámetros de esteructura
  ArrayInitialize(src.i, 0); 
  str2 dst;
  //--- convirtiendo la estructura en una matriz de bytes
  dst=src; 
}

Con este método tan simple hemos sido capaces de copiar la estructura en una matriz de bytes.

Para que el ejemplo sea más práctico, echemos un vistazo a la función de creación de un socket.

int connect(SOCKET s, const struct sockaddr *name, int namelen);

El parámetro más problemático de esta función es el segundo, dado que acepta el índice de la estructura. Pero ya sabemos lo que hay que hacer. Así que, manos a la obra.

1. Vamos a escribir una función connect para importación con un método permitido por MQL5:

int connect(int s, uchar &name[], int namelen);

2. Vamos a ver la estructura requerida en la documentación:

struct sockaddr_in
{
  short   sin_family;
  u_short sin_port;
  in_addr sin_addr; // estructura adicional de 8 bytes
  char sin_zero[8];
};

3. Creamos una estructura con una matriz de tamaño análogo:

struct ref_sockaddr_in
{
  uchar c[2+2+8+8];
};

4. Después de rellenar la estructura sockaddr_in requerida, la transferimos a una matriz de bytes y la transmitimos en calidad de parámetro connect.

Más abajo tenemos el fragmento de código elaborado según estos puntos.

Ejemplo 5. Invocación del socket del cliente al servidor
#import "Ws2_32.dll"
  ushort htons(ushort hostshort);
  ulong inet_addr(char &cp[]);
  int connect(int s, char &name[], int namelen);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  //--- tras inicializar el socket, llevamos a cabo la conexión con el host

  char ch[];
  StringToCharArray("127.0.0.1", ch);
  //--- preparamos la estructura
  sockaddr_in addrin;
  addrin.sin_family=AF_INET;
  addrin.sin_addr=inet_addr(ch);
  addrin.sin_port=htons(1000);
  //--- copiamos la estructura en una matriz
  ref_sockaddr_in ref=addrin; 
  //--- conectamos con el host
  res=connect(asock, ref.c, sizeof(addrin)); 

  //--- trabajo posterior con el socket
}

Como se puede ver, para trabajar con el socket, no es necesario hacer una DLL propia. Las estructuras se transmiten directamente a API.


3. Cómo trabajar con los índices de las funciones API

En la mayoría de los casos, las funciones API retornan un índice de los datos: o de la estructura o de la matriz. Resulta imposible extraer los datos con los medios de que dispone MQL5, pero a nuestra ayuda acude la función memcpy.

Ejemplo de trabajo con matrices de memoria de Memory Mapping File (MMF)



Al trabajar con MMF se usa una función que retorna un índice de una matriz de memoria seleccionada.

int MapViewOfFile(int hFile, int DesiredAccess, int OffsetHigh, int OffsetLow, int NumOfBytesToMap);

La lectura de los datos de esta matriz se efectúa mediante la copia del número de bytes necesarios con ayuda de la función memcpy.
Para Escribir los datos en la matriz, se usa de manera análoga memcpy.

Ejemplo 6. Escritura y lectura de los datos de la memoria de MMF
#import "kernel32.dll"
  int OpenFileMappingW(int dwDesiredAccess, int bInheritHandle,  string lpName);
  int MapViewOfFile(int hFileMappingObject, int dwDesiredAccess, 
                      int dwFileOffsetHigh, int dwFileOffsetLow, int dwNumberOfBytesToMap);
  int UnmapViewOfFile(int lpBaseAddress);
  int CloseHandle(int hObject);
#import "msvcrt.dll"
  int memcpy(uchar &Destination[], int Source, int Length);
  int memcpy(int Destination, int &Source, int Length);
  int memcpy(int Destination, uchar &Source[], int Length);
#import

#define FILE_MAP_ALL_ACCESS   0x000F001F

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  //--- abriendo objeto de la memoria
  int hmem=OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, "Local\\file");
  //--- obteniendo índice de la memoria
  int view=MapViewOfFile(hmem, FILE_MAP_ALL_ACCESS, 0, 0, 0); 
  //--- leyendo los primeros 10 bytes de la memoria
  uchar src[10];
  memcpy(src, view, 10);
  int num=10;
  //--- grabando el número int de 4 bytes en el principio de la memoria
  memcpy(view, num, 4);
  //--- cerrando la vista
  UnmapViewOfFile(view); 
  //--- cerrando el objeto
  CloseHandle(hmem); 
}

Como podemos ver, no hay problemas con el trabajo con los índices de la matriz de memoria. Y lo que es más importante, no necesitamos crear una DLL propia adicional.




Ejemplo de trabajo con estructuras retornadas para MySQL

Uno de los problemas más urgentes a la hora de trabajar con MySQL ha sido ontener datos de este. La función mysql_fetch_row retorna la matriz de las líneas. Cada línea es una matriz de campos. Resulta entonces que dicha función retorna el índice de un índice. Nuestra misión es extraer todos estos datos del índice retornado.

La tarea se complica un tanto porque los campos son diversos tipos de datos, datos binarios incluidos. Esto significa que será imposible representarlos como una matriz de string . Para obtener información sobre la líneas y las dimensiones de los campos, tenemos las funciones mysql_num_rows, mysql_num_fields, mysql_fetch_lengths.

En el esquema 4 podemos ver la estructura de representación del resultado en la memoria.
Las direcciones de los principios de las tres líneas son reunidas en la matriz. Y la dirección del comienzo de esta matriz (en el ejemplo es = 94) es precisamente lo que retornará la función mysql_fetch_row.

Estructura de representación del resultado a una solicitud en la memoria

Más abajo podemos ver un ejemplo de código para la obtención de datos mediante una solicitud a la base.

Ejemplo 7. Obtención de datos de MySQL
#import "libmysql.dll"
  int mysql_real_query(int mysql, uchar &query[], int length);
  int mysql_store_result(int mysql);
  int mysql_field_count(int mysql);
  uint mysql_num_rows(int result);
  int mysql_num_fields(int result);
  int mysql_fetch_lengths(int result);
  int mysql_fetch_row(int result);
#import 
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  //--- ... se ha iniciado de manera preliminar la base de datos mysql
  //--- solicitud de obtención de todas las líneas del recuadro table
  string query="SELECT * FROM table"; 
  uchar aquery[]; 
  StringToCharArray(query, aquery);

  //--- enviando solicitud
  err=mysql_real_query(mysql, aquery, StringLen(query)); 
  int result=mysql_store_result(mysql);

  //--- en caso de que contenga líneas
  if (result>0) 
  {
    ulong num_rows=mysql_num_rows(result);
    int num_fields=mysql_num_fields(result);    

    //--- obteniendo el primer índice de línea
    int r=0, row_ptr=mysql_fetch_row(result);
    while(row_ptr>0)
    {

       //--- obteniendo el índice de la longitud de las columnas de la línea actual
      int len_ptr=mysql_fetch_lengths(result); 
      int lens[]; 
       ArrayResize(lens, num_fields);
      //--- obteniendo las dimensiones de los campos de la línea
      memcpy(lens, len_ptr, num_fields*sizeof(int));
      //--- obteniendo los campos de datos   
      int field_ptr[];
      ArrayResize(field_ptr, num_fields);
      ArrayInitialize(field_ptr, 0);

      //--- obteniendo índices de los campos
      memcpy(field_ptr, row_ptr, num_fields*sizeof(int)); 
      for (int f=0; f<num_fields; f++)
      {
        ArrayResize(byte, lens[f]);
        ArrayInitialize(byte, 0);
         //--- copiamos el campo en la matriz de byte
        if (field_ptr[f]>0 && lens[f]>0) memcpy(byte, field_ptr[f], lens[f]);
      }
      r++;
      //--- obtenemos el índice al índice de la línea siguiente
      row_ptr=mysql_fetch_row(result); 
    }
  }
}



4. Lectura de las strings de C de las funciones API

Ciertas funciones API retornan un índice a una línea, pero no nos informan de la longitud de dicha línea. En esta situación tenemos entre manos líneas que terminan en cero. El final de la línea viene determinado precisamente por ese cero. Por la tanto se puede determinar su tamaño.

Representación de las strings de C en la memoria

En la biblioteca C (msvcrt.dll) ya existe una función que copia el contenido de las strings de C desde el índice adecuado a otra línea. Además, ella misma se encarga de determinar el tamaño de la línea de origen. Como receptor será mejor que usemos una matriz de bytes, ya que los APIs con frecuencia retornan líneas multibyte, en lugar de Unicode.

strcpy - copia las string de C

char *strcpy(char *dst, const char *src);
dst - índice de la línea de destino
src - índice de la string de C de fuente

En realidad, se trata de un caso especial de la función memcpy. Ya que el sistema detiene la copia al encontrar un cero en la línea. Esta función deberá usarse siempre cuando se trabaje con estos índices.

Por ejemplo, hay varias funciones en API de MySQL, que retornan índices de una línea. Y obtener datos de ellas usando strcpy es una tarea trivial.

Ejemplo 8. Obteniendo líneas de los índices
#import "libmysql.dll"
  int mysql_init(int mysql);
  int mysql_real_connect(int mysql, uchar &host[], uchar &user[], uchar &password[], 
                            uchar &DB[], uint port, uchar &socket[], int clientflag);
  int mysql_get_client_info();
  int mysql_get_host_info(int mysql);
  int mysql_get_server_info(int mysql);
  int mysql_character_set_name(int mysql);
  int mysql_stat(int mysql);
#import "msvcrt.dll"
  int strcpy(uchar &dst[], int src);
#import
//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
{
  uchar byte[];
  ArrayResize(byte, 300);

  int ptr;
  string st;
  //--- índice de una línea
  ptr=mysql_get_client_info();

  if (ptr>0) strcpy(byte, ptr);
  Print("client_info="+CharArrayToString(byte));
  //--- inicializando la base
  int mysql=mysql_init(mysql);

  //--- transfiriendo las líneas a matrices de byte
  uchar ahost[]; 
  StringToCharArray("localhost", ahost);
  uchar auser[];
  StringToCharArray("root", auser);
  uchar apwd[];
  StringToCharArray("", apwd);
  uchar adb[];
  StringToCharArray("some_db", adb);
  uchar asocket[];
  StringToCharArray("", asocket);
  //--- conectando con la base
  int rez=mysql_real_connect(mysql, ahost, auser, apwd, adb, port, asocket, 0);
  //--- comprobando el estado de la conexión y de la base
  ptr=mysql_get_host_info(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_host_info="+CharArrayToString(byte));
  ptr=mysql_get_server_info(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_server_info="+CharArrayToString(byte));
  ptr=mysql_character_set_name(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_character_set_name="+CharArrayToString(byte));
  ptr=mysql_stat(mysql);
  if (ptr>0) strcpy(byte, ptr);
  Print("mysql_stat="+CharArrayToString(byte));
}


Conclusión

De esta forma, el uso de los tres mecanismos básicos de trabajo con la memoria (copia de estructuras, obtención de índices y sus datos sobre memcpy y la obtención de líneas strcpy) cubre virtualmente todas las tareas cuando trabajamos con varias funciones API.

Advertencia. Trabajar con memcpy y strcpy puede ser inseguro si para el buffer receptor no se ha reservado volumen de datos suficiente. Por eso, le recomendamos que preste atención a los volúmenes reservados para la recepción de datos.


Traducción del ruso hecha por MetaQuotes Ltd.
Artículo original: https://www.mql5.com/ru/articles/364

Depuración de programas en MQL5 Depuración de programas en MQL5
Este artículo va dirigido a los programadores que ya conocen el lenguaje, pero que aún no han asimilado suficiententemente bien el desarrollo de programas. El artículo nos descubrirá métodos prácticos para depurar programas, es el fruto de la experiencia combinada, no sólo mía, sino también de muchos de los programadores de cuya experiencia he aprendido.
Cómo reducir el gasto de memoria en los indicadores auxiliares Cómo reducir el gasto de memoria en los indicadores auxiliares
Si el indicador implica en sus cálculos los valores de muchos otros indicadores, este tipo de sistema gastará mucha memoria. En el artículo veremos varias maneras de reducir el gasto de memoria al usar los indicadores auxiliares. La memoria ahorrada le permitirá aumentar el número de parejas de divisas, de indicadores y estrategias usadas de manera simultánea en el terminal, incrementando así la fiabilidad de su portfolio comercial. De este modo, esta pequeña gestión de los recursos de su computadora es capaz de convertirse en recursos materiales para su propio uso.
MQL5 Cloud Network: ¿Aún sigue calculando? MQL5 Cloud Network: ¿Aún sigue calculando?
Ya ha pasado casi un año desde el lanzamiento de la red de cálculos en la nube MQL5 Cloud Network. Este acontecimiento, representa una revolución que marca una nueva era en el comercio algorítmico, ya que ahora cualquier trader, con sólo cliquear un par de veces puede tener a su disposición cientos y miles de núcleos de cálculo para optimizar sus estrategia comercial.
Cómo copiar el trading desde MetaTrader 5 a MetaTrader 4 Cómo copiar el trading desde MetaTrader 5 a MetaTrader 4
¿Se puede tradear hoy en día en una cuenta real utilizando MetaTrader 5? ¿Cómo organizar este trading? Se aporta la base teórica de estas preguntas, así como los códigos con la ayuda de los cuales se realiza el copiado de las transacciones del terminal MetaTrader 5 a MetaTrader 4. Este artículo será útil tanto para los desarrolladores de los Asesores Expertos, como para los traders que operan en el mercado.