Текущий раздел:

Описания RDS

Руководство программиста

Глава 2. Создание моделей блоков

§2.6. Динамические переменные

§2.6.1. Использование динамических переменных

# Руководство программиста

## Глава 2. Создание моделей блоков

### §2.6. Динамические переменные

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

#### §2.6.1. Использование динамических переменных

Описываются общие принципы работы с динамическими переменными – их создание, уничтожение, получение доступа к созданным («подписка»). Описаны основные сервисные функции RDS, относящиеся к динамическим переменным.

Динамические переменные позволяют нескольким блокам обмениваться данными через общую для этих блоков область памяти, содержащую переменную одного из используемых в RDS типов. В отличие от передачи данных по связям через статические входы и выходы, которая определяется пользователем, соединяющим выходы одних блоков со входами других, передача данных через динамические переменные задается на этапе проектирования модели блока. RDS не позволяет пользователю, не разрабатывающему модели блоков, самостоятельно создавать динамические переменные в произвольном блоке и указывать, с какими переменными должен быть связан блок – все эти действия только выполняются моделями блоков при помощи различных сервисных функций. Иногда модель дает пользователю возможность указать имя динамической переменной, используемой для связи с другими блоками (например, в окне настроек блока-графика можно указать имя переменной, из которой блок берет значение времени), но, чаще всего, обмен данными через динамические переменные скрыт от пользователя и не требует от него никаких действий. Это позволяет организовать взаимодействие большого числа блоков, не загромождая схему многочисленными связями. Кроме того, передача данных по связям работает только в режиме расчета, в то время как данные динамических переменных доступны блокам во всех режимах.

Для того, чтобы блоки могли работать с динамической переменной, один из них должен ее создать, а остальные – подписаться на нее, то есть найти динамическую переменную по имени и типу и, если такая переменная существует, получить к ней доступ. Динамическая переменная всегда находится в каком-либо блоке, при этом чаще всего не в том, который ее создал. Механизм подписки устроен таким образом, что блок может получить доступ к своей динамической переменной или к переменной любой из родительских подсистем в иерархии, начиная от непосредственного родителя и заканчивая корневой подсистемой. Таким образом, если простой блок создаст динамическую переменную в себе самом, никто кроме него не сможет на нее подписаться, поскольку он не может являться ничьим родителем, то есть внутри него нет других блоков. Обычно простые блоки создают динамические переменные либо в родительской, либо в корневой подсистеме. Если блок создал переменную в родительской подсистеме, на нее смогут подписаться другие блоки этой же подсистемы и блоки вложенных в нее подсистем. Если блок создал переменную в корневой подсистеме, к ней смогут получить доступ все блоки схемы.

Рис. 37. Связь элементов векторной
картинки блока с динамическими
переменными

Подсистемы, в отличие от простых блоков, иногда создают динамические переменные, принадлежащие им самим, для передачи данных во вложенные блоки. Кроме того, собственные динамические переменные могут использоваться для управления элементами векторной картинки в подсистемах и внешних входах и выходах. В простом блоке для этой цели можно создать несколько внутренних статических переменных, но все остальные типы блоков лишены такой возможности – структура их переменных не может быть задана произвольно. Для того, чтобы связать элемент картинки с собственной динамической переменной, необходимо указать ее имя с префиксом «$DYN.». Например, если для управления элементом должна использоваться динамическая переменная с именем «Value», при редактировании этого элемента необходимо ввести в соответствующее поле текст «$DYN.Value». При необходимости, элементы картинки блока можно также связать с динамической переменной его родительской подсистемы (используется префикс «$PARENT.») или с первой встреченной в иерархии подсистем переменной с заданным именем (используется префикс «$SEARCH.»). На рис. 37 изображено окно настройки системы координат в редакторе векторной картинки блока, в котором координаты этой системы связаны с динамическими переменными «ObjX» и «ObjY» в родительской подсистеме блока, угол поворота системы координат – с динамической переменной «Angle» в самом блоке, а масштаб – с первой встреченной динамической переменной с именем «GlobK» в иерархии родительских подсистем.

