Руководство программиста
Глава 3. Создание модулей автоматической компиляции
Описывается принцип создания модулей автокомпиляции, которые по введенному пользователем тексту могут формировать и компилировать модель блока. Рассматривается пример такого модуля для создания простых моделей в синтаксисе языка C.
§3.1. Принцип работы модулей автокомпиляции
Рассматривается структура данных модуля автокомпиляции и его функции, описывается последовательность вызовов функции модуля при подключении и отключении моделей и выполнении компиляции.
Модули автоматической компиляции предназначены для того, чтобы облегчить создание моделей блоков пользователям, мало знакомым с программированием. Чтобы написать модель, даже если это простейшая функция, вычисляющая сумму двух входов блока и выдающая ее на выход, необходимо правильно оформить главную функцию динамически загружаемой библиотеки (DLL) и собственно функцию модели блока с учетом всех особенностей синтаксиса используемого языка программирования (описания экспортированной функции, включения всех необходимых заголовочных файлов и т.д.) Чаще всего пользователь не горит желанием изучать всю эту «лишнюю» для него информацию – он хочет просто указать с помощью какого-либо простого интерфейса, что блок имеет входы «x1» и «x2», выход «y», и выполняет операцию «y=x1+x2». В этом ему может помочь грамотно написанный модуль автокомпиляции.
Модуль автокомпиляции нужен для того, чтобы преобразовать введенные пользователем тексты и данные, каким-либо образом описывающие работу блока, в полноценную библиотеку с экспортированной функцией модели, которая может быть загружена в RDS и подключена к указанным пользователем блокам. При этом RDS совершенно не важно, как именно пользователь описывает алгоритм работы блока и как эти алгоритмы трансформируются в готовую библиотеку – всем этим должен заниматься модуль автокомпиляции. Обычно имеет смысл дать пользователю возможность писать основные, самые важные, фрагменты модели блока на каком-либо языке программирования высокого уровня, а затем автоматически собирать из них полный исходный текст библиотеки со всеми необходимыми описаниями и вызывать внешний компилятор для ее сборки: так достигается баланс между удобством пользователя и простотой реализации модуля. Именно так устроены модули автокомпиляции, входящие в комплект RDS: они предоставляют пользователю редактор, в котором он записывает реакции блока на различные события в синтаксисе языка C (при этом он может пользоваться функциями из стандартных библиотек, функциями Windows API и сервисными функциями RDS), после чего собирают из них библиотеку с функцией модели, вызывая один из стандартных компиляторов.
Модуль автокомпиляции представляет собой экспортированную из какой-либо библиотеки функцию следующего вида:
extern "C" __declspec(dllexport) int RDSCALL имя_функции_модуля( int CallMode, // Режим вызова RDS_PCOMPMODULEDATA ModuleData, // Данные модуля LPVOID ExtParam) // Дополнительные параметры
Как только в схеме появляется блок, для которого установлена автоматическая компиляция модели каким-либо модулем, RDS загружает библиотеку с функцией этого модуля и начинает вызывать эту функцию для выполнения различных действий в ответ на системные события (подключение моделей к блокам и их отключение, компиляция модели, вызов редактора и т.п.). Можно заметить, что функция модуля автокомпиляции очень похожа на функцию модели блока – как и в функцию модели, в нее передается целый идентификатор события CallMode, на которое должен отреагировать модуль, указатель ModuleData на структуру данных объекта (в данном случае объектом является модуль автокомпиляции, а не блок схемы) и указатель ExtParam на дополнительные параметры, тип и структура которых зависит от конкретного события. Разумеется, события, на которые реагирует модуль автокомпиляции, отличаются от событий, на которые реагирует функция модели.
Прежде, чем модуль автокомпиляции можно будет использовать в блоках схемы, его необходимо зарегистрировать в RDS. Для этого следует поместить файл описания модуля, то есть текстовый файл специального формата с расширением «.acm», либо в папку «Extensions\AutoComp\» в папке установки RDS, либо в папку «UserExtensions\AutoComp\» в папке настроек (см. §2.18). Файл описания модуля обычно содержит:
- указание на поддерживаемые платформы (32-битная, 64-битная версия Windows, или обе);
- имя библиотеки и имя экспортированной функции модуля (отдельно для 32-битных и 64-битных версий);
- название модуля, видимое пользователю, для разных языков интерфейса;
- строка формата модели для поиска совместимых модулей при отсутствии данного;
- условный порядковый номер модуляв общем списке модулей RDS по умолчанию.
Файл описания всегда начинается со слова «autocomp», за которым располагаются следующие параметры, разделенные пробелами или переводами строк:
- file "путь_к_dll_32_бита"
- Путь к библиотеке с функцией модуля для 32-битной версии. В параметре могут использоваться стандартные обозначения путей RDS. Символы «\» в строке пути должны удваиваться.
- func "имя_функции_32_бита"
- Имя функции модуля, экспортированной из 32-битной библиотеки.
- file64 "путь_к_dll_64_бита"
- Путь к библиотеке с функцией модуля для 64-битной версии. В параметре также могут использоваться стандартные обозначения путей RDS. Символы «\» в строке пути должны удваиваться.
- func64 "имя_функции_64_бита"
- Имя функции модуля, экспортированной из 64-битной библиотеки.
- win32
- Указание на то, что модуль предназначен только для 32-битной версии RDS.
- win64
- Указание на то, что модуль предназначен только для 64-битной версии RDS.
- title язык "название_модуля_для_пользователя"
- Название модуля, видимое пользователю. Это название пользователь видит в общем списке модулей, и по нему выбирает модуль при подключении к блоку. Сразу за словом «title» указывается название языка интерфейса, при установке которого будет показываться это название (на данный момент поддерживается только английская локлизация с названием «English»), или слово «default» для языка по умолчанию (русского).
- format "строка_формата_модели"
- Уникальная строка, описывающая формат текста моделей, поддерживаемых модулем. Если у пользователя в системе будет несколько модулей с одинаковой строкой формата, а также специальный модуль поиска совместимых (см. §3.5), то пользователь сможет подключать к своим моделям модуль поиска, и не задумываться о том, какой из совместимых модулей зарегистрирован и настроен у него в системе.
- seq "номер_для_упорядочения"
- Условный порядковый номер модуля в общем списке. Все зарегистрированные модули по умолчанию упорядочиваются по возрастанию этого параметра. Пользователь, при желании, может менять порядок зарегистрированных модулей в окне их настройки.
Рассмотрим, например, файл описания стандартного модуля автокомпиляции, предназначенного для вызова компилятора GCC MinGW-w64 (https://www.mingw-w64.org/⇗), который установлен локально вместе с RDS в подпапку «mingw-w64». Этот файл входит в состав стандартной установки 64-битной версии RDS, расположен в подпапке «Extensions\AutoComp» и называется «MinGW_GCC64_local.acm». В нем записан следующий текст:
autocomp
win64
file64 "$DLL$\\Win64\\Common.dll" func64 "Gcc_MinGW_Local"
title default "MinGW GCC 64 бита (локально)"
title English "MinGW GCC 64 bit (local)"
format "InstituteOfControlSciences.CPlusPlus.Common"
seq 10040
В нем указано, что:
- модуль предназначен только для 64-битной версии RDS («win64»);
- в 64-битной версии модуль обслуживается функцией «Gcc_MinGW_Local» в библиотеке «Win64\Common.dll» в стандартной папке DLL («func64» и «file64» соответственно);
- русское название модуля – «MinGW GCC 64 бита (локально)», английское – «MinGW GCC 64 бита (local)» («title default» и «title English» соответственно);
- модели, обслуживаемые модулем, имеют формат «InstituteOfControlSciences.CPlusPlus.Common» («format»);
- если пользователь не перемещал модули в списке, считать условным порядковым номером этого модуля 10040 («seq»).
На рис. 120 приведен вид окна настройки модулей автокомпиляции, в котором можно видеть название этого модуля на пятом месте. Поскольку окно изображается для 32-битной версии RDS, а в описании модуля указано, что он предназначен для 64-битной, название отображается серым цветом и его нельзя выбрать и настроить. В окне 64-битной версии он выбирался и отображался бы нормально.
Рис. 120. Окно списка модулей автокомпиляции (в 32-битной версии RDS)
В этом же окне можно настраивать модули автокомпиляции (двойным щелчком на названии модуля в списке или специальной кнопкой в правой части окна). В RDS нет собственного интерфейса для настройки модулей, его должна обеспечить функция настраиваемого модуля. Когда пользователь вызывает настройку какого-либо модуля, RDS, в свою очередь, вызывает его функцию с параметром CallMode, равным RDS_COMPM_SETUP (все константы и структуры, используемые для создания модулей автокомпиляции, описаны в файле «RdsDef.h»). Реагируя на этот вызов, функция должна самостоятельно обеспечить пользователю возможность ввода параметров, необходимых для работы модуля, например, открыв какое-либо окно с полями ввода. Если на момент настройки библиотека с модулем не загружена, RDS загружает ее перед вызовом функции и выгружает после него. При этом сразу после загрузки функция модуля вызывается с параметром RDS_COMPM_INIT, а перед выгрузкой – с параметром RDS_COMPM_CLEANUP для инициализации данных модуля и их очистки соответственно (инициализация и очистка данных модуля рассмотрены в §3.2).
В отличие от функции модели блока, функция модуля автокомпиляции должна заботиться еще и о том, как и где она будет хранить настроечные параметры модуля. Если параметры блока обычно сохраняются в файле схемы, в которой находится этот блок, с параметрами модуля автокомпиляции так поступать нельзя: они не связаны с конкретной схемой и относятся ко всему модулю в целом. Кроме того, они могут различаться на разных машинах (например, путь к исполняемому файлу используемого внешнего компилятора зависит от того, в какую папку он установлен на данной машине), поэтому при переносе схемы с машины на машину параметры модулей автокомпиляции не должны переноситься вместе с ней. RDS при вызове модуля автоматически формирует для него уникальное имя файла для хранения настроек, размещающегося в одной из подпапок папки настроек RDS (символическое имя «$INI$»). Имя этого файла передается в параметре DataFile структуры параметров модуля RDS_COMPMODULEDATA (см. ниже). Созданием, чтением и записью этого файла функция модуля должна заниматься самостоятельно. Можно и не использовать предлагаемый файл, но тогда модуль должен сам обеспечивать уникальность имени файла настроек, чтобы это имя не вступило в конфликт с какими-либо другими именами файлов RDS.
Для того, чтобы связать блок с автоматически компилируемой моделью, пользователь должен на вкладке «» окна параметров блока (рис. 121) выбрать модуль, который будет обслуживать модель блока, и указать имя этой модели.
Рис. 121. Подключение автокомпилируемой модели к блоку
С точки зрения RDS, имя модели – это просто строка символов, не чувствительная к регистру (имена «Model», «model» и «MODEL» будут считаться именами одной и той же модели). Сами модели блоков, как и параметры модулей, не сохраняются в файле схемы вместе с параметрами блока, к которому они подключены. Дело в том, что одна и та же модель может использоваться в разных схемах, и, если бы ее текст хранился в каждой из этих схем независимо, для изменения модели пришлось бы менять ее текст во всех этих схемах. Поэтому в схеме сохраняется только имя модели, имя библиотеки с обслуживающим ее модулем автокомпиляции и имя функции этого модуля (последние два параметра – независимо для 32-битных и 64-битных версий). Модуль автокомпиляции должен самостоятельно организовать хранение текста модели, с которым работает пользователь, и уметь вызывать нужную модель по имени. Чаще всего каждая модель хранится в отдельном файле, и в качестве ее имени используется имя этого файла – такой подход позволяет реализовать поиск и хранение моделей очень просто. Тем не менее, разработчик модуля автокомпиляции может выбрать и другие способы хранения – например, хранить модель в таблице какой-либо базы данных, а в качестве имени модели использовать уникальный ключ (идентификатор) записи таблицы. RDS никак не ограничивает разработчика в этом вопросе.
Таким образом, хранение автоматически компилируемых моделей отдельно от схемы приводит к тому, что внесение изменений в какую-либо модель отражается на всех схемах, которые эту модель используют. При этом следует помнить, что для переноса схемы с автокомпилируемыми моделями на другую машину необходимо скопировать на нее не только файл схемы, но и все используемые этой схемой модели. Либо, если на другой машине не нужна автоматическая компиляция, отключить ее в параметрах всех блоков – при этом блоки останутся связанными с уже скомпилированными библиотеками, но исходный текст модели им будет уже не нужен. Разумеется, скомпилированные библиотеки нужно будет скопировать на другую машину вместе со схемой.
На вкладке «» окна параметров блока имя модели может вводиться вручную или выбираться при помощи кнопок «» (для подключения к блоку существующей модели) и «» (для создания новой модели). RDS не обрабатывает нажатия этих кнопок, они передаются в функцию модуля автокомпиляции, как и нажатие кнопки «», которая позволяет сохранить используемую модель под другим именем. Модуль должен сам выполнить все необходимые действия по подключению новой модели, показу необходимых для этого диалогов и т.д. При необходимости модуль может запретить эти кнопки, если он не поддерживает соответствующие функции, запретить ручной ввод имени модели, а также задать название поля ввода этого имени (например, выбранный на рис. 121 модуль хранит модели в файлах, поэтому в качестве названия поля ввода он установил строку «Файл исходного текста»).
Каждый модуль автокомпиляции может одновременно обслуживать произвольное число моделей, каждая из которых может быть подключена к произвольному числу блоков (рис. 122).
Рис. 122. Структура данных модуля автокомпиляции
При загрузке модуля автокомпиляции для него создается структура RDS_COMPMODULEDATA, которая хранится в памяти до момента его выгрузки, то есть до тех пор, пока последняя модель, обслуживаемая этим модулем, не будет отключена от блока. Описание этой структуры в файле «RdsDef.h» имеет следующий вид:
typedef struct { RDS_COMPHANDLE Module; // Уникальный идентификатор модуля LPVOID ModuleData; // Личная область данных модуля RDSCSTR DllFullPathA; // Полный путь к DLL модуля (UTF8) RDSWCSTR DllFullPathW; // Полный путь к DLL модуля (UTF16) // RDSXCSTR DllFullPath; // Полный путь к DLL модуля (поле-псевдоним) RDSCSTR DllFuncNameA; // Имя функции DLL модуля (UTF8) RDSWCSTR DllFuncNameW; // Имя функции DLL модуля (UTF16) // RDSXCSTR DllFuncName; // Имя функции DLL модуля (поле-псевдоним) RDSINT32 NModels; // Число моделей, обслуживаемых модулем RDSCSTR DataFileA; // Предлагаемое имя файла параметров (UTF8) RDSWCSTR DataFileW; // Предлагаемое имя файла параметров (UTF16) // RDSXCSTR DataFile; // Предлагаемое имя файла параметров (поле-псевдоним) RDSCSTR ModelFormatA; // Строка формата модели (UTF8) RDSWCSTR ModelFormatW; // Строка формата модели (UTF16) // RDSXCSTR ModelFormat; // Строка формата модели (поле-псевдоним) } RDS_COMPMODULEDATA; typedef RDS_COMPMODULEDATA *RDS_PCOMPMODULEDATA; // Указатель
Указатель на эту структуру передается во втором параметре функции модуля автокомпиляции. В поле ModuleData функция может записать указатель на личную область данных модуля, в которой она может хранить, например, его настроечные параметры. RDS не обрабатывает это поле структуры, поэтому отведением памяти под личную область и ее освобождением должна заниматься функция модуля, точно так же, как функция модели блока отводит и освобождает память под личную область данных блока. Все остальные поля структуры функция модуля может только читать – они заполняются RDS автоматически.
Для каждой модели, обслуживаемой модулем, в памяти хранится структура RDS_COMPMODELDATA. Она создается RDS при первом подключении модели к какому-либо блоку и хранится в памяти до тех пор, пока в схеме остаются блоки, подключенные к этой модели. Функция модуля автокомпиляции в любой момент может получить указатель на структуру любой из своих моделей, вызвав сервисную функцию rdscompGetModelData и указав в ее параметре идентификатор модуля и номер модели (общее число моделей, обслуживаемых модулем в данный момент, всегда хранится в поле NModels структуры данных модуля RDS_COMPMODULEDATA). Структура RDS_COMPMODELDATA описана в файле «RdsDef.h» следующим образом:
typedef struct { RDS_MODELHANDLE Model; // Уникальный идентификатор модели RDSCSTR ModelNameA; // Имя модели (UTF8) RDSWCSTR ModelNameW; // Имя модели (UTF16) // RDSXCSTR ModelName; // Имя модели (поле-псевдоним) RDSCSTR ModelNameUCA; // Имя модели в верхнем регистре (UTF8) RDSWCSTR ModelNameUCW; // Имя модели в верхнем регистре (UTF16) // RDSXCSTR ModelNameUC; // Имя модели в верхнем регистре (поле-псевдоним) LPVOID ModelData; // Личная область данных модели RDSINT32 NBlocks; // Число блоков, связанных с моделью RDS_COMPHANDLE Module; // Идентификатор обслуживающего модуля // Параметры скомпилированной модели RDSCSTR CompDllNameA; // DLL скомпилированной модели (UTF8) RDSWCSTR CompDllNameW; // DLL скомпилированной модели (UTF16) // RDSXCSTR CompDllName; // DLL скомпилированной модели (поле-псевдоним) RDSCSTR CompDllFuncA; // Имя экспортированной функции (UTF8) RDSWCSTR CompDllFuncW; // Имя экспортированной функции (UTF16) // RDSXCSTR CompDllFunc; // Имя экспортированной функции (поле-псевдоним) BOOL Valid; // Признак необходимости компиляции RDSCSTR AltModelNameA; // Альтернативное имя модели (UTF8) RDSWCSTR AltModelNameW; // Альтернативное имя модели (UTF16) // RDSXCSTR AltModelName; // Альтернативное имя модели (поле-псевдоним) RDSINT32 Tag; // Пользовательское поле } RDS_COMPMODELDATA; typedef RDS_COMPMODELDATA *RDS_PCOMPMODELDATA; // Указатель
Как и у модуля автокомпиляции, у модели может быть личная область данных, в которой функция модуля может хранить какие-либо необходимые для компиляции этой модели данные. Как обычно, отведением и освобождением памяти под эту область функция модуля должна заниматься самостоятельно: при первом подключении модели к блоку функция модуля вызывается с параметром RDS_COMPM_MODELINIT, в этот момент она может отвести память под личную область модели и записать указатель на нее в поле ModelData структуры RDS_COMPMODELDATA (см. рис. 122). При отключении модели от последнего блока схемы функция модуля будет вызвана с параметром RDS_COMPM_MODELCLEANUP, и должна будет освободить отведенную под личную область данных модели память.
Кроме поля ModelData функция модуля может изменять в этой структуре еще только два поля: признак необходимости компиляции Valid (его использование будет описано ниже) и целое поле Tag, которое никак не обрабатывается RDS и может использоваться разработчиком модуля по своему усмотрению. Все остальные поля либо устанавливаются RDS, либо меняются функцией модуля не непосредственно, а через вызовы специальных сервисных функций.
В поля ModelName* и ModelNameUC* RDS записывает указатели на строки в своей внутренней памяти, содержащие имя модели в том виде, в котором его ввел пользователь, и преобразованное в верхний регистр соответственно (как было указано выше, имя модели не чувствительно к регистру). Функция модуля никак не может изменить эти строки, они заполняются автоматически при первом подключении модели к блоку схемы.
В целом поле NBlocks содержится общее число блоков схемы, к которым подключена данная модель. Это поле изменяется автоматически при подключении модели к блокам и отключении от них. Функция модуля может получить идентификатор любого из использующих данную модель блоков и описание этого блока, вызвав сервисную функцию rdscompGetModelBlock и передав ей идентификатор модели, номер блока и, при необходимости, указатель на структуру описания блока RDS_BLOCKDESCRIPTION для заполнения.
В полях CompDllName* и CompDllFunc* содержатся указатели на строки, содержащие имя DLL, которая будет создана в результате компиляции данной модели, и имя экспортированной из нее функции блока. Эти имена устанавливаются функцией модуля перед компиляцией при помощи сервисной функции rdscompSetModelFunction, после компиляции RDS использует их, чтобы загрузить новую модель и подключить ее ко всем использующим ее блокам. Логическое поле Valid обычно используется для того, чтобы зря не компилировать модели, текст и параметры которых не изменились: при подготовке к компиляции функция модуля записывает в это поле значение FALSE, если требуется компиляция, и TRUE, если модель с момента прошлой компиляции не менялась. Если исходные тексты моделей хранятся в файлах, необходимость компиляции проще всего выяснить, сравнив время последнего изменения файла исходного текста модели с временем последнего изменения скомпилированной DLL. Если текст модели менялся позднее, чем была записана DLL, значит, он изменился, и DLL необходимо скомпилировать заново.
В полях AltModelName* хранятся указатели на строки, представляющие собой так называемое «альтернативное имя» модели. Это имя устанавливается сервисной функцией rdscompSetAltModelName и запоминается в файле схемы вместе с «нормальным» именем модели. Пользователь не видит альтернативное имя модели и установить его не может (при подключении модели через окно параметров блока, как на рис. 121, альтернативному имени присваивается пустая строка). Поскольку исходные тексты моделей хранятся отдельно от файлов схем, альтернативное имя является единственной возможностью записать в файл схемы какую-либо информацию, относящуюся к модели. Разработчик модуля автокомпиляции может использовать его по своему усмотрению, RDS это альтернативное имя никак не обрабатывает. Например, если тексты моделей хранятся в файлах, в альтернативном имени можно хранить полный путь к такому файлу, а в «нормальном имени» – короткий путь, возможно, с использованием символических констант.
Допустим например, что файл схемы имеет имя «scheme.rds», файл модели – «model.mod», и оба они находятся в папке «d:\data\files». В этом случае в качестве имени модели целесообразно использовать текст «model.mod» – поскольку сервисные функции RDS позволяют автоматически добавлять к именам файлов без путей путь к папке, в которой находится файл загруженной схемы, схема будет работать до тех пор, пока файл схемы и файл модели находятся в одной и той же папке, куда бы они ни были скопированы. Однако, если пользователь скопирует файл схемы в другую папку, а файл модели скопировать забудет, модуль автокомпиляции «потеряет» файл модели: файл теперь находится не в папке файла схемы, и имени файла без пути для его загрузки уже недостаточно. Из этого положения можно выйти, записывая в альтернативное имя модели путь к папке, в которой находится файл модели на момент последней удачной загрузки, то есть «d:\data\files». В этом случае модуль автокомпиляции должен быть написан так, чтобы, не обнаружив файл модели в ожидаемом месте (в данном случае – в папке файла схемы), он искал его в папке, указанной в строке альтернативного имени модели. Разумеется, разработчик может придумать и другие способы использования альтернативного имени.
Наконец, целое поле Tag структуры RDS_COMPMODELDATA может использоваться разработчиком по своему усмотрению. Функция модуля может записывать в это поле что угодно – RDS его никак не обрабатывает.
Автокомпилируемая модель может подключаться к блоку по нескольким причинам. Кроме явного включения автокомпиляции в параметрах блока (см. рис. 121), модели подключаются к блокам при загрузке схемы, при вставке автокомпилируемого блока из буфера обмена или из библиотеки блоков и т.п. Независимо от причины, при этом выполняется следующая последовательность действий:
- Если выбранный модуль автокомпиляции еще не используется в схеме, его DLL загружается в память RDS, для него создается структура RDS_COMPMODULEDATA, после чего функция модуля вызывается с параметром RDS_COMPM_INIT. В этот момент она обычно создает личную область данных модуля и загружает в нее настроечные параметры, необходимые для его работы.
- Функция модуля вызывается с параметром RDS_COMPM_CANATTACHBLK, и в нее передается имя модели и идентификатор блока, к которому будет подключаться эта модель. Структура данных модели RDS_COMPMODELDATA на этот момент может быть еще не создана. Реагируя на этот вызов, функция модуля должна проверить принципиальную возможность подключения данной модели к данному блоку. Например, модель, использующая статические переменные, не может подключаться к подсистемам, внешним входам/выходам и вводам шин, поскольку их структура переменных устроена не так, как у простых блоков. Если подключение модели к блоку невозможно, функция либо возвращает сообщение об ошибке, либо вызывает сервисную функцию rdscompAttachDifferentModel, сообщая RDS, что, хотя модель с данным именем подключить нельзя, вместо нее можно подключить модель с другим именем (например, если файл, соответствующий имени модели, отсутствует, но по альтернативному имени можно найти другой, как в примере, описанном выше).
- Если данная модель еще не подключалась ни к одному блоку схемы, для нее создается структура RDS_COMPMODELDATA, функция модуля вызывается с параметром RDS_COMPM_MODELINIT, и в нее передается указатель на структуру данных модели. Реагируя на этот вызов, функция может создать для модели личную область данных, если это необходимо.
- Функция модуля вызывается с параметром RDS_COMPM_ATTACHBLOCK и в нее передается указатель на структуру данных модели RDS_COMPMODELDATA и идентификатор блока, к которому она подключается.
При отключении автокомпилируемой модели от блока (опять же, причины отключения могут быть различными: выключение автокомпиляции в параметрах блока, удаление блока, завершение RDS и т.п.) выполняется следующая последовательность действий:
- Функция модуля автокомпиляции вызывается с параметром RDS_COMPM_DETACHBLOCK, и в нее передается указатель на структуру данных модели RDS_COMPMODELDATA и идентификатор блока, от которого она сейчас будет отключена.
- Если это был последний блок, с которым связана данная модель, функция модуля вызывается с параметром RDS_COMPM_MODELCLEANUP, при этом в нее передается указатель на структуру данных модели RDS_COMPMODELDATA. Реагируя на этот вызов, функция должна уничтожить личную область данных, если она создавалась для этой модели. После этого структура данных модели уничтожается.
- Если данный модуль компиляции больше не используется в схеме (то есть последняя модель этого модуля отключена от последнего использовавшего ее блока), функция модуля вызывается с параметром RDS_COMPM_CLEANUP, после чего данные модуля уничтожаются и его DLL выгружается из памяти. Реагируя на вызов RDS_COMPM_CLEANUP, функция должна уничтожить личную область данных модуля, если она создавалась.
Таким образом, функция модуля автокомпиляции всегда получает информацию о загрузке и выгрузке своего модуля, о создании структур данных новых моделей и уничтожении более не используемых, и о подключении моделей к новым блокам и их отключении.
RDS вызывает модуль автокомпиляции для компиляции обслуживаемых им моделей в четырех случаях: после загрузки схемы или блока, при переходе из режима редактирования в режим моделирования и при выборе пользователем пунктов меню «» или «». В последнем случае подразумевается, что модуль должен скомпилировать даже те модели, которые не менялись с момента прошлой компиляции – это может быть полезно, например, при сбое системных часов, когда время записи скомпилированной DLL модели оказывается большим времени изменения ее текста из-за того, что время изменения текста показывается неправильно. За компиляцию моделей отвечают два последовательных вызова функции модуля: сначала функция вызывается с параметром RDS_COMPM_PREPARE для каждой модели по отдельности, а затем – для всех моделей сразу с параметром RDS_COMPM_COMPILE.
При вызове функции модуля с параметром CallMode, равным RDS_COMPM_PREPARE, функция модуля должна проверить необходимость компиляции конкретной модели, и подготовить ее для этого, если необходимо. В параметре ExtParam при этом передается указатель на структуру RDS_COMPPREPAREDATA, содержащую два поля:
typedef struct { RDS_PCOMPMODELDATA Model; // Указатель на структуру данных модели BOOL Rebuild // TRUE, если необходимо принудительно // компилировать все модели } RDS_COMPPREPAREDATA; typedef RDS_COMPPREPAREDATA *RDS_PCOMPPREPAREDATA; // Указатель
В поле Model этой структуры содержится указатель на структуру данных модели, а в логическом поле Rebuild – TRUE, если пользователь выбрал пункт меню «» (то есть если модель нужно компилировать даже тогда, когда функция модуля не видит в этом необходимости) и FALSE в противном случае. Реагируя на этот вызов, функция должна установить поле Valid в структуре, указатель на которую находится в поле Model. Если данная модель должна быть скомпилирована, Valid нужно присвоить FALSE, если же компилировать не нужно, Valid присваивается TRUE.
Если в результате вызовов функции модуля с параметром RDS_COMPM_PREPARE хотя бы в одной структуре модели поле Valid получило значение FALSE, или если пользователем была выбрана принудительная компиляция всех моделей, функция вызывается с параметром RDS_COMPM_COMPILE. В параметре ExtParam при этом передается указатель на структуру RDS_COMPILEDATA:
typedef struct { // Массив указателей на структуры моделей, которые необходимо // компилировать RDS_PCOMPMODELDATA *InvalidModels; RDSINT32 IMCount; // Размер массива (число моделей) BOOL Rebuild; // TRUE – принудительная компиляция } RDS_COMPILEDATA; typedef RDS_COMPILEDATA *RDS_PCOMPILEDATA; // Указатель
В поле InvalidModels находится указатель на массив указателей на структуры данных (RDS_COMPMODELDATA) моделей, которые должны быть скомпилированы, а в поле IMCount – размер этого массива. Функция модуля должна перебрать все модели в этом массиве и скомпилировать каждую из них. Перед этим вызовом RDS выгружает из памяти все DLL этих моделей, и снова загружает их только после того, как функция модуля вернет ему управление – таким образом, DLL не будут заблокированы, и функция сможет заменить их на новые, полученные в результате компиляции. В поле Rebuild передается признак принудительной компиляции всех моделей, но функция модуля может его игнорировать, поскольку в любом случае в массиве InvalidModels будет находиться список моделей, которые нужно компилировать, независимо от того, попали они туда из-за того, что при вызове с параметром RDS_COMPM_PREPARE флаг Valid в их структурах был установлен в FALSE, или из-за того, что пользователь приказал компилировать все модели схемы.
Кроме описанных выше вызовов, функция модуля также вызывается для открытия редактора модели, при изменении пользователем какой-либо структуры в списке типов переменных (если эта структура используется в моделях, модуль должен скомпилировать их заново), при изменении режима RDS, при сохранении блока с автокомпилируемой моделью, для запроса разрешенности кнопок на вкладке «» в окне параметров блока (рис. 121) и т.д. Все возможные режимы ее вызова подробно рассмотрены в приложениях.
В §3.2 и далее рассматривается относительно простой пример, иллюстрирующий работу модуля автокомпиляции.