Llevaba mucho tiempo esperando un artículo como éste. Gracias al autor.
Gracias a usted. No es el primero que me da las gracias. Estaré encantado de escuchar todos los deseos y comentarios críticos sobre el material del artículo.
En el futuro me gustaría desarrollar el tema de la programación en Delphi para MT5, añadiendo nueva información al sitio.
Creo que es un artículo útil para mucha gente. Un par de comentarios:
1. Las unidades SysUtils y Classes deberían haberse dejado en el proyecto. A pesar de que su presencia "hincha" un poco el proyecto, tienen muchas funciones pequeñas pero importantes. Por ejemplo, la presencia de SysUtils añade automáticamente al proyecto el tratamiento de excepciones. Como sabes, si una excepcion no se procesa en la dll, se pasa a mt5, donde provoca que se detenga la ejecucion del programa mql5.
2. No debes usar todo tipo de procedimientos dentro de DllEntryPoint (aka DllMain). Como Microsoft indica en sus documentos, esto está plagado de varios efectos desagradables. He aquí una pequeña lista de artículos sobre este tema:
Best Practices for Creating DLLs de Microsoft - http://www.microsoft.com/whdc/driver/kernel/DLL_bestprac.mspx.
DllMain y la vida antes del parto - http://transl-gunsmoker.blogspot.com/2009/01/dllmain.html.
DllMain - un cuento antes de dormir - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_04.html
Algunas razones para no hacer nada que dé miedo en tu DllMain - http://transl-gunsmoker.blogspot.com/2009/01/dllmain_05.html
Más razones para no hacer nada que dé miedo en tu DllMain: bloqueo accidental -
http://transl-gunsmoker.blogspot.com/2009/01/dllmain_7983.htmlYa di un extracto de un artículo inacabado en algún sitio, creo que en el foro de quad. Lo repetiré aquí.
inicio...fin
Cuando se crea un proyecto Delphi destinado a la compilación de DLL, la sección begin...end aparece en el fichero de proyecto .DPR. Esta sección siempre se ejecuta cuando la DLL se proyecta por primera vez en el espacio de direcciones del proceso. En otras palabras, se puede considerar como una especie de sección de inicialización que tienen todas las unidades. En este lugar se pueden realizar algunas acciones que deben realizarse al principio y sólo una vez, para el proceso actual. Al cargar la DLL en el espacio de direcciones de otro proceso, esta sección se ejecutará allí de nuevo. Pero como los espacios de direcciones de los procesos están separados entre sí, la inicialización en un proceso no afectará de ninguna manera al otro proceso.
Esta sección tiene algunas limitaciones que debes conocer y tener en cuenta. Estas limitaciones están relacionadas con las sutilezas del mecanismo de carga de DLL de Windows. Hablaremos de ellas con más detalle más adelante.
inicialización/finalización
Cada unidad Delphi tiene secciones especiales, las llamadas secciones de inicialización y finalización. Tan pronto como cualquier unidad se conecta al proyecto, estas secciones se conectan al mecanismo especial de carga y descarga del módulo principal. Y estas secciones se ejecutan antes de que la sección principal de inicio...finalización comience su trabajo y después de que el trabajo haya terminado. Esto es muy conveniente, porque elimina la necesidad de escribir la inicialización y la finalización en el propio programa. Al mismo tiempo, la conexión y desconexión se realiza de forma automática, sólo es necesario conectar o desconectar la unidad al proyecto. Y esto ocurre no sólo en los archivos EXE tradicionales, sino también en las DLL. El orden de inicialización de la DLL, al "cargarla" en memoria es el siguiente, primero se ejecutan todas las secciones de inicialización de la unidad, en el orden en que están marcadas en los usos del proyecto, luego se ejecuta la sección begin...end. La finalización se hace en el orden inverso, excepto que no hay una función de terminación especialmente diseñada en el fichero de proyecto DLL. Esta es, en general, otra razón por la que se recomienda separar los proyectos DLL en un archivo de proyecto y una unidad de usos.
DllMain
Este es el llamado punto de entrada DLL. El punto es que Windows ocasionalmente necesita reportar cualquier evento que ocurra dentro del proceso a la propia DLL. Para ello y existe un punto de entrada. Es decir, una función especialmente predefinida que tiene cada DLL y que puede manejar mensajes. Y aunque todavía no hemos visto esta función en una DLL escrita en Delphi, sin embargo tiene dicho punto. Sólo el mecanismo de su funcionamiento está velado, pero siempre se puede llegar a él. La respuesta a la pregunta - ¿es necesaria? - no es tan obvia como parece.
En primer lugar vamos a tratar de entender qué es lo que Windows está tratando de decirle a DLL. Hay un total de 4 mensajes con los que el sistema operativo se dirige a la DLL. El primero, la notificación DLL_PROCESS_ATTACH, se envía cada vez que el sistema adjunta una DLL al espacio de direcciones del proceso llamante. En el caso de MQL4se trata de una carga implícita. No importa que esta DLL ya haya sido cargada en el espacio de direcciones de otro proceso, el mensaje seguirá siendo enviado. Y no importa que de hecho Windows cargue una DLL particular en memoria sólo una vez, todos los procesos que deseen cargar esta DLL en su espacio de direcciones reciben sólo un reflejo de esta DLL. Se trata del mismo código, pero los datos que pueda tener la DLL son únicos para cada proceso (aunque es posible que existan datos comunes). La segunda, la notificación DLL_PROCESS_DETACH, indica a la DLL que se desprenda del espacio de direcciones del proceso llamante. De hecho, este mensaje se recibe antes de que Windows comience a descargar la DLL. De hecho, si la DLL está siendo utilizada por otros procesos, no se produce ninguna descarga, Windows simplemente "olvida" que la DLL existía en el espacio de direcciones del proceso. Dos notificaciones más, DLL_THREAD_ATTACH yDLL_THREAD_DETACH se reciben cuando el proceso que cargó la DLL crea o destruye hilos dentro del proceso. Hay algunas cuestiones sutiles relacionadas con el orden en que se reciben las notificaciones de hilos, pero no las consideraremos.
Ahora sobre cómo se organizan las DLLs escritas en Delphi y lo que normalmente se oculta a los programadores. Después de que Windows haya "proyectado la DLL en el espacio de direcciones del proceso llamante", o dicho de forma sencilla, cargado la DLL en memoria, en este punto se produce una llamada a la función situada en el punto de entrada y se pasa la notificación DLL_PROCESS_ATTACH a esta función. En una DLL escrita en Delphi, este punto de entrada contiene código especial que hace muchas cosas diferentes, incluyendo el inicio de la inicialización de las unidades. Recuerda que se ha realizado la inicialización y la primera ejecución de la DLL, y ejecuta begin...end del fichero principal del proyecto. Así, este código de carga inicial se ejecuta sólo una vez, todas las demás llamadas de Windows al punto de entrada se hacen a otra función, que maneja las notificaciones posteriores - de hecho, las ignora, excepto el mensaje DLL_PROCESS_DETACH, que finaliza la unidad. Este es el aspecto general del mecanismo de carga de una DLL escrita en Delphi. En la mayoría de los casos, es suficiente con escribir y utilizar DLLs en MQL4.
Si aún así necesitas un DllMain exactamente igual que en C, no es difícil organizarlo. Se hace de la siguiente manera. Al cargar una DLL por primera vez, entre otras cosas, el módulo System (siempre está presente en un programa o DLL) crea automáticamente una variable de procedimiento global DllProc, que se inicializa con nil. Esto significa que no se requiere ningún procesamiento adicional de las notificaciones DllMain, aparte del que ya existe. En cuanto se asigne la dirección de la función a esta variable, todas las notificaciones de DLLs de Windows irán a esta función. Que es lo que se requiere del punto de entrada. Sin embargo, la notificación DLL_PROCESS_DETACH seguirá siendo rastreada por la función de terminación de DLL, como antes, para permitir su finalización.
procedureDllEntryPoint(Reason: DWORD);
begin
case Reason de
DLL_PROCESS_ATTACH : ;//'Conexión proceso'
DLL_THREAD_ATTACH : ;//'Conexión del hilo'
DLL_THREAD_DETACH : ;//'Desconexión de un hilo'. flujo'
DLL_PROCESS_DETACH : ;//'Desconectar el proceso'
end;
fin;
begin
if not Assigned(DllProc) then begin
DllProc :=@DllEntryPoint;
DllEntryPoint (DLL_PROCESS_ATTACH);
fin;
end.
En caso de que no nos interesen las notificaciones de hilos, todo esto es innecesario. Sólo es necesario organizar las secciones de inicialización/finalización en unidad, ya que los eventos de conexión y desconexión del proceso serán rastreados automáticamente.
La perfidia y la traición de DllMain
Ahora, quizás, sea el momento de tocar un tema que sorprendentemente se trata poco en la literatura de programación. Este tema concierne no sólo a Delphi o C, sino a cualquier lenguaje de programación capaz de crear DLLs. Se trata de una propiedad del cargador de DLL de Windows. De la literatura traducida seria y difundida sobre la programación en el entorno Windows, sólo un autor consiguió encontrar una mención al respecto y eso en los términos más vagos. Este autor es J. Richter, y se le perdona, porque su maravilloso libro se publicó en 2001, cuando en general Windows de 32 bits no estaba tan extendido.
Es interesante que la propia MS nunca ocultó la existencia del problema con DllMain e incluso publicó un documento especial, algo así como - "La mejor manera de utilizar DllMain". En el que explicaba lo que se puede hacer en DllMain y lo que no es recomendable. Y se señalaba que las cosas no recomendadas conducen a errores difíciles de ver e inconsistentes. Aquellos que deseen leer este documento pueden mirar aquí. Un resumen más popular de varias traducciones de varios informes alarmistas sobre el tema se esboza aquí.
La esencia del problema es muy simple. La cuestión es que DllMain, especialmente cuando se carga una DLL, es un lugar especial. Un lugar donde no se debe hacer nada complicado y sobresaliente. Por ejemplo, no se recomienda CreateProcess, o LoadLibrary otras DLLs. Tampoco se recomienda CreateThread o CoInitialise COM. Y así sucesivamente.
Se pueden hacer las cosas más sencillas. De lo contrario, nada está garantizado. Por lo tanto, no pongas nada innecesario en DllMain, de lo contrario te sorprenderá ver cómo las aplicaciones que utilizan tu DLL se bloquean. Es mejor ir sobre seguro y crear funciones especiales exportadas de inicialización y finalización, que serán llamadas por la aplicación principal en los momentos adecuados. Esto al menos ayudará a evitar problemas con DllMain.
ExitProc, ExitCode,MainInstance,HInstance....
El módulo System, que siempre está enganchado a tu DLL en tiempo de compilación, tiene algunas variables globales útiles que puedes utilizar.
ExitCode, - una variable en la que puedes poner un número distinto de 0 al cargar, como resultado la carga de la DLL se detendrá.
ExitProc, - una variable de procedimiento que puede almacenar la dirección de la función que se ejecutará al salir. Esta variable es una reliquia del pasado lejano, no funciona en DLLs y, además, los desarrolladores de Delphi no recomiendan usarla en DLLs por posibles problemas.
HInstance, - una variable en la que después de la carga se almacena el descriptor de la propia DLL. Puede ser muy útil.
MainInstance, - descriptor de la aplicación que ha cargado la DLL en su espacio de direcciones.
IsMultiThread, - variable que se establece automáticamente a True, si la compilación de la DLL detecta trabajo con hilos. En base al valor de esta variable el gestor de memoria de la DLL cambia a modo multihilo. En principio, es posible forzar al gestor de memoria a cambiar a modo multihilo aunque no se utilicen hilos explícitamente en la DLL. IsMultiThread:=True; Naturalmente, el modo multihilo es más lento que el modo monohilo debido a que los hilos están sincronizados entre sí.
MainThreadID, - descriptor del hilo principal de la aplicación.
Y así sucesivamente. En general, el módulo System realiza aproximadamente las mismas funciones que CRT en C. Incluidas las funciones de gestión de memoria. Se puede obtener una lista de todas las funciones y variables que están presentes en la DLL compilada, no sólo las exportadas, sino todas, si se activa la opción Linker, Map file - Detailed, en los ajustes del Proyecto.
Gestión de la memoria
El siguiente problema, bastante serio, que suele causar dificultades es la gestión de memoria en las DLL. Más concretamente, la gestión de memoria en sí no causa ningún problema, pero tan pronto como la DLL intenta trabajar activamente con la memoria asignada por el gestor de memoria de la propia aplicación, - aquí es donde suelen empezar los problemas.
La cuestión es que normalmente las aplicaciones se compilan con MemoryManager incorporado. La DLL compilada también contiene su propio MemoryManager. Esto es especialmente cierto para aplicaciones y DLLs creadas en diferentes entornos de programación. Como en nuestro caso, el terminal está en MSVC, la DLL está en Delphi. Está claro que se trata de gestores diferentes por su estructura, pero al mismo tiempo también son gestores físicamente diferentes, cada uno gestiona su propia memoria dentro del espacio de direcciones común del proceso. En principio, no interfieren entre sí, no se quitan memoria unos a otros, existen en paralelo y normalmente no "saben" nada de la existencia de competidores. Esto es posible porque ambos gestores acceden a la memoria desde la misma fuente, el gestor de memoria de Windows.
Los problemas empiezan cuando una función DLL y una aplicación intentan gestionar secciones de memoria distribuidas por un gestor de memoria diferente. Por esta razón, existe una regla empírica entre los programadores que dice que "la memoria no debe cruzar los límites de un módulo de código".
Es una buena regla, pero no es del todo correcta. Sería más correcto utilizar el mismo MemoryManager en la DLL que utiliza la aplicación. En realidad, me gusta bastante la idea de conectar el gestor de memoria de MT4al gestor de memoria de Delphi FastMM, pero no es una idea muy factible en absoluto. De todas formas, la gestión de memoria debería ser una cosa.
En Delphi es posible sustituir el gestor de memoria por defecto por cualquier otro gestor de memoria que cumpla algunos requisitos. Así es posible hacer que la DLL y la aplicación tengan un único gestor de memoria, y será el gestor de memoria MT4.

- Aplicaciones de trading gratuitas
- 8 000+ señales para copiar
- Noticias económicas para analizar los mercados financieros
Usted acepta la política del sitio web y las condiciones de uso
Artículo publicado Guía para escribir un DLL para MQL en Delphi:
En este artículo examinaremos el mecanismo para crear un módulo DLL usando el popular lenguaje de programación de ObjectPascal dentr del entorno de programación de Delphi. Los materiales que se facilitan en este artículo están diseñados para dirigirse a programadores principiantes que trabajan con problemas que superan las barreras del lenguaje de programación incrustado de MQL5 conectando los módulos DLL externos.
Autor: Andrey Voytenko