Чтобы получить доступ к какой-либо динамической переменной, блок должен на нее подписаться. Это касается и переменных, созданных самим блоком – сервисная функция, создающая динамическую переменную, автоматически подписывает на нее блок-создатель. При подписке указывается имя переменной, ее тип, а также блок, в котором необходимо ее найти (данный блок, родительская подсистема или корневая подсистема). В результате, независимо от того, найдена переменная или нет, RDS создает структуру RDS_DYNVARLINK, содержащую информацию о переменной, и возвращает указатель на нее модели блока. Структура RDS_DYNVARLINK описана в файле «RdsDef.h» следующим образом:

      typedef struct
      { LPVOID Data;          // Указатель на область данных
        RDSCSTR VarNameA;     // Имя переменной (UTF8)
        RDSWCSTR VarNameW;    // Имя переменной (UTF16)
        // RDSXCSTR VarName;  // Имя переменной (поле-псевдоним)
        RDSCSTR VarTypeA;     // Строка типа переменной (UTF8)
        RDSWCSTR VarTypeW;    // Строка типа переменной (UTF16)
        // RDSXCSTR VarType;  // Строка типа переменной (поле-псевдоним)
        RDS_BHANDLE Provider; // Блок-владелец
        LPVOID UID;           // Служебный идентификатор переменной
        RDS_VHANDLE Var;      // Идентификатор переменной для
                              // сервисных функций
      } RDS_DYNVARLINK;
    typedef RDS_DYNVARLINK *RDS_PDYNVARLINK; // Указатель на структуру

Data(LPVOID)
  Указатель на область данных переменной. Структура этой области данных в точности соответствует данным такой же статической переменной в дереве переменных блока (см. §2.5). Например, для динамической переменной типа double поле Data будет указывать на восьмибайтовую область памяти, содержащую вещественное число, для строки – на четырехбайтовую область, содержащую указатель на первый символ строки и т.п. (см. рис. 38). Если переменная не найдена, значение этого поля равно NULL.
VarNameA(RDSCSTR),VarNameW(RDSWCSTR),VarName(RDSXCSTR)
  Строка имени переменной в двух разных кодировках. Можно также пользоваться полем-псевдонимом VarName, которое будет указывать на одну из этих строк в зависимости от наличия макроопределения RDS_UTF16DEFAULT.
VarTypeA(RDSCSTR),VarTypeW(RDSWCSTR),VarType(RDSXCSTR)
  Строка типа переменной (такая же, как и у статических переменных) в двух разных кодировках. Можно также пользоваться полем-псевдонимом VarType, которое будет указывать на одну из этих строк в зависимости от наличия макроопределения RDS_UTF16DEFAULT.
Provider(RDS_BHANDLE)
  Идентификатор блока, в котором располагается найденная переменная.
UID(LPVOID)
  Уникальный идентификатор динамической переменной (служебный, используется внутри RDS).
Var(RDS_VHANDLE)
  Идентификатор переменной, используемый в некоторых сервисных функциях.

Рис. 38. Размещение в памяти динамических переменных разных типов

Если переменная с заданным именем и типом не существует, RDS запомнит факт подписки, и, как только такая переменная будет создана каким-либо блоком, подписавшийся блок немедленно получит к ней доступ. Структура RDS_DYNVARLINK создается один раз при подписке на переменную, после чего RDS самостоятельно обновляет значение ее поля Data при создании и удалении соответствующей динамической переменной. Перед каждым использованием переменной модель блока должна проверять ее существование: если в поле Data находится значение NULL, значит, запрошенная переменная еще не создана или уже удалена. Таким образом, при создании моделей блоков нужно иметь в виду, что, в отличие от статической переменной, динамическая может появляться и исчезать в процессе расчета.

