Cómo escribir una biblioteca DLL para MQL5 en 10 minutos (Parte II): Escribiendo en el entorno de Visual Studio 2017

20 junio 2019, 13:50
Andrei Novichkov
0
611

Introducción

Este artículo es la continuación del artículo escrito anteriormente sobre la creación de una DLL a través de Visual Studio 2005/2008. El artículo inicial «básico» de ningún modo perdió su actualidad, y todos los interesados en este asunto deben leerlo sí o sí. Pero ya pasó bastante tiempo desde aquel entonces, y ahora la versión Visual Studio 2017 es de la actualidad, disponiendo de una interfaz ligeramente modificada, mientras que la propia plataforma MetaTrader 5 tampoco estaba sin desarrollo. Obviamente, existe una necesidad de actualizar los datos, analizar nuevas posibilidades y ajustar as antiguas. Eso es lo que haremos ahora, avanzando desde la creación de un proyecto DLL en Visual Studio 2017 hasta la conexión de la DLL al terminal y el trabajo con ella.

El artículo está destinado para los desarrolladores principiantes que desean aprender a diseñar y conectar las bibliotecas escritas en C++ con el terminal.

¿Cuál es el sentido de todo eso?

Existe una opinión entre los desarrolladores de que no es necesario conectar ninguna biblioteca al terminal, de que simplemente no existen tareas que requieren tal conexión, de que todo puede ser hecho por medio de las herramientas de MQL. Hasta cierto punto, esta opinión es verdadera. De hecho, hay pocas tareas que requieren la conexión de las bibliotecas. Pues sí, muchas de estas tareas pueden ser resueltas usando las herramientas de MQL, y muchas veces vemos los ejemplos de eso. Además, al aplicar una biblioteca, es necesario tener en cuenta que ahora el Asesor Experto (EA) o el indicador que va a usar esta biblioteca estará operacional solamente si ella está disponible. Si el desarrollador quiere transferir esta herramienta a los terceros, habrá que transferir dos archivos: la propia herramienta y la biblioteca usada por esta herramienta. Eso puede resultar bastante inconveniente, y a veces, será simplemente imposible. Hay otro reverso de la medalla, es decir, las bibliotecas pueden ser inseguras y contener un código destructivo.

A pesar de lo arriba mencionado, se puede observar también las ventajas en la aplicación de las bibliotecas, que superan definitivamente las desventajas. Por ejemplo:

  • Solamente al usar las bibliotecas de los terceros, es posible resolver las tareas que MQL es incapaz de resolver, por ejemplo, enviar los emails, adjuntar un archivo a los mensajes, escribir en Skype, etc.
  • Ejecutar las tareas que pueden ser ejecutadas usando las herramientas de MQL, pero de una forma más rápida y eficiente, por ejemplo, el análisis sintáctico (parsing) de las páginas HTML, trabajo con las expresiones regulares.

Está claro que si el desarrollador quiere aprender a cumplir con semejantes tareas complicadas, tendrá que dominar la creación, conexión y el trabajo con las bibliotecas a nivel necesario.

Ahora, una vez considerados todos los pros y contras del uso de las bibliotecas en nuestros proyectos, empezaremos a realizar el proceso de la creación de una DLL en el entorno Visual Studio 2017 paso a paso.

Creando una biblioteca DLL simple

Este proceso ya fue realizado en el artículo inicial. Aquí vamos a repetirlo considerando las modificaciones acumuladas.

En el entorno de Visual Studio 2017, seleccionamos File -> New -> Project. En la parte izquierda de la ventana que aparece, abrimos la lista de Visual C++ y seleccionamos Windows Desktop, y en la parte media, seleccionamos la línea Windows Desktop Wizard. En la parte inferior hay varios campos de texto donde se puede cambiar el nombre (se recomienda definir su propio y razonable) y la ubicación del proyecto (es mejor dejarlo como sugerido). Todo está listo, pulsamos el botón "ОК" y vamos a la siguiente ventana:


Aquí, es necesario seleccionar Dynamic Link Library (.dll) en la lista desplegable y marcar la casilla "Export Symbols". En realidad, marcar esta casilla es opcional, pero es preferible para los desarrolladores principiantes. En este caso, un código de demostración será añadido a los archivos del proyecto (puede estudiar y eliminarlo después, o incluir comentarios). Hacemos clic en "ОК" para crear los archivos del proyecto que podemos editar después. No obstante, es pronto para hacerlo, vamos a aclararnos con las configuraciones del proyecto. Primero, hay que tener en la mente que MetaTrader 5 trabaja sólo con las bibliotecas de 64 bits. Si intentamos de conectar una de 32 bits, recibiremos los siguientes mensajes:

'E:\...\MQL5\Libraries\Project2.dll' is not 64-bit version
Cannot load 'E:\MetaTrader 5\MQL5\Libraries\Project2.dll' [193]

Por consiguiente, será imposible trabajar así.

Los mismo se refiere a MetaTrader 4, pero al revés, o sea, son necesarias las bibliotecas de 32 bits y es imposible conectar las de 64 bits. Vale la pena recordarlo para evitar un trabajo innecesario.

Ahora, procedemos a las configuraciones del proyecto. Seleccionamos "Name Properties..." en el menú "Project", donde "Name" es el nombre del proyecto elegido por el desarrollador en la fase del diseño. Como resultado, obtenemos una ventana con una gran variedad de diferentes configuraciones. Lo primero que hay que hacer, es activar el soporte de Unicode. En la parte izquierda de la ventana, seleccionamos el elemento "General", y en la derecha, la línea con el encabezado en la primera columna: "Character Set". Entonces, la lista desplegable en la que hace falta seleccionar "Use Unicode Character Set" estará disponible en la segunda columna. En algunas ocasiones, se puede prescindir del soporte de Unicode, pero de eso hablaremos más tarde.

Copiar una biblioteca a la carpeta "Library" del terminal es otra modificación muy útil (pero no es necesaria) de las propiedades del proyecto. En el artículo inicial, para eso, se recomendaba alterar el parámetro "Output Directory" que se encuentra en la misma ventana del elemento "General" del proyecto. En este Visual Studio 2017, eso no es necesario. Hay que dejar este parámetro inalterado y prestar atención en el elemento desplegable "Build Events" en la ventana izquierda, y seleccionar su subelemento "Post Build Events". En la primera columna de la ventana derecha, aparece el parámetro "Command Line" que, al ser seleccionado, proporciona el acceso a la lista desplegable en la segunda columna, que puede ser editada. La lista debe contener una serie de acciones que serán ejecutadas por Visual Studio 2017 tras la construcción de la biblioteca. Añadimos la siguiente línea a esta lista:

xcopy "$(TargetDir)$(TargetFileName)" "E:\...\MQL5\Libraries\" /s /i /y

Aquí, en vez de puntos suspensivos, se pone la ruta completa hacia la carpeta correspondiente del terminal. Ahora, si la construcción de la librería ha concluido con éxito, será copiada al lugar especificado. En este caso, todos los archivos en "Output Directory" permanecerán en su lugar, lo que puede ser importante si el desarrollador trabaja, por ejemplo, con los sistemas del control de las versiones.

Ahora, procedemos a la última, y la más importante, etapa de la configuración del proyecto. Imagine que la biblioteca ya está diseñada y contiene una función que puede ser usada por el terminal. Que tenga el siguiente prototipo:

int fnExport(wchar_t* t);
En el script del terminal, puede llamar a esta función de la siguiente manera:
#import "Project2.dll"
int fnExport(string str);
#import

Sin embargo, al intentar hacer eso, recibimos el siguiente mensaje del error:

¿Qué hacemos en esta situación? Nótese que Visual Studio 2017 ha generado una macro al generar el código de la biblioteca:

#ifdef PROJECT2_EXPORTS
#define PROJECT2_API __declspec(dllexport)
#else
#define PROJECT2_API __declspec(dllimport)
#endif

El prototipo de nuestra función es el siguiente:

PROJECT2_API int fnExport(wchar_t* t);

