[Из песочницы] Пишем библиотеку DLL для Metastock с нуля. Часть 1

Metastock — наверное, самая известная программа для технического анализа рынка. Данная программа может подключать внешние библиотеки DLL, написанные пользователями для создания своих торговых стратегий, используя полную мощь традиционных языков программирования, таких как C или Паскаль.Занявшись поиском в интернете, с удивлением обнаружил полное отсутствие информации по данной теме. Единственная статья, которая оказалась достойной внимания: «Что такое Metastock Developer’s Kit?» (mdk), где описывается коротенький пример на Delphi, там же можно скачать MDK.В данной статье я постараюсь заполнить этот пробел и описать процесс создания библиотеки внешних функций Metastock (MSX DLL) по шагам на языке C/C++. Все примеры писались в среде Visual Studio 2010.Немного теории Функции, которые реализованы в MSX DLL ведут себя точно также как и стандартные встроенные функции Metastock«а. Все функции MSX DLL возвращают массив данных. Каждая внешняя функция имеет уникальное имя.ExtFml («DLL Name.Function Name», arg1, …, argn), где

arg1…argn — аргументы функции (n <= 9).

Каждый аргумент может быть одним из четырех типов: • Массивы данных (например, Open, High, Low, Close, и т.д., или результаты другой функции)• Числовые константы (например, 5, -5, 20.55 и т.д.)• Строковые константы (например, «Hello Woodpecker» и др.)• Индивидуальные наборы (например, Simple, Triangular, и т.д.)Как их определять и использовать мы рассмотрим позже, на конкретных примерах.

Функции, определенные в MSX DLL делятся на две категории: • Функции инициализации• Функции расчета (или внешние функции).Функции инициализации вызываются MetaStock«ом во время запуска, чтобы определить, какие внешние функции доступны и какие аргументы они требуют. Функции расчета доступны для пользователей MetaStock«а. Все функции ссылаются на структуры данных определенных в файле MSXStruc.h. Этот файл необходим для компиляции нашей DLL.Прежде чем писать свои функции необходимо прописать несколько служебных MSX-функций (функции инициализации), для того что бы Mетосток мог общаться с вашей DLL. Их четыре: • MSXInfo — обязательная функция. Всегда вызывается во время инициализации и проверяет, является ли наша DLL MSX DLL и возвращает основную информацию о ней (авторство, количество наших функций, версия).• MSXNthFunction — обязательная функция. Вызывается один раз для каждой функции указанной MSXInfo и нумерует, начиная с нуля наши функции. Здесь прописываются имена функций (c учетом регистра), их дескриптор и количество аргументов у каждой.• MSXNthArg — эта функция обязательна, только если у наших функций есть аргументы. Вызывается во время инициализации для каждого аргумента наших функций.• MSXNthCustomString — эта функция обязательна, только если у наших функций есть custom-аргументы.

Для начала теории достаточно, приступим к написанию первой DLL. В этом примере будет показано, как вывести массив цен, дату и время в наш индикатор.

Пишем код Открываем VS 2010. Первым делом создадим пустой проект.File→New→Project→Other Languages→Visual C++→Win32→Win32 Console Application.Задаем имя нашей библиотеки (пусть UsePrice) и нажимаем OK.Открывается Win32 Application Wizard.Next→ и ставим галочки на DLL и Empty project, нажимаем Finish. Пустой проект создан. Добавим в него три файла UsePrice.cpp, UsePrice.def, MSXStruc.h. Правой кнопкой по проекту Add→New Item…, выбираем файл с соответствующим расширением и задаем ему соответствующее имя. Нажимаем Add.В файле UsePrice.cpp пишем наш код./*— Шаг 1 — Заголовки

Коментарии Директива #include дает указание компилятору читать еще один исходный файл —в дополнение к тому файлу, в котором находится сама эта директива.Имя исходного файла должно быть заключено в двойные кавычки или в угловые скобки.Заголовки для библиотечных функций C

*/ #include #include #include #include #include

// Обязательно включить — определяет MSX Data Structures (см. файл MSXStruc.h).

#include «MSXStruc.h» /*— Шаг 2 — ЭкспортКоментарии Директива #define определяет идентификатор и последовательность символов, которой будет замещаться данный идентификатор при его обнаружении в текстепрограммы. Идентификатор также называется именем макроса, а процессзамещения называется подстановкой макроса. Стандартный вид директивыследующий:#define имя_макроса последовательность_символов

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

Например, если необходимо использовать TRUE для значения 1, a FALSE для 0, то можно объявить следующие два макроса:

#define TRUE 1#define FALSE 0

В результате, если компилятор обнаружит в тексте программы TRUE или FALSE, тоон заменит их на 1 и 0 соответственно.В нашем случае если в коде встречается DLL_EXPORT, то выполняется макрос

