Руководство программиста
Глава 2. Создание моделей блоков
§2.6. Динамические переменные
§2.6.2. Подписка на динамическую переменную
Приводится пример модели блока, получающего доступ к стандартной динамической переменной времени «DynTime», создаваемой блоком-планировщиком, и используемой большинством библиотечных блоков. Блок в примере вычисляет синус от времени, умножает его на значение входа и выдает на выход. В примере также рассматривается реакция модели на событие RDS_BFM_DYNVARCHANGE, сигнализирующее о том, что динамическая переменная изменилась.
Рассмотрим пример, в котором модель блока получит доступ к динамической переменной, созданной другим блоком. Создадим блок, вычисляющий значение «y=A×sin(T)», где «A» – вход блока, «y» – выход, а «T» – значение текущего времени из динамической переменной «DynTime» типа double, которая создается и изменяется планировщиком динамического расчета. Блок будет иметь следующую структуру переменных:
| Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение |
|---|---|---|---|---|---|---|
| 0 | Start | Сигнал | 1 | Вход | ✓ | 1 |
| 1 | Ready | Сигнал | 1 | Выход | 0 | |
| 2 | A | double | 8 | Вход | ✓ | 0 |
| 10 | y | double | 8 | Выход | 0 |
В параметрах блока следует отключают флаг «» и установить флаг «» для входа «A» – теперь блок будет автоматически запускаться при срабатывании связи, подключенной к этому входу. Также желательно дать сигналу «Start» начальное значение 1, чтобы блок запустился в самом первом такте расчета, вычислив значение выхода «y» по начальному значению «A». Чтобы блок запускался и при изменении переменной «DynTime», необходимо будет ввести в его модель реакцию на событие RDS_BFM_DYNVARCHANGE. Модель блока будет иметь следующий вид:
extern "C" __declspec(dllexport) int RDSCALL TestDynSinT(int CallMode, RDS_PBLOCKDATA BlockData, LPVOID ExtParam) { // Макроопределения для статических переменных #define pStart ((char *)(BlockData->VarTreeData)) #define Start (*((char *)(pStart))) #define Ready (*((char *)(pStart+RDS_VSZ_S))) #define A (*((double *)(pStart+2*RDS_VSZ_S))) #define y (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D))) // Вспомогательная переменная – указатель на структуру подписки RDS_PDYNVARLINK Link; switch(CallMode) { // Инициализация блока case RDS_BFM_INIT: // Подписка на динамическую переменную DynTime Link=rdsSubscribeToDynamicVar(RDS_DVPARENT, // В родителе "DynTime", // Переменная "D", // Тип TRUE); // Искать // Запомнить указатель на структуру подписки BlockData->BlockData=Link; break; // Очистка case RDS_BFM_CLEANUP: // Запомненный указатель на структуру подписки Link=(RDS_PDYNVARLINK)BlockData->BlockData; // Прекратить подписку на DynTime rdsUnsubscribeFromDynamicVar(Link); break; // Проверка типа статических переменных case RDS_BFM_VARCHECK: if(strcmp((char*)ExtParam,"{SSDD}")==0) return RDS_BFR_DONE; return RDS_BFR_BADVARSMSG; // Выполнение такта моделирования case RDS_BFM_MODEL: // Запомненный указатель на структуру подписки Link=(RDS_PDYNVARLINK)BlockData->BlockData; // Проверка существования DynTime if(Link!=NULL && Link->Data!=NULL) { // DynTime существует – привести указатель на область // данных переменной к типу "double*" double *pT=(double*)Link->Data; // pT – указатель на данные DynTime y=A*sin(*pT); // Вычисление выхода } else // DynTime не существует Ready=0; // Не передавать данные по связям break; // Реакция на изменение динамической переменной case RDS_BFM_DYNVARCHANGE: Start=1; // Запустить модель в следующем такте расчета break; } return RDS_BFR_DONE; // Отмена макроопределений #undef y #undef A #undef Ready #undef Start #undef pStart } //=========================================
При инициализации блока (вызове модели с параметром RDS_BFM_INIT) модель вызывает функцию rdsSubscribeToDynamicVar, запрашивая у RDS подписку на переменную «DynTime» типа double (строка «D») с поиском по иерархии (параметр функции Search равен TRUE) в родительской подсистеме (константа RDS_DVPARENT). Таким образом, если где-нибудь между родительской подсистемой этого блока и корневой подсистемой будет находиться планировщик динамического расчета, эта модель получит доступ к созданной им переменной «DynTime». Возвращаемый функцией указатель на созданную структуру подписки присваивается вспомогательной переменной Link. Для того, чтобы динамическую переменную можно было использовать в модели блока при реакции на другие события (например, при выполнении такта моделирования), необходимо как-нибудь запомнить этот указатель, поскольку вспомогательная переменная Link, как и любая автоматическая переменная в языке C, будет уничтожена при завершении функции модели и ее значение будет потеряно. У некоторых программистов может возникнуть соблазн объявить переменную Link статической, чтобы ее значение сохранялось между вызовами функции модели, но этого ни в коем случае нельзя делать. В RDS одна и та же функция вызывается для всех блоков с данной моделью, сколько бы их ни было, при этом в параметре BlockData передаются данные конкретного блока, для которого вызвана функция. Если объявить статической какую-либо переменную внутри функции модели, она станет общей для всех блоков с этой моделью. В данном случае это приведет к тому, что блоки будут обращаться не к тем динамическим переменным, на которые они подписались, а к переменной, на которую подписался последний из них, поскольку он присвоил общей переменной Link новое значение. При удалении любого из этих блоков структура подписки будет уничтожена, и все остальные блоки, пытаясь получить доступ к динамической переменной, обратятся к уже освобожденной области памяти, что с большой вероятностью вызовет ошибку общей защиты.
Для хранения данных, относящихся к конкретному блоку, в том числе и указателей на структуры подписки, чаще всего используется личная область данных блока. Обычно при инициализации блока модель отводит необходимую для личной области данных память и присваивает указатель на эту область полю BlockData структуры данных блока (пример модели, работающей с личной областью данных, приведен в §2.4). При написании модели на языке C/C++ личная область данных блока обычно оформляется как объект какого-либо класса или структуры, и указатели на структуры подписки делают членами этого класса. В рассматриваемом примере модели необходимо хранить для каждого блока только один параметр – указатель на структуру подписки на переменную «DynTime». Можно было бы описать структуру личной области данных с единственным полем типа RDS_PDYNVARLINK (указатель на структуру подписки), отвести память под эту структуру при инициализации блока и присвоить этому полю значение вспомогательной переменной Link, но в данном случае можно поступить проще – будем считать личной областью данных блока ту самую структуру подписки, указатель на которую находится в переменной Link. Отведением и освобождением памяти под эту структуру занимается RDS, поэтому никаких дополнительных действий в функции модели не потребуется – нужно просто присвоить значение переменной Link переменной BlockData->BlockData, предназначенной для хранения указателя на личную область данных блока, что и делается в последнем операторе реакции модели на событие RDS_BFM_INIT.
При вызове модели в режиме RDS_BFM_CLEANUP (при отключением модели от блока, перед уничтожением блока и т.п.), блок прекращает подписку на переменную «DynTime». Для этого вызывается функция rdsUnsubscribeFromDynamicVar, в которую передается указатель на структуру подписки. Поскольку этот указатель был сохранен в поле структуры данных блока BlockData->BlockData, которая имеет тип void* (произвольный указатель), перед вызовом функции он приводится к типу RDS_PDYNVARLINK.
При вызове модели с параметром RDS_BFM_MODEL вспомогательной переменной Link присваивается (с приведением типа) запомненный указатель на структуру подписки, хранящийся в поле BlockData структуры данных блока. Затем модель проверяет, удалось ли ей получить доступ к запрошенной переменной. Если RDS по каким-либо причинам не удалось выполнить подписку и создать соответствующую структуру (например, при нехватке памяти), значение переменной Link будет равно NULL. Если переменной «DynTime» нет ни в одной из подсистем, просмотренных в процессе поиска, значение поля Data структуры подписки (Link->Data) будет равно NULL. Если же и Link, и Link->Data имеют ненулевые значения, значит, RDS удалось найти переменную «DynTime» и записать указатель на ее область данных в поле Link->Data. В этом случае указатель на область данных переменной приводится к типу double* (указатель на double) и присваивается вспомогательной переменной pT, после чего с использованием этой переменной вычисляется значение выхода y.
Для того, чтобы значение выхода y вычислялось заново при каждом изменении значения «DynTime», нужно каким-либо образом отслеживать эти изменения. Поскольку эта переменная формируется стандартным блоком-планировщиком, про который точно известно, что он уведомляет всех подписчиков о ее изменении, в модель можно ввести реакцию на событие RDS_BFM_DYNVARCHANGE. При вызове функции модели в этом режиме стандартному сигналу Start будет присвоено значение 1, что приведет к запуску модели в следующем такте расчета. Тогда и будет вычислено новое значение y.
Рис. 40. Доступ к динамической переменной времени
Чтобы проверить работу блока с этой моделью, следует поместить его в схему (см. рис. 40) вместе со стандартным блоком-планировщиком (на рисунке он располагается в левом верхнем углу) и подключить ко входу блока «A» поле ввода, а к выходу «y» – график, получающий значение времени из той же самой переменной «DynTime» (это задается в параметрах графика, впрочем, по умолчанию стандартные графики работают именно с этой переменной). При запуске расчета график должен отобразить синусоиду с амплитудой, равной значению в поле ввода, подключенном ко входу «A».