Después de la compilación, veamos como es la tabla de la exportación:


Para visualizar, simplemente seleccionamos el archivo con la biblioteca en la ventana "Total Commander" y pulsamos F3. Obsérvese como se visualiza el nombre de la función exportada. Ahora, editamos la macro de arriba (precisamente así fue hecho en el artículo inicial):

#ifdef PROJECT2_EXPORTS
#define PROJECT2_API extern "C" __declspec(dllexport)
#else
#define PROJECT2_API __declspec(dllimport)
#endif

La inserción de

extern "C"

significa el uso de una generación simple de la signatura de la función (en el estilo del lenguaje C) al recibir los archivos de objetos. En particular, eso prohíbe al compilador «decorar» el nombre de la función usando símbolos adicionales al exportar a una DLL. Repetimos la compilación y veamos nuevamente como será la tabla de la exportación:

Los cambios en la tabla de exportación son evidentes, y el error al llamar a la función ha desaparecido del script. No obstante, desde mi parecer, este método tiene una desventaja, es que es necesario editar el script creado por el compilador. Existe una manera más segura de lograr los mismos resultados, aunque es un poco más larga:

Archivo de definiciones

Se trata de un archivo de texto común, generalmente, con un nombre que coincide con el nombre del proyecto y que tiene la extensión def. Es decir, en este caso, será el archivo Project2.def. Este archivo se crea en el Bloc de notas (Notepad), de ninguna manera en Word y editores similares. El contenido de texto será aproximadamente el siguiente:

; PROJECT2.def : Declares the module parameters for the DLL.

LIBRARY      "PROJECT2"
DESCRIPTION  'PROJECT2 Windows Dynamic Link Library'

EXPORTS
    ; Explicit exports can go here
        fnExport @1
        fnExport2 @2
        fnExport3 @3
        ....

Primero, va el encabezado, luego, una lista de funciones a exportar. Los símbolos tipo @1, @2, etc. indican en el orden deseado de las funciones en la biblioteca. Este archivo debe guardarse en la carpeta del proyecto.

Creamos este archivo y lo incluimos en el proyecto. En la ventana de las propiedades del proyecto, en la ventana izquierda, seleccionamos el elemento desplegable "Linker" y su subelemento "Input", y en la derecha, el parámetro "Module Definition File". Igual como en los casos anteriores, obtenemos el acceso a la lista editable, a donde añadimos el nombre del archivo: "Project2.def". Hacemos clic en el botón "OK" y repetimos la compilación. Obtenemos el mismo resultado que en la última captura de pantalla. El nombre no está decorado y no hay errores al llamar a la función. Después de aclararnos con las configuraciones del proyecto, podemos empezar a escribir el código de la biblioteca en sí.

Creando una biblioteca y DllMain

Las cuestiones del intercambio de datos y las llamadas a diferentes funciones desde DLL fueron aclaradas bastante detalladamente en el artículo inicial. No obstante, hay que prestar atención en determinados momentos, y para eso, vamos a crear este simple código en la biblioteca:

1. Añadimos la función a la exportación (no olvidemos editar el archivo de las definiciones):

PROJECT2_API int fnExport1(void) {
        return GetSomeParam();
}

2. Creamos y añadimos el archivo de encabezado Header1.h al proyecto y escribimos dentro otra función:

const int GetSomeParam();
3.Editamos el archivo dllmain.cpp:
#include "stdafx.h"
#include "Header1.h"

int iParam;

BOOL APIENTRY DllMain( HMODULE hModule,
                       DWORD  ul_reason_for_call,
                       LPVOID lpReserved
                     )
{
    switch (ul_reason_for_call)
    {
    case DLL_PROCESS_ATTACH:
                iParam = 7;
                break;
    case DLL_THREAD_ATTACH:
                iParam += 1;
                break;
    case DLL_THREAD_DETACH:
    case DLL_PROCESS_DETACH:
        break;
    }
    return TRUE;
}

const int GetSomeParam() {
        return iParam;
}