Для подписки на динамическую переменную используется сервисная функция rdsSubscribeToDynamicVar, существующая в версиях для разных кодировок:

      RDS_PDYNVARLINK RDSCALL rdsSubscribeToDynamicVarA(
        int Block,         // В каком блоке искать переменную
        RDSCSTR VarName,   // Имя переменной (UTF8)
        RDSCSTR VarType,   // Строка типа переменной (UTF8)
        BOOL Search);      // Искать по иерархии
      RDS_PDYNVARLINK RDSCALL rdsSubscribeToDynamicVarW(
        int Block,         // В каком блоке искать переменную
        RDSWCSTR VarName,  // Имя переменной (UTF16)
        RDSWCSTR VarType,  // Строка типа переменной (UTF16)
        BOOL Search);      // Искать по иерархии
      // Функция-псевдоним
      RDS_PDYNVARLINK RDSCALL rdsSubscribeToDynamicVar(
        int Block,         // В каком блоке искать переменную
        RDSXCSTR VarName,  // Имя переменной (кодировка по умолчанию)
        RDSXCSTR VarType,  // Строка типа переменной (кодировка по умолчанию)
        BOOL Search);      // Искать по иерархии

Функция принимает следующие параметры:

Block(int)
  Одна из трех констант RDS_DV*, определяющая, в каком блоке нужно искать переменную:

  RDS_DVSELF | в вызвавшем функцию блоке;
  RDS_DVPARENT | в родительской подсистеме;
  RDS_DVROOT | в корневой подсистеме.

VarNameA(RDSCSTR),VarNameW(RDSWCSTR),VarName(RDSXCSTR)
  Имя переменной.
VarTypeA(RDSCSTR),VarTypeW(RDSWCSTR),VarType(RDSXCSTR)
  Строка типа переменной (такая же, как и у статических переменных).
Search(BOOL)
  Нужно ли искать переменную в цепочке родительских подсистем, если она не найдена в блоке, указанном в параметре Block.

Функция rdsSubscribeToDynamicVar возвращает указатель на созданную RDS структуру подписки RDS_DYNVARLINK. Эта структура будет находиться в памяти до тех пор, пока блок-подписчик не откажется от подписки на данную переменную или не будет удален. В параметрах VarName и VarType указывается соответственно имя динамической переменной и строка ее типа. Как и имена статических переменных, имена динамических чувствительны к регистру и не должны содержать некоторых специальных символов (знака доллара, точек, запятых и скобок). Строка типа для динамических переменных строится по тому же принципу, что и строка типа статических. Например, если необходимо подписаться на переменную «Var1» типа double, следует передать в параметре VarName строку «Var1» и в параметре VarType строку «D». Подписка будет успешной, если будет найдена переменная с указанным именем и типом, при этом переменная с тем же именем, но другим типом будет проигнорирована. Блоку-подписчику не нужно следить за типом динамической переменной – если поле Data структуры RDS_DYNVARLINK не равно NULL, значит, найдена переменная, тип которой в точности соответствует запросу.

Если подписка на указанную в параметрах функции переменную принципиально невозможна (например, указано недопустимое имя переменной), функция вернет значение NULL.

Блок-владелец переменной, на которую необходимо подписаться, задается параметром Block. В этом параметре может быть передана одна из трех констант, описанных в файле «RdsDef.h»: RDS_DVSELF (искать переменную в блоке, модель которого вызвала функцию rdsSubscribeToDynamicVar), RDS_DVPARENT (искать в родительской подсистеме) или RDS_DVROOT (искать в корневой подсистеме). Если переменная будет найдена в указанном блоке, в поле Data структуры RDS_DYNVARLINK будет записан указатель на ее область данных. Если же переменная в указанном блоке не найдена, дальнейшие действия RDS определяются параметром Search, разрешающим или запрещающим поиск переменной вверх по иерархии. Если в Search передано значение FALSE, полю Data структуры подписки будет присвоено значение NULL и работа функции rdsSubscribeToDynamicVar на этом завершится. Если же в Search передано TRUE, RDS попытается найти переменную в подсистеме, родительской по отношению к блоку, указанному в параметре Block. Если и в этой подсистеме указанная переменная не будет обнаружена, будет сделана попытка найти ее в родительской подсистеме этой подсистемы и так далее, пока, перебирая подсистемы вверх по иерархии, RDS не доберется до корневой подсистемы. Только если и в корневой подсистеме переменная не будет найдена, полю Data структуры RDS_DYNVARLINK будет присвоено значение NULL. Таким образом, если параметр Search равен TRUE, RDS попытается найти динамическую переменную в ближайшей к указанному блоку родительской подсистеме. Если параметр Block имеет значение RDS_DVROOT, значение параметра Search не имеет значения – у корневой подсистемы нет родительской и, при отсутствии динамической переменной, ее больше негде будет искать.

