Особенности подключения DLL-библиотек
В импортируемые из DLL функции нельзя передавать в качестве параметров:
- Классы (объекты и указатели на них);
- Структуры, содержащие динамические массивы, строки, классы, другие сложные структуры;
- Массивы строк или вышеперечисленных сложных объектов.
Все параметры простых типов передаются по значению, если явно не указано, что они передаются по ссылке. При передаче строки передается адрес буфера скопированной строки; если строка передается по ссылке, то в функцию, импортируемую из DLL, передается адрес буфера именно этой строки без копирования.
При передаче в DLL массива всегда (независимо от флага AS_SERIES) передается адрес начала буфера данных. Функция внутри DLL ничего не знает о флаге AS_SERIES, переданный массив является массивом неизвестной длины, для указания его размера нужен дополнительный параметр.
При описании прототипа импортируемой функции можно использовать параметры со значениями по умолчанию.
При импорте DLL-библиотек требуется дать разрешение на их использование в свойствах конкретной MQL-программы или в общих настройках терминала. В связи с этим в разделе Разрешения мы представляли скрипт EnvPermissions.mq5, в котором, в частности, имеется функция чтения содержимого системного буфера обмена Windows, использующая системные DLL-библиотеки. Там эта функция была приведена в факультативном порядке: её вызов был закомментирован, потому что мы еще не знали, как работать с библиотеками, но сейчас перенесем её в отдельный скрипт LibClipboard.mq5.
Запуск скрипта может инициировать запрос подтверждения у пользователя (так как по умолчанию DLL-библиотеки запрещены из соображений безопасности). При необходимости включите опцию в диалоге, на закладке с зависимостями.
Вместе с терминалом поставляются заголовочные файлы в каталоге MQL5/Include/WinApi, где уже прописаны директивы #import для востребованных системных функций, таких как работа с буфером обмена (OpenClipboard, GetClipboardData, CloseClipboard), управление памятью (GlobalLock, GlobalUnlock), окнами Windows и многие другие. Мы подключим лишь два файла — winuser.mqh и winbase.mqh. В них есть требуемые директивы импорта и, опосредовано — через подключение windef.mqh, макросы терминов Windows (HANDLE и PVOID):
#define HANDLE long
|
Кроме того, мы самостоятельно импортируем функцию lstrcatW из библиотеки kernel32.dll, потому что нас не устраивает её описание в winbase.mqh, предоставленное по умолчанию: тем самым у функции появляется второй прототип, подходящий для передачи в первом параметре значения PVOID.
#include <WinApi/winuser.mqh>
|
Суть работы с буфером обмена заключается в "захвате" доступа к нему с помощью OpenClipboard, после чего следует получить дескриптор данных (GetClipboardData), преобразовать его в адрес в памяти (GlobalLock) и, наконец, скопировать данные из системной памяти в свою переменную (lstrcatW). Далее выполняется освобождение занятых ресурсов в обратном порядке (GlobalUnlock, CloseClipboard).
void ReadClipboard()
|
Попробуйте скопировать в буфер обмена текст и затем запустить скрипт: в журнал должно вывестись содержимое буфера. Если в буфере находится изображение или другие данные, не имеющие текстового представления, результат окажется пустым.
Функции, импортируемые из DLL, подчиняются соглашению о связывании (linking) двоичных исполняемых файлов, принятому для функций Windows API. Для обеспечения такого соглашения в исходном тексте программ используются специфические для конкретного компилятора ключевые слова, такие как, например, __stdcall в C или C++. Данные правила связывания подразумевают следующее:
- Вызывающая функция (в нашем случае, MQL-программа) должна "видеть" прототип вызываемой (импортируемой из DLL) функции, для того чтобы правильно сложить параметры на стек;
- Вызывающая функция (в нашем случае, MQL-программа) складывает параметры на стек в обратном порядке, справа налево — именно в таком порядке импортируемая функция считывает переданные ей параметры;
- Параметры передаются по значению, за исключением тех, которые явно передаются по ссылке (в нашем случае, строк);
- Импортируемая функция, считывая переданные ей параметры, сама очищает стек.
Приведем еще один пример скрипта, использующего DLL, — LibWindowTree.mq5. Его задача — проход по дереву всех окон терминала и получение их имен классов (согласно регистрации в системе средствами WinApi) и заголовков. Под окнами здесь имеются в виду стандартные элементы интерфейса Windows, включающие также и элементы управления. Данная процедура может пригодиться для автоматизации работы с терминалом: эмуляции нажатия кнопок в окнах, переключения режимов, которые недоступны через MQL5 и так далее.
Для импорта требуемых системных функций подключим заголовочный файл WinUser.mqh, использующий user32.dll.
#include <WinAPI/WinUser.mqh> |
Получить название класса окна и его заголовок можно с помощью функций GetClassNameW и GetWindowTextW: они вызываются в функции GetWindowData.
void GetWindowData(HANDLE w, string &clazz, string &title)
|
Суффикс 'W' в названиях функций означает, что они предназначены для строк формата Unicode (2 байта на символ) — наиболее распространенного сегодня (суффикс 'A' для ANSI-строк имеет смысл использовать только для обратной совместимости со старыми библиотеками).
При наличии некоего начального дескриптора окна Windows проход вверх по иерархии его родительских окон обеспечивает функция TraverseUp: её работа основывается на системной функции GetParent. Для каждого найденного окна TraverseUp вызывает GetWindowData и выводит полученные имя класса и заголовок в журнал.
HANDLE TraverseUp(HANDLE w)
|
Обход вглубь иерархии производится функцией TraverseDown: для перечисления дочерних окон применяется системная функция FindWindowExW.
HANDLE TraverseDown(const HANDLE w, const int level = 0)
|
В функции OnStart найдем главное окно терминала за счет обхода окон вверх от дескриптора текущего графика, на котором запущен скрипт. А затем построим все дерево окон терминала.
void OnStart()
|
По идее мы можем искать требуемые окна по имени класса и/или заголовку, а потому главное окно можно было бы сразу получить, вызвав FindWindowW, поскольку его атрибуты известны.
h = FindWindowW("MetaQuotes::MetaTrader::5.00", NULL); |
Вот пример журнала (фрагмент):
'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'
|