Создание ex5-библиотек и export функций

Для описания библиотеки следует добавить в исходный код главного (компилируемого) модуля директиву #property library (обычно, в начало файла).

#property library

Указание этой директивы в любых других файлах, подключаемых в процесс компиляции через #include, эффекта не имеет.

Свойство library указывает компилятору, что данный ex5-файл является библиотекой: пометка об этом хранится в заголовке ex5-файла.

Для библиотек в MetaTrader 5 зарезервирована отдельная папка MQL5/Libraries. В ней можно организовать иерархию вложенных папок, как и для других типов программ в MQL5.

Библиотеки не участвуют напрямую в обработке событий, и потому компилятор не требует в коде наличия каких-либо стандартных обработчиков. Однако вы сможете вызывать экспортированные функции библиотеки из обработчиков событий той MQL-программы, к которой библиотека подключена.

Для экспорта функции из библиотеки достаточно пометить её специальным ключевым словом export. Данный модификатор должен располагаться в самом конце заголовка функции.

тип_результата идентификатор_функции ( [ тип_параметра идентификатор_параметра
                                       [ = значение_по_умолчанию] ...] ) export
{
   ...
}

Параметры должны быть простых типов или строками, структурами с полями таких типов или их массивами. Для объектных типов MQL5 допустимы указатели и ссылки (про ограничения при импорте DLL-библиотек см. соответствующий раздел).

Приведем несколько примеров. Параметр — простое число:

double Algebraic2(const double xexport
{
   return x / sqrt(1 + x * x); 
}

Параметры — указатель на объект и ссылка на указатель (позволяет присвоить указатель внутри функции).

class X
{
public:
   X() { Print(__FUNCSIG__); }
};
void setObject(const X *objexport { ... }
void getObject(X *&objexport { obj = new X(); }

Параметр — структура:

struct Data
{
   int value;
   double data[];
   Data(): value(0) { }
   Data(const int i): value(i) { ArrayResize(datai); }
};
   
void getRefStruct(const int iData &dataexport { ... }

Экспортировать можно только функции, но не классы или структуры целиком. Частично данные ограничения можно обойти с помощью указателей и ссылок, что мы рассмотрим подробнее в дальнейшем.

Шаблоны функций нельзя объявлять с ключевым словом export и в директиве #import.

Модификатор export указывает компилятору внести функцию в таблицу экспортируемых функций внутри данного исполняемого ex5-файла. За счет этого такие функции становятся доступными ("видимыми") из других MQL-программ, где их можно использовать после импорта с помощью специальной директивы #import.

Все функции, которые планируется экспортировать, должны быть помечены модификатором export, хотя главная программа не обязана затем импортировать их все, а может — лишь необходимые.

Если забыть проэкспортировать какую-либо функцию, но включить её в директиву импорта в главной MQL-программе, то при запуске последней возникнет ошибка вида:

cannot find 'функция' in 'библиотека.ex5'
unresolved import function call

Аналогичная проблема возникнет, если в описании экспортированной функции и её импортируемом прототипе есть разночтения. Это может произойти, например, если забыть перекомпилировать библиотеку или главную программу после внесения изменений в программный интерфейс, который обычно описан в отдельном заголовочном файле.

Отладка библиотек невозможна, поэтому при необходимости следует иметь вспомогательный скрипт или другую MQL-программу, которая собирается из исходных кодов библиотеки в режиме отладчика и может исполняться с точками остановки или пошагово. Разумеется, при этом потребуется эмулировать вызовы экспортируемых функций с использованием неких реальных или искусственных данных.

Для dll-библиотек описание экспортируемых функций делается по-разному, в зависимости от языка программирования, на котором они создаются. Ищите подробности в документации избранных вами сред разработки.

Рассмотрим пример простой библиотеки MQL5/Libraries/MQL5Book/LibRand.mq5, из которой экспортируется несколько функций с различными типами параметров и результатов. Библиотека предназначена для генерации случайных данных:

  • числовых данных с псевдо-нормальным распределением;
  • строк со случайными символами из заданных наборов (может пригодиться для паролей).

В частности, получить одно случайное число можно с помощью функции PseudoNormalValue, в которой параметрами задаются матожидание и дисперсия.

double PseudoNormalValue(const double mean = 0.0const double sigma = 1.0,
   const bool rooted = falseexport
{
   // используем готовый sqrt при массовой генерации в цикле в PseudoNormalArray
   const double s = !rooted ? sqrt(sigma) : sigma
   const double r = (rand() - 16383.5) / 16384.0// [-1,+1] исключая границы
   const double x = -(log(1 / ((r + 1) / 2) - 1) * s) / M_PI * M_E + mean;
   return x;
}

Для заполнения массива случайными значениями в заданном количестве (n) и с нужным распределением реализована функция PseudoNormalArray.

bool PseudoNormalArray(double &array[], const int n,
   const double mean = 0.0const double sigma = 1.0export
{
   bool success = true;
   const double s = sqrt(fabs(sigma)); // передаем готовый sqrt при вызове PseudoNormalValue
   ArrayResize(arrayn);
   for(int i = 0i < n; ++i)
   {
      array[i] = PseudoNormalValue(meanstrue);
      success = success && MathIsValidNumber(array[i]);
   }
   return success;
}

Для генерации одной случайной строки написана функция RandomString, которая "выбирает" из предоставленного набора символов (pattern) заданное количество (length) произвольных символов. Когда параметр pattern оставлен пустым (по умолчанию), подразумевается полный набор букв и цифр. Для его получения используются вспомогательные функции StringPatternAlpha и StringPatternDigit, которые также являются экспортируемыми (в книге не приводятся, см. исходный код).

string RandomString(const int lengthstring pattern = NULLexport
{
   if(StringLen(pattern) == 0)
   {
      pattern = StringPatternAlpha() + StringPatternDigit();
   }
   const int size = StringLen(pattern);
   string result = "";
   for(int i = 0i < length; ++i)
   {
      result += ShortToString(pattern[rand() % size]);
   }
   return result;
}

В целом, для работы с библиотекой необходимо опубликовать заголовочный файл с описанием всего, что в ней должно быть доступно извне (а подробности внутренней реализации можно и нужно скрывать). В нашем случае такой файл называется MQL5Book/LibRand.mqh. В частности, в нем описываются пользовательские типы (в нашем случае, перечисление STRING_PATTERN) и прототипы функций.

Хотя точный синтаксис блока #import нам еще не известен, это не должно сказываться на понятности деклараций внутри него: здесь повторяются заголовки экспортируемых функций, но уже без ключевого слова export.

enum STRING_PATTERN
{
   STRING_PATTERN_LOWERCASE = 1// только строчные буквы
   STRING_PATTERN_UPPERCASE = 2// только заглавные буквы
   STRING_PATTERN_MIXEDCASE = 3  // оба регистра
};
   
#import "MQL5Book/LibRand.ex5"
string StringPatternAlpha(const STRING_PATTERN _case = STRING_PATTERN_MIXEDCASE);
string StringPatternDigit();
string RandomString(const int lengthstring pattern = NULL);
void RandomStrings(string &array[], const int nconst int minlength,
   const int maxlengthstring pattern = NULL);
void PseudoNormalDefaultMean(const double mean = 0.0);
void PseudoNormalDefaultSigma(const double sigma = 1.0);
double PseudoNormalDefaultValue();
double PseudoNormalValue(const double mean = 0.0const double sigma = 1.0,
   const bool rooted = false);
bool PseudoNormalArray(double &array[], const int n,
   const double mean = 0.0const double sigma = 1.0);
#import

Тестовый скрипт, использующий данную библиотеку, напишем в следующем разделе, после изучения директивы #import.