Подписка на динамическую переменную с поиском вверх по иерархии подсистем применяется достаточно часто, поскольку дает вложенным блокам какой-либо подсистемы возможность получить доступ к переменным этой подсистемы независимо от глубины вложенности этих блоков. С помощью этого механизма, например, стандартные блоки для динамического расчета получают значение времени. Блок, управляющий динамическим расчетом (так называемый планировщик) создает в подсистеме, в которой он находится, динамическую переменную «DynTime» типа double и изменяет ее значение согласно шагу расчета, заданной синхронизацией с реальным временем и другими своими параметрами. Блоки, выполняющие динамический расчет, подписываются на эту переменную с поиском по иерархии. В результате, любой блок вложенной подсистемы всегда имеет доступ к текущему значению времени, если где-нибудь в цепочке его родительских подсистем находится блок-планировщик. В схеме может независимо работать несколько планировщиков, каждый из которых будет предоставлять доступ к текущему значению времени блокам своей подсистемы и вложенных в нее подсистем. Это дает возможность, например, моделировать какие-либо процессы с разным шагом по времени в разных подсистемах, что часто бывает полезно при расчете динамических систем.

Если при подписке с поиском по иерархии была найдена какая-либо переменная, RDS продолжает следить за цепочкой подсистем, и, если в более близкой подсистеме появится переменная с тем же именем и типом, блок-подписчик будет переключен на нее.

Пусть, например, в корневой подсистеме находится динамическая переменная «Var1» типа double (рис. 39 а). Модели блоков «Block1» в корневой подсистеме и «Block3» в подсистеме «Sys1» вызвали сервисную функцию rdsSubscribeToDynamicVar со следующими параметрами:

      rdsSubscribeToDynamicVarA(RDS_DVPARENT,"Var1","D",TRUE);

то есть оба блока запросили подписку на динамическую переменную «Var1» типа double (строка «D») в родительской подсистеме (константа RDS_DVPARENT) с поиском по иерархии. Родительской подсистемой для блока «Block2» является корневая, поэтому он сразу же получит доступ к динамической переменной «Var1» корневой подсистемы (функция rdsSubscribeToDynamicVar запишет в поле Data структуры подписки RDS_DYNVARLINK указатель на область данных «Var1»). Блок «Block3» находится в подсистеме «Sys1», в которой нет запрашиваемой переменной. Поскольку при вызове сервисной функции был указан поиск по иерархии, функция продолжит искать переменную с указанным именем и типом, найдет ее в корневой подсистеме и запишет в поле Data структуры подписки указатель на ее область данных. Таким образом, блоки «Block1» и «Block3» получили доступ к одной и той же динамической переменной, находящейся в корневой подсистеме. При этом RDS запомнит тот факт, что блок «Block3» получил доступ к переменной не в той подсистеме, в которой он его запрашивал.

(а)

(б)

Рис. 39. Подписка на динамическую переменную с поиском по иерархии:
блоки «Block1» и «Block3» подписаны на переменную «Var1» с поиском по иерархии (а),
в подсистеме «Sys1» появилась переменная «Var1», и блок «Block3» получил к ней доступ (б)

Теперь представим себе, что какой-либо блок создал динамическую переменную типа double с именем «Var1» в подсистеме «Sys1». Эта переменная удовлетворяет условиям запроса на подписку, сделанного блоком «Block3», и при этом находится ближе к нему по иерархии, чем переменная «Var1» корневой подсистемы, на которую «Block3» подписан в данный момент. В результате RDS переключит этот блок на использование переменной «Var1» подсистемы «Sys1» вместо одноименной переменной корневой подсистемы (рис. 39 б). При этом от модели блока «Block3» не потребуется никаких действий – все произойдет автоматически. В структуре подписки RDS_DYNVARLINK, которая была создана при вызове сервисной функции rdsSubscribeToDynamicVar, в поле Data будет записан новый указатель, и когда модель блока «Block3» в очередной раз обратится к этому полю, она считает данные переменной из подсистемы «Sys1», а не из корневой подсистемы.