La lógica de este código debe ser clara: a la biblioteca se le añade una variable cuyo valor se calcula en la función DllMain y está disponible a través de la función fnExport1. Llamamos a la función en el script:

#import "Project2.dll"
int fnExport1(void);
#import
...
void OnStart() {
Print("fnExport1: ",fnExport1() );

Recibimos la siguiente entrada:

fnExport1: 7

Eso quiere decir que esta parte del código no se ejecuta en DllMain:

    case DLL_THREAD_ATTACH:
                iParam += 1;
                break;

¿Hasta qué punto es importante eso? Desde mi punto de vista es extremadamente importante, porque si el desarrollador coloca una parte del código de inicialización en esta ramificación a la espera de que sea ejecutada cuando la biblioteca se adjunte al flujo, sus cálculos no serán justificados. Además, no será registrado ningún error, lo que sin duda complicará su búsqueda.

Cadenas

En el artículo inicial, se dice cómo trabajar con las cadenas (string). Este trabajo no es difícil, pero hay un punto que debe ser aclarado.

Creamos una simple función en la biblioteca (y editamos el archivo de definiciones):

PROJECT2_API void SamplesW(wchar_t* pChar) {
        size_t len = wcslen(pChar);
        wcscpy_s(pChar + len, 255, L" Hello from C++");
}
Llamamos a esta función en el script:
#import "Project2.dll"
void SamplesW(string& pChar);
#import

void OnStart() {

string t = "Hello from MQL5";
SamplesW(t);
Print("SamplesW(): ", t);

Como es de esperar, recibimos el siguiente mensaje:

SamplesW(): Hello from MQL5 Hello from C++

Modificamos la llamada a la función:

#import "Project2.dll"
void SamplesW(string& pChar);
#import

void OnStart() {

string t;
SamplesW(t);
Print("SamplesW(): ", t);

Y ahora, recibimos un mensaje de error que asusta:

Access violation at 0x00007FF96B322B1F read to 0x0000000000000008

Inicializamos la cadena que pasamos a la función de la biblioteca y repetimos la ejecución del script:

string t="";

El mensaje de error ha desaparecido, volvemos a recibir la salida esperada:

SamplesW():  Hello from C++

De lo arriba expuesto, se puede sacar la conclusión de que ¡las cadenas pasadas en las funciones exportadas de la biblioteca deben ser inicializadas obligatoriamente!

Aquí, hemos llegado a la cuestión a la que hemos prometido volver al discutir el uso de Unicode. Si no se planea pasar las cadenas en DLL, como en el último ejemplo, se puede prescindir del soporte de Unicode. No obstante, es mejor incluir este soporte de cualquier manera, ya que las signaturas de las funciones exportadas pueden cambiar, aparecer las nuevas, y el desarrollador simplemente puede olvidar de que no hay soporte de Unicode.

La transmisión y la recepción de los arrays de los símbolos no tiene ninguna característica especial. Ella fue discutida en el artículo inicial, y aquí no vamos a detenernos en ella.

Estructuras

Definimos la estructura más simple en la biblioteca y en el script:

//В dll:
typedef struct E_STRUCT {
        int val1;
        int val2;
}ESTRUCT, *PESTRUCT;

//В скрипте MQL:
struct ESTRUCT {
   int val1;
   int val2;
};

Añadimos la función para trabajar con esta estructura a la biblioteca:

PROJECT2_API void SamplesStruct(PESTRUCT s) {
        int t;
        t = s->val2;
        s->val2 = s->val1;
        s->val1 = t;
}

A partir del código se hace claro que la función simplemente ejecuta un swap habitual de sus propios campos.

Llamamos a la función desde el script:

#import "Project2.dll"
void SamplesStruct(ESTRUCT& s);
#import
....
ESTRUCT e;
e.val1 = 1;
e.val2 = 2;
SamplesStruct(e);
Print("SamplesStruct: val1: ",e.val1," val2: ",e.val2);

Ejecutamos el script y obtenemos un resultado predicible:

SamplesStruct: val1: 2 val2: 1

El objeto ha sido pasado en la función invocada por referencia, la función ha procesado este objeto y lo ha devuelto al código de la llamada.

No obstante, no siempre es posible limitarse con el trabajo con las estructuras tan simples. Vamos a complicar la tarea, añadiendo otro campo de un tipo diferente a la estructura:

typedef struct E_STRUCT1 {
        int val1;
        char cval;
        int val2;
}ESTRUCT1, *PESTRUCT1;

También añadimos una función para trabajar con ella:

PROJECT2_API void SamplesStruct1(PESTRUCT1 s) {
        int t;
        t = s->val2;
        s->val2 = s->val1;
        s->val1 = t;
        s->cval = 'A';
}

La función, como la anterior, hace el swap de sus campos tipo int y asigna un valor al campo tipo char. Llamamos a esta función en el script (exactamente de la misma manera que la función anterior). Recibimos inesperadamente una entrada en el registro de este tipo:

SamplesStruct1: val1: -2144992512 cval: A val2: 33554435

Se ve que los campos tipo int contienen la basura. No recibimos las excepciones, sino la basura aleatoria, los datos incorrectos. ¿Qué ha pasado? ¡La cosa está en la alineación! La «alineación» es un concepto no tan simple, pero tampoco tan complicado. En la documentación, hay una sección pack dedicada a las estructuras donde se describe detalladamente de qué se trata. En cuanto a la alineación en el entorno Visual Studio C++, también existe bastante material dedicado al alineamiento.

En nuestro ejemplo, el origen del error consiste en el hecho de que en este caso la biblioteca y el script tienen diferentes alineaciones y por eso, el script «pilla» la basura. Hay dos maneras de resolver el problema:

  1. Especificar una nueva alineación en el script. Para eso, tenemos el atributo pack(n). Intentaremos alinear la estructura por el campo de la magnitud máxima, es decir, por int:
    struct ESTRUCT1 pack(sizeof(int)){
            int val1;
            char cval;
            int val2;
    };
    Repetimos la salida ejecutando el script. Ahora, la entrada en el registro ha cambiado: SamplesStruct1: val1: 3 cval: A val2: 2 . Todo está bien, el problema está resuelto.

  2. Especificar una nueva alineación en la biblioteca. Por defecto, las estructuras en MQL tienen la alineación por pack(1), es necesario aplicar la misma en la biblioteca de la siguiente forma:
    #pragma pack(1)
    typedef struct E_STRUCT1 {
            int val1;
            char cval;
            int val2;
    }ESTRUCT1, *PESTRUCT1;
    #pragma pack()
    Construimos la biblioteca, volvemos a ejecutar el script y obtenemos un resultado correcto (el mismo que al usar el primer método).
Verificamos otra cosa. ¿Qué pasará si en la estructura hay métodos, además de los campos de datos? Eso es completamente posible. Por ejemplo, el desarrollador puede añadir un constructor (aunque claro no es un método), un destructor, algo más según su parecer. Vamos a comprobar eso usando la siguiente estructura en la biblioteca:
#pragma pack(1)
typedef struct E_STRUCT2 {
        E_STRUCT2() {
                val2 = 15;
        }
        int val1;
        char cval;
        int val2;
}ESTRUCT2, *PESTRUCT2;
#pragma pack()
Esta estructura se usa por la siguiente función:
PROJECT2_API void SamplesStruct2(PESTRUCT2 s) {
        int t;
        t = s->val2;
        s->val2 = s->val1;
        s->val1 = t;
        s->cval = 'B';
}
Hacemos las alteraciones apropiadas en el script:
struct ESTRUCT2 pack(1){
        ESTRUCT2 () {
           val1 = -1;
           val2 = 10;
        }
        int val1;
        char cval;
        int f() { int val3 = val1 + val2; return (val3);}
        int val2;
};

#import "Project2.dll" 
void SamplesStruct2(ESTRUCT2& s); 
#import
...
ESTRUCT2 e2;
e2.val1 = 4;
e2.val2 = 5;
SamplesStruct2(e2);
t = CharToString(e2.cval);
Print("SamplesStruct2: val1: ",e2.val1," cval: ",t," val2: ",e2.val2);

Obsérvese que el método f() ha sido añadido a la estructura para que en la biblioteca haya aún más diferencias de la estructura. Ejecutamos el script y obtenemosesta entrada en el registro: SamplesStruct2:  val1: 5 cval: B val2: 4  ¡Todo está bien! La presencia de un constructor y un método adicional en nuestra estructura no ha afectado el resultado de ninguna manera.

Hagamos el último experemento. Quitamos el constructor y el método de la estructura en el script, dejamos sólo los campos con datos, mientras que la estructura en la biblioteca se queda sin alterar. Ejecutamos el script nuevamente y obtenemos el mismo resultado correcto. Ahora, ya podemos concluir definitivamente que la presencia de los métodos adicionales en las estructuras no afecta el resultado de ninguna forma.

El proyecto de esta biblioteca para Visual Studio 2017 y el script para MetaTrader 5 se encuentran en los archivos adjuntos al artículo..

Sobre lo que no hay que hacer

Al trabajar con las bibliotecas dll, existen ciertas limitaciones que están descritas en la documentación. No vamos a repetir lo que está escrito en la documentación. Vamos a poner sólo un ejemplo:

struct BAD_STRUCT {
   string simple_str;
};

No se puede pasar esta estructura en dll. ¡Hemos envuelto apenas una cadena (sólo una cadena) con la ayuda de una estructura! Es más, es imposible pasar los objetos más complejos en dll sin recibir una excepción.

Sobre lo que hacer cuando no se puede hacerlo

Hay muchas ocasiones cuando es necesario pasar en dll un objeto que está prohibido pasar. Por ejemplo, puede ser una estructura con objetos dinámicos, un array de engranaje, etc. ¿Qué hacemos en esta situación? Si el desarrollador no tiene acceso al código de la biblioteca, tendrá que rechazar esta solución. La situación es completamente diferente si tiene este acceso.

No vamos a considerar la situación con el cambio en el diseño de datos, hay que intentar resolver el problema usando medios disponibles y no obtener excepciones al hacer eso. Voy a introducir una precisión. Puesto que el artículo no está destinado para los usuarios experimentados, aquí vamos a delinear solamente las posibles soluciones del problema, dejando la escritura del código real para el ejercicio y perfeccionamiento de los lectores.

  1. Posibilidad de la aplicación de la función StructToCharArray(). Es otra posibilidad atrayente que permite escribir aproximadamente el siguiente código en el script:
    struct Str 
      {
         ...
      };
    
    Str s;
    uchar ch[];
    StructToCharArray(s,ch);
    
    SomeExportFunc(ch);
    Y también en el archivo cpp de la biblioteca:
    #pragma pack(1)
    typedef struct D_a {
    ...
    }Da, *PDa;
    #pragma pack()
    
    void SomeExportFunc(char* pA)
      {
            PDa = (PDa)pA;
            ......
      }
    Pasando de la seguridad y de la calidad de este código, notamos inmediatamente la inutilidad del propio método: StructToCharArray() funciona sólo con las estructuras POD, se puede transferir estas estructuras a las bibliotecas sin conversiones adicionales. Reparare en que no he verificado la aplicación de esta función en vida «real».

  2. Escribir su propio empaquetador/desempaquetador de las estructuras en un objeto que puede transferirse a la biblioteca. Es una manera posible, pero bastante complicada y que requiere muchos esfuerzos. No obstante, eso nos lleva a una solución aceptable:

  3. Todos los objetos que no pueden ser transferidos a la biblioteca directamente, se empaquetan en una secuencia JSON en el script y se desempaquetan en las estructuras en la biblioteca, y viceversa. Hay herramientas necesarias para eso. Los analizadores sintácticos para JSON también están disponibles para C++, C#, MQL. Podemos usar esta posibilidad si dedicamos tiempo para empaquetar/desempaquetar. Pues sí, habrá algunas demoras. Pero las ventajas son obvias. Se puede trabajar con las estructuras (y no sólo con las estructuras) de una complejidad bastante alta. Si es necesario, se puede modificar el empaquetador/desempaquetador existente en vez de escribir uno desde cero, lo que es obviamente más fácil.

Así, vamos a tener en cuenta que la posibilidad de transferir/recibir un objeto complejo a/de la biblioteca casi existe.

Aplicación práctica

Apliquemos los conocimientos obtenidos y creemos una biblioteca útil. Que sea una biblioteca que envía mensajes de correo. Nótese algunos puntos desde el principio:

  • No se puede usar la biblioteca para enviar spam.
  • La biblioteca va a enviar correo no obligatoriamente desde la dirección y desde el servidor que están especificados en los ajustes del terminal. La verdad es que en las configuraciones del terminal, puede que haya una prohibición de usar el correo, pero eso no influirá de ninguna manera en el funcionamiento de la biblioteca.

Y por último, la mayor parte del código en C++ no me pertenece, ha sido bajada de los foros de Microsoft. Es un ejemplo bastante antiguo y comprobado cuyas variantes también están en VBS.

Empecemos. Creamos un proyecto en Visual Studio 2017 y modificamos sus configuraciones tal como ha sido descrito al principio del artículo. Creamos el archivo de definiciones y lo incluimos en el proyecto. Tendremos la única función exportada:

SENDSOMEMAIL_API bool  SendSomeMail(LPCWSTR addr_from,
        LPCWSTR addr_to,
        LPCWSTR subject,
        LPCWSTR text_body,

        LPCWSTR smtp_server,
        LPCWSTR smtp_user,
        LPCWSTR smtp_password);

El sentido de sus argumentos se comprende forma intuitiva, aun así, los explicaremos de forma breve:

  • addr_from, addr_to — direcciones de correo del remitente y destinatario.
  • subject, text_body — el asunto y el cuerpo del mensaje.
  • smtp_server, smtp_user, smtp_password — dirección del servidor SMTP, login del usuario en este servidor y la contraseña.

Observemos lo siguiente:

  • La descripción de los argumentos indica que, para enviar un email, hay que disponer de una cuenta de usuario en el servidor de correo y saber su dirección. Por eso, es imposible que el remitente sea anónimo.
  • El código de la biblioteca tiene el número del puerto que es hard-coded protegido. Es el puerto estándar con el número veinte cinco (25).
  • La biblioteca recibe los datos necesarios, se comunica con el servidor y le envía el correo. Durante una llamada, se puede enviar el correo sólo a una dirección. Si el desarrollador desea repetir el envío, habrá que repetir la llamada a la función con una nueva dirección.

El código C++ en sí no se muestra aquí. Se puede encontrarlo (y el proyecto entero) en el proyecto adjunto SendSomeMail.zip. Apenas diré que el objeto usado CDO posee muchos recursos y puede (y debe) ser realizado para desarrollar y mejorar la biblioteca.

Además de este proyecto, escribiremos un script simple para llamar a la función de la biblioteca (se encuentra en el archivo adjunto SendSomeMail.mq5):

#import "SendSomeMail.dll"
bool  SendSomeMail(string addr_from,string addr_to,string subject,string text_body,string smtp_server,string smtp_user,string smtp_password);
#import

//+------------------------------------------------------------------+
//| Script program start function                                    |
//+------------------------------------------------------------------+
void OnStart()
  {
   bool b = SendSomeMail("XXX@XXX.XX", "XXXXXX@XXXXX.XX", "hello", "hello from me to you","smtp.XXX.XX", "XXXX@XXXX.XXX", "XXXXXXXXX");
   Print("Send mail: ", b);
   
  }

En vez de los caracteres "Х", el desarrollar tendrá que colocar sus valores, porque no puedo mostrar los datos de mi cuenta de usuario. Eso concluye el desarrollo. Después de sustituir los datos correctos e introducir unas posibles modificaciones en el código, se puede usar la biblioteca.

Conclusiones

Al usar la información del artículo inicial y considerar el nuevo contenido de éste, el desarrollador puede aprender rápidamente los fundamentos y pasar a los proyectos más complejos y más interesantes.

Al final, me gustaría destacar un hecho interesante, que puede tener una gran importancia en algunas ocasiones. ¿Y si surge la necesidad de proteger el código en DLL? La solución estándar es usar un empaquetador. Hay muchos empaquetadores diferentes, y muchos de ellos son capaces de garantizar un buen nivel de protección. Resulta que tengo dos de ellos: Themida 2.4.6.0 y VMProtect Ultimate v. 3.0.9 . Vamos a aplicar estos empaquetadores y empaquetar nuestra primera y más simple Project2.dll en dos variantes para cada empaquetador. Después de eso, usando el script que ya tenemos, llamamos a las funciones exportadas en el terminal. ¡Todo funciona! El terminal puede trabajar con estas bibliotecas, lo que no obstante no garantiza un funcionamiento normal con los bibliotecas cubiertas por otros empaquetadores. La Project2.dll empaquetada en dos versiones se encuentra en el archivo Project2_Pack.zip

Eso es todo. Que tengan suerte y éxitos en su trabajo.

Programas usados en el artículo:

 # Nombre
Tipo
 Descripción
1 Project2.zip Archivo
Proyecto de DLL simple.
2
Project2.mq5
Script
Script para trabajar con DLL.
3 SendSomeMail.zip Archivo Proyecto de DLL para el envío de correo.
4 SendSomeMail.mq5 Script
Script para trabajar con la biblioteca SendSomeMail. dll
5 Project2_Pack.zip Archivo Project2.dll empaquetada por Themida y VMProtect.




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

Archivos adjuntos |
Project2.mq5 (3.42 KB)
SendSomeMail.mq5 (1.15 KB)
SendSomeMail.zip (16.62 KB)
Project2_Pack.zip (4645.25 KB)
Project2.zip (18.65 KB)
Estudio de técnicas de análisis de velas (Parte III): Biblioteca para el trabajo con patrones Estudio de técnicas de análisis de velas (Parte III): Biblioteca para el trabajo con patrones

El objetivo de este artículo es crear una herramienta personalizada que nos permita obtener y usar la matriz completa de información sobre los patrones vistos anteriormente. Para ello, desarrollaremos una biblioteca que podremos utilizar en nuestros indicadores, paneles comerciales, expertos, etc.

Optimización de color de estrategias comerciales Optimización de color de estrategias comerciales

En este artículo, vamos a realizar un experimento del coloreo de los resultados de la optimización. Como se sabe, el color se determina por tres parámetros: los niveles del color rojo, verde y azul (RGB en inglés, Red — rojo, Green — verde, Blue — azul). Hay otros métodos de codificar el color, pero igualmente se usan tres parámetros. Así, tres parámetros de la simulación pueden ser convertidos en un color que el trader percibe visualmente. Lea este artículo para averiguar si esta representación va a ser útil.

Utilidad para la selección y navegación en MQL5 y MQL4: aumentando la informatividad de los gráficos Utilidad para la selección y navegación en MQL5 y MQL4: aumentando la informatividad de los gráficos

En este artículo, continuaremos expandiendo la funcionalidad de nuestra utilidad. Esta vez, añadiremos las posibilidades de visualizar la información en el gráfico, que sirve para facilitar nuestra negociación. En particular, añadiremos en el gráfico los precios máximos y mínimos del día anterior, niveles redondos, precios máximos y mínimos durante el año, hora del inicio de la sesión, etc.

Web scraping de datos sobre la rentabilidad de los bonos Web scraping de datos sobre la rentabilidad de los bonos

Cuando diseñamos los sistemas del trading automático, casi siempre utilizamos los datos de los indicadores técnicos que analizan el pasado con el fin de predecir el futuro comportamiento del precio. Pero si no tomamos en cuenta las fuerzas fundamentales que mueven el mercado, evidentemente estaremos en una situación menos ventajosa en comparación con los traders que consideran adicionalmente los datos fundamentales en sus decisiones comerciales. Recopilando automáticamente los datos sobre los tipos de interés, Usted podrá mejorar el funcionamiento de su Asesor Experto.