extern «C» __declspec (dllexport)

Чтобы нормально спрягать код на C++ с C, где name mangling отсутствует, введеноextern «C», которое отключает этот механизм у экспортируемых имен переменных/функций.extern «C» обозначает использование простой генерации сигнатуры функции (в стиле языка С) при получении объектных файлов. В частности, это запрещаеткомпилятору C++ производить «декорацию» имени функции дополнительнымисимволами при экспорте в DLL.Атрибут класса хранения dllexport — это специфическое для Microsoft расширениеязыков C и C++. Его можно использовать для экспорта функций, данных и объектовв библиотеку DLL.__declspec (dllexport) declaratorЭтот атрибут явно определяют интерфейс DLL для ее клиента, который может бытьисполняемым файлом или другой библиотекой DLL. Объявление функций какdllexport позволяет обходиться без файла определения модуля (DEF), по крайнеймере, в отношении спецификации экспортированных функций.

*/ #define DLL_EXPORT extern «C» __declspec (dllexport) /*— Шаг 3 — Функции инициализацииКоментарии Прежде чем писать свои функции необходимо прописать несколько служебныхMSX-функций, для того, чтобы Mетосток мог общаться с нашей DLL.В первом примере мы будем писать одну функцию без аргументов, поэтомуMSXNthArg и MSXNthCustomString нам не понадобятся.MSXInfoЗамените поле вашейинформацией об авторстве, а также установите количество функций = 1.strncpy (strDest, strSource, count) — копирует символы одной строки в другую.• strDest — строка назначения.• strSource — иссходная строка.• count — число символов для копирования.strncpy требует обязательный заголовок .На strncpy компилятор VS C++ выдает предупреждения, можно использоватьstrncpy_s (проверял — все работает, но не могу сказать насколько это корректно).

