Particularidades de la conexión DLL
Las siguientes entidades no pueden pasarse como parámetros a funciones importadas de una DLL:
- Clases (objetos y punteros a ellas)
- Estructuras que contienen arrays dinámicas, cadenas, clases y otras estructuras complejas.
- Arrays de cadenas o los objetos complejos anteriores
Todos los parámetros de tipo simple se pasan por valor a menos que se indique explícitamente que se pasan por referencia. Cuando se pasa una cadena, se pasa la dirección del búfer de la cadena copiada; si la cadena se pasa por referencia, entonces la dirección del búfer de esta cadena en particular se pasa a la función importada de la DLL sin copiar.
Cuando se pasa un array a la DLL, siempre se pasa la dirección del principio del búfer de datos (independientemente de la bandera AS_SERIES). La función dentro de la DLL no sabe nada sobre la bandera AS_SERIES, el array pasado es un array de longitud desconocida, y se necesita un parámetro adicional para especificar su tamaño.
Al describir el prototipo de una función importada, puede utilizar parámetros con valores por defecto.
Al importar DLLs, debe dar permiso para usarlas en las propiedades de un programa MQL específico o en la configuración general del terminal. A este respecto, en la sección Permisos presentamos el script EnvPermissions.mq5, que, en particular, tiene una función para leer el contenido del portapapeles del sistema Windows utilizando DLLs del sistema. Esta función se proporcionó opcionalmente: su llamada se comentó porque no sabíamos cómo trabajar con bibliotecas. Ahora, lo transferiremos a un script separado LibClipboard.mq5.
La ejecución del script puede pedir confirmación al usuario (ya que las DLL están desactivadas por defecto por motivos de seguridad). Si es necesario, active la opción en el cuadro de diálogo, en la pestaña con las dependencias.
Los archivos de encabezado se proporcionan en el directorio MQL5/Include/WinApi, que también incluye directivas #import para funciones del sistema muy necesarias, como la gestión del portapapeles (openclipboard, GetClipboardData y CloseClipboard), la gestión de la memoria (GlobalLock y GlobalUnlock), las ventanas de Windows y muchas otras. Incluiremos sólo dos archivos: winuser.mqh y winbase.mqh, que contienen las directivas de importación necesarias e, indirectamente, a través de la conexión a windef.mqh, las macros de términos de Windows (HANDLE y PVOID):
#define HANDLE long
|
Además, importamos la función lstrcatW de la biblioteca kernel32.dll porque no estamos satisfechos con su descripción en winbase.mqh que se proporciona de manera predeterminada: esto ofrece a la función un segundo prototipo, adecuado para pasar el valor PVOID en el primer parámetro.
#include <WinApi/winuser.mqh>
|
La esencia del trabajo con el portapapeles es «capturar» el acceso al mismo mediante OpenClipboard, tras lo cual debe obtener un manejador de datos (GetClipboardData), convertirlo en una dirección de memoria (GlobalLock) y, por último, copiar los datos de la memoria del sistema a su variable (lstrcatW). A continuación, se liberan los recursos ocupados en orden inverso (GlobalUnlock y CloseClipboard).
void ReadClipboard()
|
Pruebe a copiar el texto en el portapapeles y, a continuación, ejecute el script: el contenido del portapapeles debería registrarse. Si el búfer contiene una imagen u otros datos que no tienen representación textual, el resultado estará vacío.
Las funciones importadas desde una DLL siguen la convención de enlace de ejecutables binarios de las funciones de la API de Windows. Para garantizar esta convención, en el texto fuente de los programas se utilizan palabras clave específicas del compilador, como, por ejemplo, __stdcall en C o C++. Estas reglas de enlace implican lo siguiente:
- La función de llamada (en nuestro caso, el programa MQL) debe ver el prototipo de la función llamada (importado de la DLL) con el fin de apilar correctamente los parámetros en la pila.
- La función de llamada (en nuestro caso, el programa MQL) apila los parámetros en orden inverso, de derecha a izquierda: este es el orden en que la función importada lee los parámetros que se le pasan.
- Los parámetros se pasan por valor, excepto los que se pasan explícitamente por referencia (en nuestro caso, cadenas).
- La función importada lee los parámetros que se le pasan y borra la pila.
He aquí otro ejemplo de script que utiliza una DLL: LibWindowTree.mq5. Su tarea es recorrer el árbol de todas las ventanas del terminal y obtener sus nombres de clase (según el registro en el sistema usando WinApi) y títulos. Por ventanas entendemos los elementos estándar de la interfaz de Windows, que también incluyen controles. Este procedimiento puede ser útil para automatizar el trabajo con el terminal: emular la pulsación de botones en ventanas, cambiar modos que no están disponibles a través de MQL5, etc.
Para importar las funciones del sistema necesarias, incluyamos el archivo de encabezado WinUser.mqh que utiliza user32.dll.
#include <WinAPI/WinUser.mqh> |
Puede obtener el nombre de la clase de ventana y su título utilizando las funciones GetClassNameW y GetWindowTextW: se llaman en la función GetWindowData.
void GetWindowData(HANDLE w, string &clazz, string &title)
|
El sufijo «W» en los nombres de las funciones significa que están pensadas para cadenas con formato Unicode (2 bytes por carácter), que son las más utilizadas hoy en día (el sufijo «A» para cadenas ANSI sólo tiene sentido utilizarlo por compatibilidad con bibliotecas antiguas).
Dado un manejador inicial de una ventana de Windows, la función TraverseUp permite recorrer la jerarquía de sus ventanas padre: su funcionamiento se basa en la función del sistema GetParent. Para cada ventana encontrada, TraverseUp llama a GetWindowData y muestra el nombre y el título de la clase resultante en el registro.
HANDLE TraverseUp(HANDLE w)
|
La función TraverseDown se encarga de recorrer la jerarquía en profundidad: la función FindWindowExW del sistema se utiliza para enumerar las ventanas secundarias.
HANDLE TraverseDown(const HANDLE w, const int level = 0)
|
En la función OnStart encontramos la ventana principal del terminal recorriendo las ventanas hacia arriba desde el manejador del gráfico actual en el que se está ejecutando el script. A continuación, construimos todo el árbol de ventanas de terminal.
void OnStart()
|
También podemos buscar las ventanas requeridas por nombre de clase y/o título, por lo que la ventana principal podría obtenerse inmediatamente llamando a FindWindowW, ya que se conocen sus atributos.
h = FindWindowW("MetaQuotes::MetaTrader::5.00", NULL); |
He aquí un ejemplo de registro (fragmento):
'AfxFrameOrView140su' '' 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,H1' 'MDIClient' '' 'MetaQuotes::MetaTrader::5.00' '12345678 - MetaQuotes-Demo: Demo Account - Hedge - ...' Main window handle: 263576 'msctls_statusbar32' 'For Help, press F1' 'AfxControlBar140su' 'Standard' 'ToolbarWindow32' 'Timeframes' 'ToolbarWindow32' 'Line Studies' 'ToolbarWindow32' 'Standard' 'AfxControlBar140su' 'Toolbox' 'Afx:000000013F110000:b:0000000000010003:0000000000000000:0000000000000000' 'Toolbox' 'AfxWnd140su' '' 'ToolbarWindow32' '' ... 'MDIClient' '' 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,H1' 'AfxFrameOrView140su' '' 'Edit' '0.00' 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'XAUUSD,Daily' 'AfxFrameOrView140su' '' 'Edit' '0.00' 'Afx:000000013F110000:b:0000000000010003:0000000000000006:00000000000306BA' 'EURUSD,M15' 'AfxFrameOrView140su' '' 'Edit' '0.00'
|