Руководство программиста
Глава 3. Создание модулей автоматической компиляции
§3.2. Инициализация, очистка и настройка параметров модуля
Рассматривается пример модуля автоматической компиляции, функция которого реагирует пока только на самые основные вызовы: инициализацию данных, очистку данных и вызов окна настройки. Созданный модуль регистрируется в RDS.
В качестве примера создадим модуль автоматической компиляции, который даст пользователю возможность ввести текст реакции модели блока на выполнение такта расчета (RDS_BFM_MODEL) в виде фрагмента программы на языке C и задать структуру статических переменных блока. Модуль мы сделаем настраиваемым, чтобы можно было использовать его вместе с одним из стандартных компиляторов, параметры командной строки которых различаются достаточно сильно. По умолчанию будем использовать компиляторы GCC с открытым исходным кодом: tdm-gcc (https://jmeubank.github.io/tdm-gcc/⇗) для 32-битной версии и MinGW-w64 GCC (https://www.mingw-w64.org/⇗) для 64-битной. Эти компиляторы могут устанавливаться локально вместе с RDS.
Функцию модуля компиляции можно вставить в ту же библиотеку, в которой мы размещали модели блоков, рассмотренных в предыдущих примерах, либо создать новую, начав ее исходный текст с такой же главной функции. Нам не нужно явно включать файл «RdsDef.h» – директива его включения уже содержится в файле «RdsFunc.h», который мы используем.
Сначала опишем класс личной области данных нашего модуля автокомпиляции – в нем будут содержаться все параметры, необходимые для формирования текста DLL с моделью блока и вызова компилятора:
// Класс личной области данных модуля автокомпиляции class TCAutoCompData { private: // Пути char *CompPath; // к компилятору char *LinkPath; // к редактору связей (link) char *IncludePath; // к файлам заголовков char *LibPath; // к библиотекам // Нужно ли добавлять путь к exe-файлу компилятора и редактора связей BOOL CompSetPath,LinkSetPath; // Параметры командной строки char *CompParams; // компилятора char *LinkParams; // редактора связей // Параметры исходного текста char *DllMainName; // имя главной функции DLL char *ModelFuncHdr; // заголовок функции модели char *Exported; // экспортированное имя функции // Освободить все динамические строки void FreeAllStrings(void) { rdsFree(CompPath); rdsFree(LinkPath); rdsFree(IncludePath); rdsFree(LibPath); rdsFree(CompParams); rdsFree(LinkParams); rdsFree(DllMainName); rdsFree(ModelFuncHdr); rdsFree(Exported); }; public: // Чтение параметров модуля из INI-файла void ReadFromIni(const char *IniFileName); // Запись параметров модуля в INI-файл void WriteToIni(const char *IniFileName); // Настройка параметров модуля void Setup(const char *IniFileName); // Конструктор класса TCAutoCompData(void) { IncludePath=LibPath=NULL; CompSetPath=LinkSetPath=TRUE; #ifdef RDS_WIN64 CompPath=rdsDynStrCopy( "$RDS$\\mingw-w64\\bin\\g++.exe"); LinkPath=rdsDynStrCopy(CompPath); CompParams=rdsDynStrCopy( "-Wall -m64 -g -I\"$RDSINCLUDE$\" -c \"$TEMP$\\$NAME$.cpp\" -o \"$TEMP$\\$NAME$.o\"" ); LinkParams=rdsDynStrCopy( "-shared -static -static-libgcc -static-libstdc++ -Wl,--dll \"$TEMP$\\$NAME$.o\" -o \"$TEMP$\\$NAME$.dll\" -s -m64 -luser32" ); DllMainName=rdsDynStrCopy("extern \"C\" __declspec(dllexport) BOOL APIENTRY DllMain"); ModelFuncHdr=rdsDynStrCopy( "extern \"C\" __declspec(dllexport) int RDSCALL autocompModelFunc" ); Exported=rdsDynStrCopy("autocompModelFunc"); #else CompPath=rdsDynStrCopy( "$RDS$\\tdm-gcc\\bin\\g++.exe"); LinkPath=rdsDynStrCopy(CompPath); CompParams=rdsDynStrCopy( "-shared -fpermissive -c -I\"$RDSINCLUDE$\" \"$TEMP$\\$NAME$.cpp\"" ); LinkParams=rdsDynStrCopy( "-shared -static -static-libgcc -static-libstdc++ -o \"$TEMP$\\$NAME$.dll\" \"$TEMP$\\$NAME$.o\"" ); DllMainName=rdsDynStrCopy("extern \"C\" BOOL WINAPI DllMain"); ModelFuncHdr=rdsDynStrCopy( "extern \"C\" __declspec(dllexport) int RDSCALL autocompModelFunc"); Exported=rdsDynStrCopy("autocompModelFunc@12"); #endif }; // Деструктор класса ~TCAutoCompData(){ FreeAllStrings(); }; }; //=========================================
Параметры нашего модуля описаны как логические переменные и указатели на строки в секции private класса TCAutoCompData. Все строки параметров будут динамическими, то есть память под них мы будем отводить различными сервисными функциями RDS, а освобождать ее – функцией rdsFree. Для строк будем использовать кодировку UTF8, поэтому типом указателя на строку будет «char*». В той же секции класса мы описали функцию FreeAllStrings для освобождения памяти, занятой всеми этими строками – из-за ее простоты текст этой функции мы разместили непосредственно внутри описания класса. В секции public мы описали функции для загрузки, сохранения и настройки параметров нашего модуля, а также конструктор и деструктор класса. В конструкторе мы присваиваем всем параметрам значения по умолчанию, подходящие для работы с выбранными компиляторами. Поскольку наш модуль по умолчанию будет использовать разные компиляторы для 32-битной и 64-битной версий RDS, присвоение значений по умолчанию заключено в конструкцию #ifdef RDS_WIN64 … #else … #endif. Константа RDS_WIN64 будет описана, если DLL с нашим модулем автокомпиляции собирается для 64-битной версии Windows (и, таким образом, будет загружаться в 64-битную версию RDS). При этом параметрам модуля присваиваются начальные значения, подходящие для работы с компилятором MinGW-w64 и сборки 64-битной DLL модели. В противном случае параметрам присвоятся значения для работы с компилятором tdm-gcc и сборки 32-битной DLL. Поскольку все строки параметров у нас динамические, мы присваиваем им динамические копии статических строк (символов, записанных в двойных кавычках, согласно синтаксису C), созданные при помощи сервисной функции rdsDynStrCopy. В деструкторе класса мы освобождаем все динамические строки параметров вызовом FreeAllStrings.
Параметры нашего модуля следует рассмотреть подробнее, поскольку они играют ключевую роль в его взаимодействии с компилятором.
В строках CompPath и LinkPath будут находиться пути к исполняемым файлам компилятора и редактора связей соответственно. Компилятор будет преобразовывать файл исходного текста библиотеки с моделью блока, который сформирует наш модуль, в объектный файл (обычно с расширением «.obj» или «.o»), и для этого ему может потребоваться путь к папке с файлами заголовков («.h»), который мы будем хранить в строке IncludePath. Редактор связей нужно запускать только после завершения работы компилятора, он должен будет преобразовать объектный файл в исполняемую библиотеку, и для этого ему может потребоваться путь к папке с библиотеками стандартных функций, который будет находиться в строке LibPath. Для компиляторов GCC, на которые мы рассчитываем, указывать пути к папкам заголовков и библиотек не обязательно, но мы будем делать настройки модуля как можно более универсальными.
Некоторые компиляторы не будут работать, если пути к их исполняемым файлам не добавлены в переменную окружения «PATH». В нашем модуле за необходимость этого добавления будут отвечать логические параметры CompSetPath и LinkSetPath – для пути к компилятору и редактору связей соответственно. Поскольку для компиляторов GCC, на работу с которыми модуль настроен по умолчанию, пути к исполняемым файлам необходимо добавлять в «PATH», в конструкторе класса мы присваиваем этим параметрам значение «истина» (TRUE).
И компилятору, и редактору связей в командной строке нужно передавать различные параметры, которые будут влиять на компиляцию: в частности, нужно передать необходимые пути к исходным файлам, указать, что компилируется именно библиотека (DLL), а не файл приложения (EXE) и т.д. Набор этих параметров зависит от конкретного используемого компилятора, у разных компиляторов они существенно отличаются, и мы должны дать пользователю возможность настроить их под свой компилятор. Параметры командной строки компилятора мы будем хранить в строке CompParams, а параметры редактора связей – в LinkParams.
Можно заметить, что в строках, которые мы присваиваем в конструкторе класса полям CompParams и LinkParams, содержатся символические константы «$RDSINCLUDE$» и «$TEMP$» – это стандартные символические имена, используемое во всех функциях RDS для указания путей к стандартной папке входящих в комплект RDS файлов заголовков и папке временных файлов соответственно. Помимо стандартных символических имен нам придется ввести два дополнительных для настраиваемых пользователем папок заголовков и библиотек компилятора, которые хранятся в полях класса IncludePath и LibPath. Назовем их «$INCLUDE$» и «$LIB$» соответственно. Как было указано выше, для компиляторов по умолчанию это не нужно, но для универсальности модуль должен иметь такую возможность. Кроме того, нам нужно будет указывать в строках параметров имя сформированного модулем во временной папке исходного файла на языке C, имя скомпилированного объектного файла и имя результирующего файла DLL. Мы будем использовать для этого символическое имя «$NAME$», вместо которого будет подставляться имя файла без пути и расширения (расширения будут явно указаны в самих строках параметров).
Перед запуском компилятора и редактора связей нам придется самостоятельно заменить символические имена на реальные пути и выбранное модулем имя файла исходного текста программы. К счастью, в RDS есть сервисная функция, позволяющая достаточно легко произвести такую замену.
Последняя группа параметров нашего модуля обеспечивает формирование исходного текста компилируемой библиотеки так, чтобы и компилятор, и RDS могли с ней работать. В строке DllMainName будет находиться описание главной функции DLL (без параметров), подходящее используемому компилятору. В строке ModelFuncHdr будет находиться заголовок функции модели блока, то есть имя этой функции вместе со всеми предшествующими ему описаниями, обеспечивающими экспорт этой функции из библиотеки (иначе RDS не сможет получить указатель на нее функцией Windows GetProcAddress). Наконец, в строке Exported будет находиться экспортированное имя функции, которое даст ей компилятор, и которое наш модуль автокомпиляции должен будет записать в параметры блока, чтобы скомпилированная функция модели к нему подключилась. Компиляторы часто добавляют к имени экспортированной функции специальные символы, указывающие на тип передаваемых в функцию параметров. Например, функция autocompModelFunc может получить экспортированное имя «autocompModelFunc@12» или «_autocompModelFunc@12» (такой способ формирования экспортированного имени называется «name mangling» и обычно расшифровывается в описании к конкретному компилятору). В MinGW-w64 экспортированное имя совпадает с именем самой функции, в tdm-gcc к нему добавляется «@12», это учтено в присвоении начального значения Exported в конструкторе класса.
Теперь, когда мы разобрались с тем, какие параметры нужны для работы нашего модуля, необходимо написать функции для их сохранения, загрузки и настройки. Хранить параметры модуля мы будем в файле, предлагаемом RDS, имя которого передается в модуль автокомпиляции в поле DataFile структуры RDS_COMPMODULEDATA (см. §3.1). Это имя мы будем передавать в создаваемые функции как параметр. Для чтения и записи значений мы будем использовать вспомогательный объект RDS для работы с данными в формате INI-файлов, который создается функцией rdsINICreateTextHolder. Мы уже использовали его раньше для сохранения данных блока в текстовом виде.
Функция сохранения параметров модуля выглядит следующим образом:
// Запись параметров модуля в INI-файл void TCAutoCompData::WriteToIni(const char *IniFileName) { RDS_HOBJECT ini=rdsINICreateTextHolder(TRUE); // Загружаем старый текст из файла во вспомогательный объект, если он есть if(rdsFileExists(IniFileName,NULL,NULL,NULL)) rdsSetObjectStr(ini,RDS_HINI_LOADFILE,0,IniFileName); // Создаем секцию [Paths...] rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Paths" RDS_PLATFORMNAME); // Записываем в нее пути rdsINIWriteString(ini,"Compiler",CompPath); rdsINIWriteString(ini,"Linker",LinkPath); rdsINIWriteString(ini,"Include",IncludePath); rdsINIWriteString(ini,"Lib",LibPath); rdsINIWriteBool(ini,"CompilerToPaths",CompSetPath); rdsINIWriteBool(ini,"LinkerToPaths",LinkSetPath); // Создаем секцию [Params...] rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Params" RDS_PLATFORMNAME); // Записываем в нее параметры командной строки rdsINIWriteString(ini,"Compiler",CompParams); rdsINIWriteString(ini,"Linker",LinkParams); // Создаем секцию [Func...] rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Func" RDS_PLATFORMNAME); // Записываем в нее остальные параметры rdsINIWriteString(ini,"DllMain",DllMainName); rdsINIWriteString(ini,"ModelFunc",ModelFuncHdr); rdsINIWriteString(ini,"Exported",Exported); // Сохраняем получившееся в файл rdsSetObjectStr(ini,RDS_HINI_SAVEFILE,0,TCAUTOCOMP_INI); // Уничтожение вспомогательного объекта rdsDeleteObject(ini); } //=========================================
Эта функция очень похожа на функции сохранения данных блоков, в которых мы использовали этот же вспомогательный объект, только здесь мы не передаем сформированный в объекте текст в RDS для сохранения в составе файла схемы, а записываем его в файл, вызывая функцию rdsSetObjectStr с параметром RDS_HINI_SAVEFILE.
Наш модуль автокомпиляции может работать как в 32-битной, так и в 64-битной версиях RDS, причем параметры компиляторов в двух этих версиях будут различаться. Однако, в обеих версиях в модуль будет передаваться одно и то же предлагаемое имя файла параметров. Поэтому нам нужно независимо хранить в этом файле параметры для 32-битной и 64-битной версий. Для этого к именам секций файла мы добавляем константу RDS_PLATFORMNAME, представляющую собой строку имени платформы, для которой компилируется модуль. Эта константа описана в «RdsDef.h» как "Win32" при компиляции для 32-битной версии и как "Win64" при компиляции для 64-битной. Таким образом, запись
… "Paths" RDS_PLATFORMNAME …
при компиляции в 32-битной версии даст строку «PathsWin32», а при компиляции в 64-битной – строку «PathsWin64». В результате данные разных версий будут храниться в разных секциях одного и того же файла. При этом, чтобы данные «чужой» версии не терялись (мы их не храним в классе модуля), перед записью «своих» параметров мы считываем в память весь файл, если он существует, и меняем в нем только параметры в «своих» секциях.
Функция загрузки параметров из файла будет несколько сложнее функции сохранения, поскольку при загрузке новых значений параметров модуля нам необходимо освободить динамическую память, которую занимают старые строки параметров.
// Чтение из INI-файла void TCAutoCompData::ReadFromIni(const char *IniFileName) { RDS_HOBJECT ini=rdsINICreateTextHolder(TRUE); // Загружаем текст из файла во вспомогательный объект rdsSetObjectStr(ini,RDS_HINI_LOADFILE,0,IniFileName); // Проверяем, нет ли ошибок if(rdsCommandObject(ini,RDS_HINI_GETLASTERROR)) // Ошибка return; // Читаем строки из секции [Paths...] if(rdsINIOpenSection(ini,"Paths" RDS_PLATFORMNAME)) { char *CompPath_old=CompPath, // Старые значения *LinkPath_old=LinkPath, *IncludePath_old=IncludePath, *LibPath_old=LibPath; // Загружаем новые строки CompPath=rdsDynStrCopy(rdsINIReadString(ini,"Compiler",CompPath_old,NULL)); LinkPath=rdsDynStrCopy(rdsINIReadString(ini,"Linker",LinkPath_old,NULL)); IncludePath=rdsDynStrCopy(rdsINIReadString(ini,"Include",IncludePath_old,NULL)); LibPath=rdsDynStrCopy(rdsINIReadString(ini,"Lib",LibPath_old,NULL)); // Освобождаем старые строки rdsFree(CompPath_old); rdsFree(LinkPath_old); rdsFree(IncludePath_old); rdsFree(LibPath_old); // Добавление в пути CompSetPath=rdsINIReadBool(ini,"CompilerToPaths",CompSetPath); LinkSetPath=rdsINIReadBool(ini,"LinkerToPaths",LinkSetPath); } // Читаем строки из секции [Params...] if(rdsINIOpenSection(ini,"Params" RDS_PLATFORMNAME)) { char *CompParams_old=CompParams, // Старые значения *LinkParams_old=LinkParams; // Загружаем новые строки CompParams=rdsDynStrCopy(rdsINIReadString(ini,"Compiler",CompParams_old,NULL)); LinkParams=rdsDynStrCopy(rdsINIReadString(ini,"Linker",LinkParams_old,NULL)); // Освобождаем старые строки rdsFree(CompParams_old); rdsFree(LinkParams_old); } // Читаем строки из секции [Func...] if(rdsINIOpenSection(ini,"Func" RDS_PLATFORMNAME)) { char *DllMainName_old=DllMainName, // Старые значения *ModelFuncHdr_old=ModelFuncHdr, *Exported_old=Exported; // Загружаем новые строки DllMainName=rdsDynStrCopy(rdsINIReadString(ini,"DllMain",DllMainName_old,NULL)); ModelFuncHdr=rdsDynStrCopy(rdsINIReadString(ini,"ModelFunc",ModelFuncHdr_old,NULL)); Exported=rdsDynStrCopy(rdsINIReadString(ini,"Exported",Exported_old,NULL)); // Освобождаем старые строки rdsFree(DllMainName_old); rdsFree(ModelFuncHdr_old); rdsFree(Exported_old); } // Вспомогательный объект больше не нужен rdsDeleteObject(ini); } //=========================================
В этой функции мы сначала загружаем текст из файла в созданный вспомогательный объект вызовом rdsSetObjectStr с параметром RDS_HINI_LOADFILE, а затем вызываем функцию rdsCommandObject с параметром RDS_HINI_GETLASTERROR, которая вернет ненулевое значение, если последняя операция с INI-файлом (то есть загрузка текста из файла) не удалась. В этом случае мы завершаем нашу функцию загрузки: файл параметров не существует или к нему нет доступа по каким-либо другим причинам, и загрузить параметры из него мы не можем.
Если же текст успешно считан во вспомогательный объект, мы начинаем читать из него параметры, установив название интересующей нас секции вызовом rdsINIOpenSection и получая оттуда интересующие нас строки. При этом для чтения каждой строки параметров выполняются следующие действия:
// Место для хранения старой строки char *строка_old=строка; // Создание динамической копии полученной от объекта строки строка=rdsDynStrCopy(rdsINIReadString(ini,"имя",строка_old,NULL)); // Уничтожение старой строки rdsFree(строка_old);
Мы не можем сначала уничтожить старую динамическую строку со значением параметра, а потом создать новую, получив ее из данных файла. Старое значение параметра необходимо нам в качестве значения по умолчанию, то есть того значения, которое вернет функция rdsINIReadString, если запрошенной строки в файле параметров не окажется. Поэтому для каждой строки параметров мы сохраняем старое значение во вспомогательной переменной, затем получаем указатель на строку с нужным нам названием, вызывая rdsINIReadString (при этом сохраненное значение передается в ее третьем параметре как значение по умолчанию), делаем динамическую копию этой строки функцией rdsDynStrCopy и присваиваем указатель на него полю класса, которое соответствует данному параметру. Только после этого мы освобождаем память, занятую старым значением, при помощи функции rdsFree.
Теперь нужно написать функцию, которая будет открывать окно настройки модуля. Технически она ничем не будет отличаться от функций настройки блоков, которых мы рассмотрели уже немало:
// Настройка модуля автокомпиляции // ВАЖНО: Исходный текст программы должен быть записан в UTF8, // в противном случае необходимо использовать версии функций // с суффиксом "W" и символьные константы с префиксом "L" void TCAutoCompData::Setup(const char *IniFileName) { RDS_HOBJECT window; // Объект-окно const char exefilter[]= // Фильтр для диалога выбора файла "Исполняемые файлы (*.exe)|*.exe\nВсе файлы|*.*"; // Создание окна window=rdsFORMCreate(TRUE,-1,-1,"Параметры модуля"); // Создание вкладки rdsFORMAddTab(window,0,"Пути"); // Поле ввода выбора EXE-файла компилятора rdsFORMAddEdit(window,0,1,RDS_FORMCTRL_OPENDIALOG, "Компилятор:",300); rdsSetObjectStr(window,1,RDS_FORMVAL_LIST,exefilter); rdsSetObjectStr(window,1,RDS_FORMVAL_VALUE,CompPath); // Нужно устанавливать путь? rdsFORMAddEdit(window,0,100,RDS_FORMCTRL_CHECKBOX, "Добавлять папку в путь",300); rdsSetObjectInt(window,100,RDS_FORMVAL_VALUE,CompSetPath); // Поле ввода выбора EXE-файла редактора связей rdsFORMAddEdit(window,0,2,RDS_FORMCTRL_OPENDIALOG, "Редактор связей:",300); rdsSetObjectStr(window,2,RDS_FORMVAL_LIST,exefilter); rdsSetObjectStr(window,2,RDS_FORMVAL_VALUE,LinkPath); // Нужно устанавливать путь? rdsFORMAddEdit(window,0,200,RDS_FORMCTRL_CHECKBOX, "Добавлять папку в путь",300); rdsSetObjectInt(window,200,RDS_FORMVAL_VALUE,LinkSetPath); // Выбор папки файлов заголовков rdsFORMAddEdit(window,0,3,RDS_FORMCTRL_DIRDIALOG, "Папка заголовков:",300); rdsSetObjectStr(window,3,RDS_FORMVAL_VALUE,IncludePath); // Выбор папки файлов библиотек rdsFORMAddEdit(window,0,4,RDS_FORMCTRL_DIRDIALOG, "Папка библиотек:",300); rdsSetObjectStr(window,4,RDS_FORMVAL_VALUE,LibPath); // Создание вкладки rdsFORMAddTab(window,1,"Параметры"); // Параметры командной строки компилятора rdsFORMAddEdit(window,1,5,RDS_FORMCTRL_MULTILINE, "Параметры компилятора:",300); rdsSetObjectInt(window,5,RDS_FORMVAL_MLHEIGHT,3*24);// Высота rdsSetObjectStr(window,5,RDS_FORMVAL_VALUE,CompParams); // Параметры командной строки редактора связей rdsFORMAddEdit(window,1,6,RDS_FORMCTRL_MULTILINE, "Параметры редактора связей:",300); rdsSetObjectInt(window,6,RDS_FORMVAL_MLHEIGHT,3*24);// Высота rdsSetObjectStr(window,6,RDS_FORMVAL_VALUE,LinkParams); // Создание вкладки rdsFORMAddTab(window,2,"Описания"); // Имя главной функции rdsFORMAddEdit(window,2,7,RDS_FORMCTRL_EDIT, "Имя главной функции DLL:",300); rdsSetObjectStr(window,7,RDS_FORMVAL_VALUE,DllMainName); // Заголовок модели rdsFORMAddEdit(window,2,8,RDS_FORMCTRL_MULTILINE, "Заголовок функции модели:",300); rdsSetObjectInt(window,8,RDS_FORMVAL_MLHEIGHT,2*24);// Высота rdsSetObjectStr(window,8,RDS_FORMVAL_VALUE,ModelFuncHdr); // Экспортированное имя rdsFORMAddEdit(window,2,9,RDS_FORMCTRL_EDIT, "Экспортированное имя:",300); rdsSetObjectStr(window,9,RDS_FORMVAL_VALUE,Exported); // Открытие окна if(rdsFORMShowModalEx(window,NULL)) // Нажата OK { // Освобождаем старые строки параметров FreeAllStrings(); // Делаем динамические копии всех строк из полей ввода // и присваиваем их полям класса #define GETDYNRELPATH(x) rdsGetRelFilePath(x,rdsGetSystemPath(RDS_GSPAPPPATH),NULL) CompPath=GETDYNRELPATH(rdsGetObjectStr(window,1,RDS_FORMVAL_VALUE)); LinkPath=GETDYNRELPATH(rdsGetObjectStr(window,2,RDS_FORMVAL_VALUE)); IncludePath=GETDYNRELPATH(rdsGetObjectStr(window,3,RDS_FORMVAL_VALUE)); LibPath=GETDYNRELPATH(rdsGetObjectStr(window,4,RDS_FORMVAL_VALUE)); #undef GETDYNRELPATH CompParams=rdsDynStrCopy(rdsGetObjectStr(window,5,RDS_FORMVAL_VALUE)); LinkParams=rdsDynStrCopy(rdsGetObjectStr(window,6,RDS_FORMVAL_VALUE)); DllMainName=rdsDynStrCopy(rdsGetObjectStr(window,7,RDS_FORMVAL_VALUE)); ModelFuncHdr=rdsDynStrCopy(rdsGetObjectStr(window,8,RDS_FORMVAL_VALUE)); Exported=rdsDynStrCopy(rdsGetObjectStr(window,9,RDS_FORMVAL_VALUE)); CompSetPath=rdsGetObjectInt(window,100,RDS_FORMVAL_VALUE); LinkSetPath=rdsGetObjectInt(window,200,RDS_FORMVAL_VALUE); // Запись изменившихся параметров в INI-файл WriteToIni(IniFileName); } // Уничтожение окна rdsDeleteObject(window); } //=========================================
Эта функция при помощи вспомогательного объекта RDS открывает окно с тремя вкладками «», «» и «», на которых расположены поля для ввода описанных выше параметров модуля. Поля на вкладке «» снабжены кнопками для вызова диалогов выбора файлов и папок, чтобы пользователь мог выбрать в них местоположение исполняемых файлов компилятора и редактора связей и папок библиотек и заголовочных файлов, вместо того, чтобы вводить их вручную. Если пользователь закроет это окно нажатием кнопки «», данные из полей ввода окна будут переписаны в класс личной области данных модуля (функция Setup является членом этого класса). При этом все выбранные пользователем пути будут преобразованы в относительные от папки установки RDS при помощи описанного здесь же макроса GETDYNRELPATH. Затем новые параметры модуля будут записаны в INI-файл вызовом только что написанной нами функции WriteToIni.
Наконец, напишем функцию модуля автокомпиляции – именно она будет создавать объект класса TCAutoCompData и вызывать его функции. Назовем функцию нашего модуля «TestCAutoComp»:
// Функция модуля автокомпиляции extern "C" __declspec(dllexport) int RDSCALL TestCAutoComp( int CallMode, // Событие RDS_PCOMPMODULEDATA ModuleData, // Данные модуля LPVOID ExtParam) // Дополнительные параметры { // Приведение указателя на личную область данных // к правильному типу TCAutoCompData *data=(TCAutoCompData*)(ModuleData->ModuleData); switch(CallMode) { // Инициализация модуля case RDS_COMPM_INIT: // Создание личной области данных модуля ModuleData->ModuleData=data=new TCAutoCompData(); // Чтение параметров из INI-файла data->ReadFromIni(ModuleData->DataFile); break; // Очистка данных модуля case RDS_COMPM_CLEANUP: delete data; // Удаление личной области модуля break; // Вызов окна настройки модуля case RDS_COMPM_SETUP: data->Setup(ModuleData->DataFile); break; } return RDS_COMPR_DONE; } //=========================================
Можно заметить, что эта функция построена точно так же, как все рассмотренные нами ранее функции моделей блоков: внутри нее находится оператор switch, который, в зависимости от значения параметра CallMode, выполняет то или иное действие. В данном случае функция реагирует всего на три события. При инициализации модуля (RDS_COMPM_INIT) мы создаем объект класса TCAutoCompData и записываем указатель на него в поле ModuleData структуры данных модуля, переданной во втором параметре нашей функции. Этот объект будет служить модулю в качестве личной области данных. Сразу после создания мы вызываем для этого объекта функцию ReadFromIni, чтобы параметры модуля загрузились из INI-файла, в качестве имени которого передается предлагаемое RDS имя файла параметров модуля ModuleData->DataFile (поле DataFile структуры данных модуля RDS_COMPMODULEDATA).
При очистке данных модуля (RDS_COMPM_CLEANUP), то есть перед выгрузкой его из памяти, мы уничтожаем ранее созданный объект. В самом начале функции мы уже привели значение поля ModuleData->ModuleData, в котором хранится указатель на объект, к типу TCAutoCompData* и присвоили его переменной data, так что во всех реакциях, кроме RDS_COMPM_INIT (на этот момент объект еще не создан), мы можем пользоваться ее значением.
Наконец, при запросе пользователем окна настройки модуля (RDS_COMPM_SETUP) мы просто вызываем написанную нами функцию Setup, которая откроет окно и, при необходимости, запишет измененные параметры в INI-файл.
Независимо от события, из-за которого вызвана наша функция, она возвращает константу RDS_COMPR_DONE, сигнализирующую RDS об отсутствии ошибок. В дальнейшем, когда мы добавим к нашей функции новые реакции, мы изменим это, но пока никакой обработки ошибок нам не требуется.
Скомпилировав DLL с нашим новым модулем автокомпиляции, мы должны зарегистрировать этот модуль в RDS, чтобы пользователь смог его настраивать и подключать к блокам. Для этого, во-первых, необходимо переписать скомпилированную DLL (точнее, две DLL, если мы скомпилировали и 32-битную, и 64-битную версии) в какую-либо папку с известным нам именем, и, во-вторых, создать файл описания модуля и разместить его либо в папке «Extensions\AutoComp\» внутри папки установки RDS, либо в папке «UserExtensions\AutoComp\» внутри папки настроек (см. §2.18). Поскольку при отладке и дальнейшей модификации нашего модуля мы будем постоянно перекомпилировать его DLL, разместим и их, и файл описания модуля в папке настроек в подпапке «UserExtensions\AutoComp\» – туда гарантированно есть доступ по записи. Для простоты дальнейшего изложения будем считать, что:
- мы скомпилировалиии и 32-битную, и 64-битную версии модуля;
- DLL модуля в 32-битной версии называется «p3.32.dll», в 64-битной – «p3.64.dll»;
- DLL модуля находятся в «UserExtensions\AutoComp\»;
- 32-битная версия компилировалась при помощи tdm-gcc, 64-битная – при помощи MinGW-w64.
Тогда файл описания модуля может выглядеть следующим образом:
autocomp
file "$INI$\\UserExtensions\\AutoComp\\p3.32.dll" func "TestCAutoComp@12"
file64 "$INI$\\UserExtensions\\AutoComp\\p3.64.dll" func64 "TestCAutoComp"
title default "Тестовый модуль из руководства"
title English "Test module from the manual"
format "ProgrammersManual.CPlusPlus.Test"
seq 20001
В этом файле указывается, что:
- в модуле нет ограничения разрядности поддерживаемых версий (нет слов win32 или win64);
- для 32-битной версии экспортирована функция «TestCAutoComp@12» в библиотеке «p3.32.dll» в подпапке «UserExtensions\AutoComp\» папки настроек RDS («@12» к имени функции добавлено из-за того, что мы предполагаем, что библиотека компилировалась 32-битным tdm-gcc);
- для 64-битной версии экспортирована функция «TestCAutoComp» в библиотеке «p3.64.dll» в той же папке;
- по умолчанию модуль называется «Тестовый модуль из руководства»;
- в английской локализации RDS модуль называется «Test module from the manual»;
- условное название формата исходного текста моделей для этого модуля – «ProgrammersManual.CPlusPlus.Test» (сейчас нам это не нужно, но пригодится в §3.5, где мы создадим еще один модуль для этих же моделей);
- по умолчанию условный порядковый номер модуля – 20001 (это с большой вероятностью поставит его в конец списка).
Назовем этот файл «progman_sample.acm» и поместим его в папку «UserExtensions\AutoComp\» папки настроек. Теперь, если открыть окно со списком модулей, вызвав пункт главного меню RDS «», можно будет увидеть наш модуль в списке зарегистрированных (рис. 123).
Рис. 123. Результат регистрации созданного модуля в RDS
Наш новый модуль пока мало что умеет, но его уже можно настроить на установленный на данной машине компилятор. Для этого в окне списка модулей следует дважды щелкнуть на его названии или, выбрав его в списке, нажать кнопку сверху справа от списка. При этом модуль загрузится в память RDS (что приведет к вызову его функции с параметром RDS_COMPM_INIT и загрузке параметров из INI-файла, если, конечно, он существует), после чего его функция будет вызвана с параметром RDS_COMPM_SETUP, в результате чего она, в свою очередь, вызовет функцию Setup класса личной области данных модуля, которая откроет окно настройки (рис. 124).
Рис. 124. Окно настройки модуля (в 32-битной версии)
При самом первом вызове окна настроек INI-файл параметров модуля еще не существует, поэтому все параметры будут иметь значения по умолчанию. Однако, если нажать кнопку «», функция Setup создаст файл и запишет в него введенные значения, после чего они будут загружаться в личную область данных модуля при каждой его инициализации.
Поскольку наш модуль в данный момент не обслуживает ни одного блока схемы (это пока невозможно, мы еще не ввели в его функцию необходимые реакции), закрытие окна настройки приведет к его немедленной выгрузке из памяти.