*/ DLL_EXPORT BOOL __stdcall MSXInfo (MSXDLLDef *a_psDLLDef) { strncpy (a_psDLLDef→szCopyright, «VS 2010 C++ MSX DLL, Copyright © Pretzel, 2014», sizeof (a_psDLLDef→szCopyright)-1); a_psDLLDef→iNFuncs = 1; // число функций. a_psDLLDef→iVersion = MSX_VERSION; // версия return MSX_SUCCESS; } /*Коментарии MSXNthFunctionЗначение, копируемое в a_sFuncDef→szFunctionName должно точно соответствоватьнашей функции, которую мы будете экспортировать, с учетом регистра.Изменяемые значения: • Name — имя нашей функции.• Description — то, как она будет читаться в Metastock’е (в окне 'Paste Functions').• Arguments — число аргументов нашей функции.На strcpy компилятор VS C++ выдает предупреждения, можно использовать strcpy_s (проверял — все работает, но не могу сказать насколько это корректно).

*/ DLL_EXPORT BOOL __stdcall MSXNthFunction (int a_iNthFunc, MSXFuncDef *a_psFuncDef) { BOOL l_bRtrn = MSX_SUCCESS;

switch (a_iNthFunc) { case 0: strcpy (a_psFuncDef→szFunctionName, «Price»); strcpy (a_psFuncDef→szFunctionDescription, «FirstFunction»); a_psFuncDef→iNArguments = 0; // число аргументов break; default: l_bRtrn = MSX_ERROR; break; } return l_bRtrn; } /*— Шаг 4 — Наша функция

*/

DLL_EXPORT BOOL __stdcall Price (const MSXDataRec *a_psBasic, const MSXDataInfoRecArgsArray *a_psArrayArgs, const MSXNumericArgsArray *a_psNumericArgs, const MSXStringArgsArray *a_psStringArgs, const MSXCustomArgsArray *a_psCustomArgs, MSXResultRec *a_psResult)

{ // Выводим в наш индикатор Close for (int i= a_psBasic →sClose.iFirstValid; i<= a_psBasic ->sClose.iLastValid; i++) a_psResult→psResultArray→pfValue[ i ] = a_psBasic →sClose.pfValue[ i ]; // Заменив предыдущую строку на: // a_psResult→psResultArray→pfValue[ i ] = float (a_psBasic →psDate[i].lDate); // или // a_psResult→psResultArray→pfValue[ i ] = float (a_psBasic →psDate[i].lTime); // соответственно получим дату или время.

return MSX_SUCCESS; }

Далее в файле UsePrice.def введем следующий код: LIBRARY UsePrice EXPORTS MSXInfo MSXNthFunction Price Заполняем MSXStruc.h кодом.MSXStruc.h #ifndef MSX_Structures_h #define MSX_Structures_h

/* Structures required for MetaStock External Function DLL interface */

#ifndef BOOL typedef int BOOL; #endif

#ifndef TRUE #define TRUE 1 #endif

#ifndef FALSE #define FALSE 0 #endif

// -------------------------------------------------------------------------- // Return this DLL version constant // -------------------------------------------------------------------------- const int MSX_VERSION = 1;

// -------------------------------------------------------------------------- // Maximum number of aguments // -------------------------------------------------------------------------- const int MSX_MAXARGS = 9;

// -------------------------------------------------------------------------- // Maximum string size (does not include MSXString arguments passed in to // external functions). // -------------------------------------------------------------------------- const int MSX_MAXSTRING = 100;

// -------------------------------------------------------------------------- // The following two BOOL return values are returned from MSX functions // -------------------------------------------------------------------------- const BOOL MSX_SUCCESS = FALSE; const BOOL MSX_ERROR = TRUE;

// ---------------------------------------------------------------------------------------- // There are four potential argument types // ---------------------------------------------------------------------------------------- const int MSXDataArray = 0; const int MSXNumeric = 1; const int MSXString = 2; const int MSXCustom = 3;

// ---------------------------------------------------------------------------------------- // The following structure is used by the exported function MSXInfo // ---------------------------------------------------------------------------------------- typedef struct { char szCopyright[MSX_MAXSTRING]; int iNFuncs; int iVersion; } MSXDLLDef;

// ---------------------------------------------------------------------------------------- // The following structure is used by the exported function MSXNthFunction // ---------------------------------------------------------------------------------------- typedef struct { char szFunctionName[MSX_MAXSTRING]; char szFunctionDescription[MSX_MAXSTRING]; int iNArguments; } MSXFuncDef;

// ---------------------------------------------------------------------------------------- // The following structure is used by the exported function MSXNthArg // ---------------------------------------------------------------------------------------- typedef struct { int iArgType; // argtype constants: // 0 DataArray // 1 Numeric // 2 String // 3 CustomType char szArgName[MSX_MAXSTRING]; int iNCustomStrings; } MSXFuncArgDef;

// ---------------------------------------------------------------------------------------- // The following structure is used by the exported function MSXNthCustomString // ---------------------------------------------------------------------------------------- typedef struct { char szString[MSX_MAXSTRING]; int iID; } MSXFuncCustomString;

// ---------------------------------------------------------------------------------------- // the following datastructures are passed into and out of the user-written external // calculation functions. // ---------------------------------------------------------------------------------------- typedef struct { long lDate; long lTime; } MSXDateTime;

typedef struct { float *pfValue; int iFirstValid; int iLastValid; } MSXDataInfoRec; typedef struct { MSXDateTime *psDate; MSXDataInfoRec sOpen; MSXDataInfoRec sHigh; MSXDataInfoRec sLow; MSXDataInfoRec sClose; MSXDataInfoRec sVol; MSXDataInfoRec sOI; MSXDataInfoRec sInd; char *pszSecurityName; // Security Name char *pszSymbol; // Security Symbol char *pszSecurityPath; // Path where security is stored (may be in UNC format) char *pszOnlineSource; // Unused — reserved for future use… int iPeriod; // 'D’aily, 'W’eekly, 'M’onthly, 'Q’uarterly, 'I’ntraday int iInterval; // For period='I’ntraday only. 0=tick, other value = minutes compression. int iStartTime; // HHMM format. Undefined for non-intraday period. int iEndTime; // HHMM format. Undefined for non-intraday period. int iSymbolType; // Unused — reserved for future use } MSXDataRec;

typedef struct { // possible for MSX_MAXARGS data arrays MSXDataInfoRec *psDataInfoRecs[MSX_MAXARGS]; // pointers to the data arrays int iNRecs; // number of arrays present (just a sanity check) } MSXDataInfoRecArgsArray; typedef struct { float fNumerics[MSX_MAXARGS]; // possible for MSX_MAXARGS numerics int iNRecs; // also a sanity check — func knows how many there should be. } MSXNumericArgsArray; typedef struct { char *pszStrings[MSX_MAXARGS]; // possible for MSX_MAXARGS strings int iNRecs; // ditto the above } MSXStringArgsArray;

typedef struct { int iCustomIDs[MSX_MAXARGS]; // numeric ID associated with a custom arg int iNRecs; // ditto the above } MSXCustomArgsArray; typedef struct { MSXDataInfoRec *psResultArray; // Pointer to result array char szExtendedError[MSX_MAXSTRING]; // Extended Error string } MSXResultRec;

#endif

В Visual Studio нажимаем Build → Build Solution (F6) и получаем нашу DLL. Отправляем ее в папку «External Function DLLs» в Metastock«e и можно пользоваться. Наш индикатор будет иметь следующий вид: ExtFml («UsePrice.Price»)

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

© Habrahabr.ru