Избавляемся от балласта самодельных DLL
Вы все еще делаете свои DLL?
Тогда мы идем к вам!
Введение
Всегда наступает момент, когда MQL5-программисту недостаточно функционала языка, которым он располагает, и он вынужден обращаться к дополнительным инструментам. Например, работать с базой данных или использовать сокеты для связи, или работать с функциями операционной системы. Чтобы расширить возможности своей MQL5-программы ему приходится обращаться к различным API. Но по некоторым причинам программист не может напрямую из MQL5 обращаться к требуемым функциям, так как он не знает:
- Как передать сложный тип данных (например, структуру) в API-функцию;
- Как работать с указателем, который возвращает API-функция.
Поэтому он вынужден использовать другой язык программирования и создавать промежуточную DLL для работы с требуемым функционалом. И хотя в MQL5 имеется механизм перевода разных типов данных с помощью структур и передачи их в API, к сожалению, MQL5 не отвечает нам на вопрос о том, как вытянуть данные из принятого указателя.
В данной статье мы поставим точку в этом вопросе и покажем простые механизмы передачи и получения сложных типов данных и работе с возвращаемыми указателями.
Содержание
- Получение указателей
- Копирование областей памяти
2. Передача структур в API функции
- Преобразование структур средствами MQL5
- Пример передачи структуры для сокетов
3. Работа с указателями от API функций
- Примеры для Memory Mapping File,
- Пример для MySQL
4. Чтение NULL-terminated строк из API функций
1. Память - это наше все
Как известно, любая переменная (в том числе и переменные сложных типов данных) имеет вполне конкретный адрес, с которого эта переменная располагается в памяти. Этот адрес является целым четырехбайтовым числом (типа int), значение которого есть адрес первого байта этой переменной.
А раз все вполне определено, значит можно работать с этим участком памяти. В библиотеке языка С (msvcrt.dll) есть функция memcpy. Ее предназначение является тем недостающим элементом, который связывает в одно целое MQL5 и различные API библиотеки, и которая открывает широкие возможности для программиста.
Обратимся к знанию предков
Функция memcpy копирует указанное число байт из одного буфера в другой и возвращает указатель на буфер-приемник.
void *memcpy(void *dst, const void *src, int cnt); dst - указатель на буфер-приемник src - указатель на буфер-источник cnt - число байт для копирования
Другими словами – участок памяти размером cnt байт, начиная с адреса src, копируется в участок памяти, начиная с адреса dst.
Данные, которые располагаются по адресу src, могут быть самыми разными. Это может быть однобайтовая char переменная, восьмибайтовое double число, массив, любая структура, и вообще, любой объем памяти. То есть, зная адреса и размер, вы можете свободно выполнять перенос данных из одной области памяти в другую.
Как это работает
На схеме 1 показаны сравнительные размеры некоторых типов данных.
Назначение функции memcpy – копирование данных из одного участка памяти в другой.
На схеме 2 показан пример копирования четырех байт.
На языке MQL5 это будет выглядеть следующим образом.
Пример 1. Использование 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("Значение dst="+string(dst)+" Адрес dst="+string(adr)); }
Важно понимать, что в качестве области памяти, на которые указывают dst и src, могут находиться абсолютно разные типы данных (главное одинакового размера cnt). Например, указатель src может ссылаться на double переменную (cnt=8 байт) а dst – на массив аналогичного размера char[8] или int[2].
Для памяти все равно, какое представление о ней имеет программист в данный момент. Будь это массив char[8] или просто одна long переменная или структура { int a1; int a2; }.
Это значит, что между собой можно копировать не только данные одного типа, но и разных типов. Например, пятибайтовый массив перегонять в структуру {int i; char c;} или обратно структуру в массив. Именно эта связь открывает возможность для прямой работы с API функциями.
Рассмотрим по порядку варианты использования memcpy.
Получение указателей
В примере 1 мы показали, что функция memcpy возвращает адрес переменной dst.
Это свойство можно использовать для получения адреса любой переменной (в том числе массивов и других сложных типов). Для этого достаточно в качестве параметров источника и приемника указать одну и ту же переменную. В cnt можно передать 0, так как реальное копирование выполнять необязательно.
Например, получим адрес double переменной и short массива:
Пример 2. Получение указателей на переменную #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]; //--- получим адрес массива src (т.е. адрес его первого элемента) int adr=memcpy(src, src, 0); double var; //--- получим адрес переменной var adr=memcpy(var, var, 0); }
Полученный адрес затем можно передать в требуемую функцию API или в качестве параметра структуры, а также в качестве параметра той же memcpy функции.
Копирование массивов
Как вы знаете, массив - это выделенный кусок в памяти. Размер выделенной памяти зависит от типа элементов и от их числа. Например, если тип элементов массива short и число этих элементов 10, то такой массив занимает в памяти 20 байт (так как размер short 2 байта).
Но эти 20 байт также представляются массивами из 20 char или из 5 int. В любом случае они занимают в памяти все те же 20 байт.
Для копирования массивов необходимо:
- Выделить для памяти dst требуемое число элементов (не менее чем результирующие cnt байт);
- Задать в cnt число байт, которое необходимо скопировать из src.
Пример 3. Копирование массивов #import "msvcrt.dll" int memcpy(double &dst[], double &src[], int cnt); #import //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { double src[5]; //--- считаем число байт!!! int cnt=sizeof(double)*ArraySize(src); double dst[]; ArrayResize(dst, 5); //--- скопировали массив из src в dst memcpy(dst, src, cnt); }
2. Передача структур в API функции
Допустим, вам нужно передать в API функцию указатель на заполненную структуру. Язык MQL5 ограничивает нас в передаче структур. В начале статьи мы сказали, что представление памяти может быть различно. Это означает, что требуемую структуру можно скопировать в тот тип данных, который поддерживается MQL5. В общем случае таким типом, который подходит для структур, является массив. Поэтому сначала нам придется получить массив из структуры, а затем массив отдать API-функции.
В разделе документации описан вариант копирования памяти с использованием структур. Так как передавать структуры в качестве параметров нельзя, то функцию memcpy использовать не получится, и копирование структур является единственным способом для работы.
На схеме 3 показано представление структуры из 5 переменных разных типов и ее аналог в виде char массива.
Пример 4. Копирование структур средствами MQL5 struct str1 { double d; // 8 байт long l; // 8 байт int i[3]; // 3*4=12 байт }; struct str2 { uchar c[8+8+12]; // размер структуры str1 }; //+------------------------------------------------------------------+ //| Script program start function | //+------------------------------------------------------------------+ void OnStart() { str1 src; src.d=-1; src.l=20; //--- заполняем параметры структуры ArrayInitialize(src.i, 0); str2 dst; //--- превратили структуру в байтовый массив dst=src; }
Таким вот нехитрым способом мы скопировали структуру в байтовый массив.
Чтобы пример получился более жизненным, рассмотрим функцию создания сокета.
int connect(SOCKET s, const struct sockaddr *name, int namelen);
В этой функции проблемным является второй параметр, так как принимает указатель на структуру. Но мы уже знаем, что с этим делать. Итак, начнем.
1. Запишем функцию connect для импорта допустимым в MQL5 способом:
int connect(int s, uchar &name[], int namelen);
2. Смотрим на требуемую структуру в документации:
struct sockaddr_in { short sin_family; u_short sin_port; in_addr sin_addr; // дополнительная 8 байтовая структура char sin_zero[8]; };
3. Создаем структуру с массивом аналогичного размера:
struct ref_sockaddr_in { uchar c[2+2+8+8]; };
4. После заполнения требуемой sockaddr_in структуры переводим ее в байтовый массив и передаем его в качестве параметра connect.
Ниже приведен участок кода, сделанный по этим пунктам.
Пример 5. Обращение клиентского сокета к серверу #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() { //--- после иницализации сокета выполняем соединение с хостом char ch[]; StringToCharArray("127.0.0.1", ch); //--- подготавливаем структуру sockaddr_in addrin; addrin.sin_family=AF_INET; addrin.sin_addr=inet_addr(ch); addrin.sin_port=htons(1000); //--- копируем структуру в массив ref_sockaddr_in ref=addrin; //--- соединяемся с хостом res=connect(asock, ref.c, sizeof(addrin)); //--- дальнейшая работа с сокетом }
Как видите, для работы с сокетами абсолютно не нужно делать свою DLL. Структуры напрямую передаются в API.
3. Работа с указателями от API функций
Зачастую API функции возвращают указатель на данные: на структуры или массивы. Средствами MQL5 вытянуть эти данные не представляется возможным, здесь на помощь приходит функция memcpy.
Пример работы с массивами памяти от Memory Mapping File (MMF)
При работе с MMF используется функция, которая возвращает указатель на выделенный массив памяти.
int MapViewOfFile(int hFile, int DesiredAccess, int OffsetHigh, int OffsetLow, int NumOfBytesToMap);
Чтение данных из этого массив производится простым копированием требуемого числа байт функцией memcpy.
Запись данных в массив производится аналогичным использованием memcpy.
Пример 6. Запись и чтение данных из памяти 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() { //--- открываем объект памяти int hmem=OpenFileMappingW(FILE_MAP_ALL_ACCESS, 0, "Local\\file"); //--- получаем указатель на память int view=MapViewOfFile(hmem, FILE_MAP_ALL_ACCESS, 0, 0, 0); //--- читаем первые 10 байт из памяти uchar src[10]; memcpy(src, view, 10); int num=10; //--- записываем в начало памяти 4 байтовое int число memcpy(view, num, 4); //--- закрываем просмотр UnmapViewOfFile(view); //--- закрываем объект CloseHandle(hmem); }
Как видите, сложностей по работе с указателями на массив памяти нет. И главное - для этого не надо создавать свою дополнительную DLL.
Пример работы с возвращаемыми структурами для MySQL
Одной из наболевших проблем при работе с MySQL была получение из нее данных. Функция mysql_fetch_row возвращает массив строк. Каждая строка это массив полей. Получается, что данная функция возвращает указатель на указатель. Наша задача - вытянуть все эти данные из возвращаемого указателя.
Задача немного осложнена тем, что поля являются различными типами данных, в том числе и бинарными. Это значит, что в виде string массива их представить не получится. Для получения информации о строках и размерах полей имеются функции mysql_num_rows, mysql_num_fields, mysql_fetch_lengths.
На схеме 4 показана структура представления результата в памяти.
Адреса начала трех строк собираются в массив. И адрес начала этого массива (в примере = 94) и есть то, что вернет функция mysql_fetch_row.
Ниже показан пример кода для получения данных из запроса к базе.
Пример 7. Получение данных из 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() { //--- ... предварительно проинициализированная база данных mysql //--- запрос на получение всех строк из таблицы table string query="SELECT * FROM table"; uchar aquery[]; StringToCharArray(query, aquery); //--- отправляем запрос err=mysql_real_query(mysql, aquery, StringLen(query)); int result=mysql_store_result(mysql); //--- если содержит строки if (result>0) { ulong num_rows=mysql_num_rows(result); int num_fields=mysql_num_fields(result); //--- получим указатель первую строку int r=0, row_ptr=mysql_fetch_row(result); while(row_ptr>0) { //--- получим указатель на длины столбцов текущей строки int len_ptr=mysql_fetch_lengths(result); int lens[]; ArrayResize(lens, num_fields); //--- получим размеры полей строки memcpy(lens, len_ptr, num_fields*sizeof(int)); //--- получаем поля данных int field_ptr[]; ArrayResize(field_ptr, num_fields); ArrayInitialize(field_ptr, 0); //--- получим указатели на поля memcpy(field_ptr, row_ptr, num_fields*sizeof(int)); for (int f=0; f<num_fields; f++) { ArrayResize(byte, lens[f]); ArrayInitialize(byte, 0); //--- скопируем поле в байтовый массив byte if (field_ptr[f]>0 && lens[f]>0) memcpy(byte, field_ptr[f], lens[f]); } r++; //--- получим указатель на указатель на следующую строку row_ptr=mysql_fetch_row(result); } } }
4. Чтение NULL-terminated строк из API функций
Некоторые API функции возвращают указатель на строку, но при этом не сообщают нам длину данной строки. В этой ситуации имеем дело со строками, которые заканчиваются нулем. По этому нулю и определяется конец строки. А значит, ее размер можно определить.
В библиотеке C (msvcrt.dll) уже есть функция, которая копирует из указателя на NULL-terminated строку ее содержимое в другую строку. При этом размер исходной строки она определяет сама. В качестве приемника нам лучше использовать байтовый массив, так как API часто возвращают многобайтовые строки, а не юникод.
strcpy - копирует NULL-terminated строки
char *strcpy(char *dst, const char *src); dst - указатель на строку назначения src - указатель на Null-terminated строку источник
По сути она является частным случаем функции memcpy. Так как система сама останавливает копирование на найденном нуле в строке. Эту функцию придется использовать всегда именно при работе с такими указателями.
Например, в API от MySQL есть несколько функций, которые возвращают указатели на строки. И получение данных из них c использованием strcpy - тривиальная задача.
Пример 8. Получение строк из указателей #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; //--- указатель на строку ptr=mysql_get_client_info(); if (ptr>0) strcpy(byte, ptr); Print("client_info="+CharArrayToString(byte)); //--- инициализируем базу int mysql=mysql_init(mysql); //--- переводим строки в байтовые массивы uchar ahost[]; StringToCharArray("localhost", ahost); uchar auser[]; StringToCharArray("root", auser); uchar apwd[]; StringToCharArray("", apwd); uchar adb[]; StringToCharArray("some_db", adb); uchar asocket[]; StringToCharArray("", asocket); //--- соединяемся с базой int rez=mysql_real_connect(mysql, ahost, auser, apwd, adb, port, asocket, 0); //--- узнаем состояние соединения и базы 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)); }
Заключение
Таким образом, использование трех базовых механизмов по работе с памятью – копирование структур, получение указателей и их данных по memcpy и получение строк по strcpy покрывает практически все задачи работы с различными API-функциями.
Предупреждение. Работа с memcpy и strcpy является небезопасной, если для буфера-приемника не выделен достаточный объем данных. Поэтому будьте внимательны к размеру выделяемых объемов для приема данных.
- Бесплатные приложения для трейдинга
- 8 000+ сигналов для копирования
- Экономические новости для анализа финансовых рынков
Вы принимаете политику сайта и условия использования
uchar по ходу неправильно, и там double присутствует тоже подозрительно.
И по ходу src надо указать что именно ей принадлежит и будет передаваться.
Вам уже кто-то подправил так же как и предположил. Хорошо. Думайте. Удачи.
а для mql4 будет работать на сегодняшний день?
языки (MQL4 / MQL5) сейчас полностью одинаковы - отличие в 2-3 функциях отсутствующих в MQL4 (ArrayPrint и что то еще по мелочи) и в "новых плюшках" для MQL5 - БД, DirectX и OpenCL
но статья написана 8 лет назад, сейчас MQL стал строго типизированным языком, и чтобы присвоить 2 структуры (пример в статье) нужно писать самостоятельно конструктор копирования или сериализовать структуру в массив байт и затем обратно
Интересно, можно ли получить реальный указатель на функцию. Указатели, полученные с помощью typedef, отлично работают внутри mql программы. Но к сожалению передать в dll мне их не удалось.
Если с++ + встроенный ассемблер можешь и в курсе что и как в стек кладется, а термины дальний и ближний вызов понятны, то можешь попробовать. Гарантию, что получится не дам, но по идее можно.