Если через некоторое время переменная «Var1» в подсистеме «Sys1» будет удалена, RDS снова изменит поле Data структуры подписки таким образом, чтобы оно ссылалось на ближайшую переменную с заданным именем и типом, в данном случае – снова на переменную «Var1» корневой подсистемы.

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

Как правило, это не вызывает проблем, если блок-подписчик срабатывает каждый такт расчета или по таймеру. Независимо от того, изменилась ли динамическая переменная, блок будет постоянно считывать ее значение. Это надежный, но неэкономный способ слежения за динамической переменной – модель блока будет постоянно запускаться и тратить время процессора впустую. Более целесообразно заложить в модель блока, присваивающего значение динамической переменной, какой-либо механизм, позволяющий уведомить об этом всех подписчиков. Проще всего это сделать при помощи сервисной функции rdsNotifyDynVarSubscribers, специально предназначенной для уведомления блоков-подписчиков о возможных изменениях динамической переменной. Функция принимает единственный параметр типа RDS_PDYNVARLINK – указатель на структуру RDS_DYNVARLINK, полученный блоком при подписке на переменную, об изменении которой нужно сообщить другим блокам. При вызове этой функции модели всех блоков, подписанных на ту же самую переменную, будут вызваны в режиме RDS_BFM_DYNVARCHANGE, при этом через параметр модели ExtParam (см. §2.3) будет передан указатель на структуру подписки на переменную, созданную для вызываемого блока. Если блок подписан на несколько динамических переменных, его модель сможет выяснить, какая из них изменилась, сравнив значение ExtParam с указателями на их структуры. Следует обратить внимание на то, что указатель, передаваемый через ExtParam в модель блока-подписчика, это не тот же самый указатель, который был использован блоком, изменившим переменную, в качестве параметра функции rdsNotifyDynVarSubscribers. Хотя переменная одна и та же, для каждого подписавшегося на нее блока создается своя структура RDS_DYNVARLINK, поэтому каждый блок-подписчик получит через ExtParam указатель на собственную структуру подписки. Пример использования механизма уведомления об изменении динамических переменных приведен в §2.6.3.

Следует помнить, что модели блоков-подписчиков будут вызваны в режиме RDS_BFM_DYNVARCHANGE при изменении значения переменной, только если в модели блока, изменившего переменную, предусмотрен вызов сервисной функции rdsNotifyDynVarSubscribers. Все стандартные блоки, работающие с динамическими переменными (например, уже упоминавшийся выше планировщик динамического расчета из библиотеки «Common.dll»), поддерживают этот вызов. Однако, если создается модель блока, которая будет работать с какими-либо динамическими переменными вместе с блоками других разработчиков, необходимо узнать из описания этих блоков, поддерживается ли ими вызов функции rdsNotifyDynVarSubscribers. Если этот вызов не поддерживается или в описании ничего об этом не сказано, надежнее проверять значение переменной каждый такт расчета, не полагаясь на вызов модели в режиме RDS_BFM_DYNVARCHANGE.

Модели блоков, подписанных на динамические переменные, вызываются в режиме RDS_BFM_DYNVARCHANGE не только при вызове каким-либо блоком функции rdsNotifyDynVarSubscribers, но и при создании или удалении переменной, а также при переключении на другую переменную при подписке с поиском по иерархии (рис. 39). Эти события, в отличие от изменения значения переменной, происходят при вызове различных сервисных функций, поэтому RDS в состоянии самостоятельно уведомить о них блоки-подписчики.

Модель блока может в любой момент прекратить подписку на динамическую переменную при помощи сервисной функции rdsUnsubscribeFromDynamicVar, в которую передается указатель на структуру подписки. При вызове этой функции созданная при подписке структура RDS_DYNVARLINK уничтожается, и указатель на нее, при помощи которого осуществлялся доступ к переменной, не может быть использован в дальнейшем – теперь он указывает на освобожденную область памяти. При уничтожении блока не обязательно прекращать подписку на переменные, на которые блок подписался в процессе работы – это будет сделано автоматически. Однако, для соблюдения хорошего стиля программирования и улучшения читаемости программы лучше явно прекратить подписку на переменные при вызове модели в режиме RDS_BFM_CLEANUP.