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

Описания RDS

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

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

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

§2.6.3. Создание и удаление динамической переменной

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

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

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

#### §2.6.3. Создание и удаление динамической переменной

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

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

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

      RDS_PDYNVARLINK RDSCALL rdsCreateAndSubscribeDVA(
        int Block,         // В каком блоке создать
        RDSCSTR VarName,   // Имя переменной (UTF8)
        RDSCSTR VarType,   // Тип переменной (UTF8)
        BOOL Fixed,        // Запретить удаление
        RDSCSTR Init);     // Начальное значение (UTF8)
      RDS_PDYNVARLINK RDSCALL rdsCreateAndSubscribeDVW(
        int Block,         // В каком блоке создать
        RDSWCSTR VarName,  // Имя переменной (UTF16)
        RDSWCSTR VarType,  // Тип переменной (UTF16)
        BOOL Fixed,        // Запретить удаление
        RDSWCSTR Init);    // Начальное значение (UTF16)
      // Функция-псевдоним
      RDS_PDYNVARLINK RDSCALL rdsCreateAndSubscribeDV(
        int Block,         // В каком блоке создать
        RDSXCSTR VarName,  // Имя переменной (кодировка по умолчанию)
        RDSXCSTR VarType,  // Тип переменной (кодировка по умолчанию)
        BOOL Fixed,        // Запретить удаление
        RDSXCSTR Init);    // Начальное значение (кодировка по умолчанию)

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

Block(int)
  Как и в уже описанной функции rdsSubscribeToDynamicVar – одна из трех констант RDS_DV*, определяющая, в каком блоке создается переменная:

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

VarNameA(RDSCSTR),VarNameW(RDSWCSTR),VarName(RDSXCSTR)
  Имя создаваемой переменной.
VarTypeA(RDSCSTR),VarTypeW(RDSWCSTR),VarType(RDSXCSTR)
  Строка типа создаваемой переменной (такая же, как и у статических переменных).
Fixed(BOOL)
  Разрешить удаление переменной любому блоку (FALSE) или только блоку, создавшему переменную (TRUE).
InitA(RDSCSTR),InitW(RDSWCSTR),Init(RDSXCSTR)
  Строка со значением переменной по умолчанию или NULL, если значение по умолчанию не важно.

Первые три параметра этой функции похожи на параметры функции rdsSubscribeToDynamicVar – они задают блок-владелец, имя и тип переменной, которую нужно создать. Параметр Fixed, установленный в TRUE, позволяет запретить удаление созданной переменной всем блокам, кроме создавшего, а через параметр Init можно передать строку со значением переменной по умолчанию в том же формате, который используется в окне редактирования статических переменных. Если значение Init будет равно NULL, переменная получит стандартное значение (0, пустой массив, пустая строка и т.п. в зависимости от типа переменной). При создании переменной нельзя указать поиск по иерархии, как при подписке – переменная будет создана именно в том блоке, который указан константой в параметре Block: в вызвавшем блоке (RDS_DVSELF), в родительской подсистеме (RDS_DVPARENT) или в корневой подсистеме (RDS_DVROOT). Если в указанном блоке на данный момент нет динамической переменной с указанным именем, функция создаст в нем переменную указанного типа и автоматически подпишет на нее вызывавший блок. Если переменная успешно создана, функция rdsCreateAndSubscribeDV возвращает указатель на структуру подписки, точно такой же, как и функция rdsSubscribeToDynamicVar. Если переменную создать не удалось (например, в указанном блоке уже есть переменная с указанным именем), функция вернет значение NULL.

Поскольку функция rdsCreateAndSubscribeDV автоматически подписывает вызвавший блок на созданную динамическую переменную, обращение к созданной переменной производится таким же образом, как и при обычной подписке – через структуру RDS_DYNVARLINK, указатель на которую возвращает функция, а точнее, через ее поле Data, указывающее на область данных динамической переменной. Как и модели блоков-подписчиков, модель блока, создавшего переменную, будет реагировать на события RDS_BFM_DYNVARCHANGE, и может, при необходимости, вызывать функцию rdsNotifyDynVarSubscribers для уведомления других блоков об изменении переменной.

Иногда в старых текстах моделей блоков можно встретить создание динамических переменных при помощи устаревшей функции rdsCreateDynamicVar. Эта функция не подписывает создавший блок на созданную переменную, поэтому если модели блока необходимо сообщать подписчикам об изменении переменной и, в свою очередь, получать уведомление об изменениях, за вызовом этой функции должен следовать вызов rdsSubscribeToDynamicVar. Функция rdsCreateAndSubscribeDV объединяет эти два вызова, поэтому ее использование предпочтительнее.

Для удаления динамической переменной обычно используется сервисная функция rdsDeleteDVByLink, в которую передается указатель на структуру подписки на переменную, которую необходимо удалить. Эта функция не только удаляет переменную, но и прекращает подписку на нее для вызвавшего блока – после ее вызова структура подписки уничтожается, и указатель на нее, переданный в функцию, больше использовать нельзя. Если блоку необходимо удалить переменную, но, при этом, сохранить подписку на нее (чтобы позже получить доступ к переменной с тем же именем и типом, если ее создаст другой блок), следует воспользоваться функцией rdsDeleteDynamicVar, в параметрах которой указывается блок, в котором нужно удалить переменную, и ее имя. Следует помнить, что если при создании переменной в параметре Fixed было передано значение TRUE, ее сможет удалить только тот блок, модель которого создала эту переменную.

Рассмотрим модели двух блоков, позволяющих организовать передачу данных между разными участками схемы через динамическую переменную. Блок-передатчик с моделью TestTunnelIn создаст в корневой подсистеме динамическую переменную типа double и будет записывать в нее значение своего входа. Блок-приемник с моделью TestTunnelOut (таких блоков может быть несколько в разных местах схемы) будет выдавать значение этой динамической переменной на свой выход. Таким образом, подав какое-нибудь значение на вход блока-передатчика, его можно будет снимать с выходов блоков-приемников, не соединяя эти блоки связями. Даже если приемники и передатчик находятся в разных подсистемах, удаленных друг от друга в иерархии, приемники смогут получать данные от передатчика, поскольку динамическая переменная, через которую они связаны, находится в корневой подсистеме и доступна всем блокам схемы. В блоках-приемниках и блоках-передатчиках необходимо предусмотреть возможность задания имени связывающей их переменной, чтобы можно было организовать несколько независимых групп из передатчика и приемников, каждая из которых будет работать со своей переменной, не мешая остальным. Чтобы не перегружать этот пример лишними функциями, связанными с организацией диалога с пользователем, имя динамической переменной для связи будет читаться из комментария блока, который пользователь может изменить в режиме редактирования на вкладке «общие» окна параметров блока. Такой способ настройки параметров блоков не очень удобен для пользователя, поэтому при разработке моделей для практического применения следует включать в них функцию настройки, позволяющую пользователю ввести параметры блока в отдельном окне в удобной для него форме (пример функции настройки приведен в §2.7.1).

Сначала создадим модель блока-передатчика. Помимо двух стандартных сигналов, блок будет иметь единственный вещественный вход «x»:

Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | --- | --- | ---
0 | Start | Сигнал | 1 | Вход | ✓ | 1
1 | Ready | Сигнал | 1 | Выход |  | 0
2 | x | double | 8 | Вход | ✓ | 0

Как и для большинства уже описанных блоков, для этого блока желательно отключить запуск каждый такт и установить флаг «пуск» для входа «x» и начальное значение 1 для сигнала «Start», чтобы модель автоматически запускалась при каждом изменении «x» и при первом запуске расчета.

Блок-передатчик должен создать динамическую переменную в корневой подсистеме и записывать в нее значения своего входа «x», причем имя создаваемой переменной необходимо считать из комментария блока. Пользователь может в любой момент остановить расчет, перейти в режим редактирования и изменить комментарий, поэтому блок должен проверять, совпадает ли имя переменной, с которой он в данный момент работает, с текстом комментария блока. Если комментарий изменился, блок должен удалить старую динамическую переменную и создать новую, с именем, взятым из нового комментария. Поскольку пользователь может изменить комментарий блока только в режиме редактирования, можно производить проверку соответствия имени переменной комментарию при входе в режим моделирования (событие RDS_BFM_CALCMODE) или при запуске расчета (событие RDS_BFM_STARTCALC). Кажется логичным вставить в модель реакцию на одно из этих событий, в которой имя динамической переменной будет сравниваться с комментарием блока, и, если они не совпадают, созданная переменная будет уничтожаться и вместо нее будет создаваться новая. Однако, в такой конструкции модели скрывается не совсем очевидная проблема, на которой следует остановиться подробнее.

Допустим, в схеме находятся два блока-передатчика: «Block1», работающий с переменной «Var1», и «Block2», работающий с переменной «Var2». Эта схема некоторое время работала в режиме расчета, после чего пользователь перешел в режим редактирования и изменил комментарий блока «Block1» на «Var2», а блока «Block2» – на «Var1», то есть поменял переменные блоков местами. При запуске расчета сначала вызовется модель блока «Block1». Она сравнит имя переменной блока «Var1» с новым текстом комментария «Var2» и обнаружит, что они не совпадают. Модель удалит переменную «Var1» и попытается создать в корневой подсистеме переменную с именем «Var2». Однако, это ей не удастся, поскольку переменная «Var2» уже есть в корневой подсистеме – ее создала модель блока «Block2», которая еще не вызывалась и не знает о том, что ей тоже нужно удалить старую переменную и создать новую. Модель блока «Block1» завершится, так и не создав новую переменную, после чего вызовется модель блока «Block2», которая сработает так, как и предполагалось – переменная «Var1» уже удалена и ничто не помешает модели создать переменную с таким именем. В результате из двух блоков-передатчиков останется работоспособным только один – тот, модель которого была вызвана позже.

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

С учетом изложенного выше, модель блока-передатчика будет выглядеть следующим образом:

      extern "C" __declspec(dllexport)
           int RDSCALL TestTunnelIn(int CallMode,
                                    RDS_PBLOCKDATA BlockData,
                                    LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart  ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define x (*((double *)(pStart+2*RDS_VSZ_S)))
        // Вспомогательная переменная – указатель на структуру подписки
        RDS_PDYNVARLINK Link;
        // Вспомогательная переменная – структура описания блока
        RDS_BLOCKDESCRIPTION Descr;

        switch(CallMode)
          { // Очистка
            case RDS_BFM_CLEANUP:
              // Запомненный указатель на структуру подписки
              Link=(RDS_PDYNVARLINK)BlockData->BlockData;
              // Удалить созданную динамическую переменную
              rdsDeleteDVByLink(Link);
              break;

            // Проверка типа статических переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Переход в режим моделирования
            case RDS_BFM_CALCMODE:
              // Запомненный указатель на структуру подписки
              Link=(RDS_PDYNVARLINK)BlockData->BlockData;
              if(Link!=NULL) // Динамическая переменная была создана
                { // Заполнить структуру описания блока
                  Descr.servSize=sizeof(Descr);
                  rdsGetBlockDescription(BlockData->Block,&Descr);
                  // Сравнить имя переменной с текстом комментария
                  if(strcmp(Link->VarName,Descr.BlockComment)!=0)
                    { // Имя переменной не совпадает с комментарием -
                      // переменная будет удалена
                      rdsDeleteDVByLink(Link);
                      // Очистить запомненный указатель на структуру подписки
                      BlockData->BlockData=NULL;
                    }
                }
              break;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              if(BlockData->BlockData!=NULL) // Переменная была создана
                break;
              // Динамической переменной нет (не было или удалена
              // в реакции на RDS_BFM_CALCMODE) - надо создать новую.
              // Сначала надо получить комментарий блока
              Descr.servSize=sizeof(Descr);
              rdsGetBlockDescription(BlockData->Block,&Descr);
              if(*Descr.BlockComment==0) // Комментарий пуст
                break;
              // Комментарий блока – не пустая строка. Создаем переменную.

              Link=rdsCreateAndSubscribeDV(RDS_DVROOT,
                                           Descr.BlockComment,
                                           "D",
                                           TRUE,
                                           NULL);
              // Запомнить новый указатель на структуру подписки
              BlockData->BlockData=Link;
              // В конце реакции на запуск расчета нет оператора break,
              // поэтому сразу после нее выполнится реакция на
              // такт расчета (чтобы начальное значение входа
              // записалось в динамическую переменную)

            // Выполнение такта моделирования
           case RDS_BFM_MODEL:
              // Запомненный указатель на структуру подписки
              Link=(RDS_PDYNVARLINK)BlockData->BlockData;
              // Проверка существования переменной
              if(Link!=NULL && Link->Data!=NULL)
                { // Переменная существует – привести указатель
                  // на область данных переменной к типу "double*"
                  double *pV=(double*)Link->Data;
                  if(*pV!=x) // Значение входа изменилось
                    { // Записать в динамическую переменную новое
                      // значение входа
                      *pV=x;
                      // Уведомить всех подписчиков об изменении
                      // переменной
                      rdsNotifyDynVarSubscribers(Link);
                    }
                }
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

Как и в предыдущем примере, в этой модели указатель на структуру подписки на созданную динамическую переменную будет запоминаться в поле структуры данных блока BlockData-<BlockData, предназначенном для хранения указателя на личную область данных блока. Однако, в этом блоке создание переменной и подписка на нее будет происходить не при инициализации блока, а при запуске расчета, поэтому эта модель не имеет реакции на событие инициализации RDS_BFM_INIT. RDS автоматически присваивает переменной BlockData->BlockData значение NULL при создании блока, что в данном случае будет означать, что динамическая переменная блока еще не создана.

Несмотря на отсутствие в модели реакции на инициализацию блока, в ней есть реакция на событие очистки RDS_BFM_CLEANUP, в которой созданная блоком динамическая переменная уничтожается при помощи функции rdsDeleteDVByLink. В эту функцию передается запомненный в BlockData->BlockData указатель на структуру подписки на созданную переменную. Если переменная так и не была создана, этот указатель будет равен NULL, и в этом случае функция rdsDeleteDVByLink не выполнит никаких действий.

При переходе RDS в режим моделирования модели всех блоков вызываются с параметром RDS_BFM_CALCMODE. По причинам, изложенным выше, именно в этот момент модель блока-передатчика должна сравнить текст комментария блока с именем созданной переменной и удалить переменную, если они не совпадают. Очевидно, что если блок до сих пор не создал динамическую переменную, эту проверку выполнять бессмысленно, поэтому сначала запомненный в BlockData->BlockData указатель на структуру подписки присваивается вспомогательной переменной Link и его значение сравнивается с NULL. Все дальнейшие действия производятся только в том случае, если Link не равно NULL, то есть у блока на данный момент есть какая-то динамическая переменная.

Текст комментария блока не содержится в структуре RDS_BLOCKDATA, указатель на которую передается в модель блока в параметре BlockData. Указатель на этот текст, как и многие другие параметры блока, можно получить только заполнив специальную структуру описания блока RDS_BLOCKDESCRIPTION при помощи сервисной функции rdsGetBlockDescription. В этой модели структура описания блока объявлена как вспомогательная переменная Descr в самом начале функции модели. Для того, чтобы записать в эту структуру параметры данного блока, необходимо вызвать функцию rdsGetBlockDescription, передав ей идентификатор блока, описание которого нужно получить (BlockData->Block), и указатель на структуру описания &Descr. Однако, сначала следует присвоить полю servSize структуры Descr размер этой структуры:

      Descr.servSize=sizeof(Descr);

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

После того, как функция rdsGetBlockDescription заполнит структуру описания блока Descr, указатель на текст комментария блока будет находиться в поле BlockComment этой структуры. Точнее, он находится в полях BlockCommentA в кодировке UTF-8 и BlockCommentW в кодировке UTF-16. Но, по умолчанию, в структуре описано поле-псевдоним BlockComment для комментария в кодировке UTF-8. Комментарий блока представляет собой строку символов, завершенную нулевым байтом. Технически комментарий может состоять из множества строк, разделенных кодами перевода строки, но в данном примере мы считаем, что комментарий блока должен содержать единственную строку – имя переменной. Модель должна сравнить текст комментария с именем динамической переменной, с которой в данный момент работает блок, и удалить эту переменную, если они отличаются. Указатель на имя переменной находится в поле VarName (это псевдоним по умолчанию для VarNameA) структуры подписки RDS_DYNVARLINK, указатель на которую в данный момент хранится во вспомогательной переменной Link. Для сравнения двух строк будет использоваться стандартная функция strcmp, описанная в файле заголовка «string.h»:

      strcmp(Link->VarName,Descr.BlockComment)

Если эта функция вернет значение, отличное от нуля, значит, имя переменной не совпадает с комментарием блока. В этом случае вызывается сервисная функция rdsDeleteDVByLink, которая удалит переменную и прекратит подписку на нее, после чего переменной BlockData->BlockData, в которой хранился указатель на структуру подписки, будет присвоено значение NULL.

При запуске расчета модель будет вызвана с параметром RDS_BFM_STARTCALC. К этому моменту все динамические переменные блоков-передатчиков, имена которых не совпадают с комментариями, должны быть уже удалены. Если на момент запуска расчета динамическая переменная существует (запомненный в BlockData->BlockData указатель на структуру подписки не равен NULL), значит, комментарий блока не изменялся, и никаких действий предпринимать не нужно – можно продолжать работать со старой переменной. В этом случае выполнение функции модели на этом прекращается. Если же указатель на структуру подписки нулевой, переменная либо еще не была создана, либо была удалена в реакции на включение режима моделирования из-за изменения комментария блока. В любом случае необходимо создать в корневой подсистеме переменную с именем, указанным в комментарии блока. Для доступа к комментарию используется описанная выше сервисная функция rdsGetBlockDescription, заполняющая вспомогательную структуру описания блока Descr. Получив строку комментария, имеет смысл проверить, содержится ли в ней какой-нибудь текст. Для этого первый символ комментария (*Descr.BlockComment) сравнивается с нулевым байтом, который завершает строку. Если первый же символ строки – нулевой, в строке ничего нет. Динамическая переменная должна иметь какое-нибудь имя, поэтому при пустой строке комментария модель завершается, не пытаясь создать переменную (для упрощения этого примера здесь не проверяется, является ли строка комментария блока допустимым именем переменной).

Если динамической переменной нет, и комментарий блока не пустой, модель вызывает функцию rdsCreateAndSubscribeDV, передавая ей в качестве имени переменной строку комментария блока (Descr.BlockComment). Эта функция создаст переменную типа double (строка «D») с указанным именем в корневой подсистеме (RDS_DVROOT) и запретит ее удаление всем блокам кроме данного (значение параметра Fixed равно TRUE). Вместо строки значения по умолчанию передается NULL – значение по умолчанию в данном случае не важно, переменной сразу же будет присвоено новое значение. Возвращаемый функцией указатель на структуру подписки на созданную переменную запоминается в BlockData->BlockData.

Следует обратить внимание на то, что, в отличие от всех остальных реакций, в конце реакции на запуск расчета отсутствует оператор break. Все реакции в этой модели, как и во всех предыдущих примерах, оформлены в виде операторов case с различными константами внутри оператора switch(CallMode). При вызове функции модели с параметром CallMode, равным RDS_BFM_STARTCALC, выполняются все операторы после «case RDS_BFM_STARTCALC» до первого оператора break или до конца оператора switch. Отсутствие break в конце реакции на запуск расчета приведет к тому, что сразу после нее выполнится часть программы, отвечающая за реакцию на такт расчета. Это сделано намеренно, чтобы при запуске расчета созданной динамической переменной было немедленно присвоено значение входа блока (именно этим и занимается функция модели в реакции на такт расчета).

При запуске модели в режиме реакции на такт расчета (RDS_BFM_MODEL) во вспомогательную переменную Link записывается запомненный указатель на структуру подписки из BlockData->BlockData, после чего проверяется, существует ли динамическая переменная. Если структура подписки была создана (значение Link не равно NULL) и ее поле Data указывает на какую-то область памяти (Link->Data!=NULL), переменная существует, и в нее можно записывать значение входа блока. В этом случае указатель на область данных переменной Link->Data приводится к типу double* и присваивается вспомогательной переменной pV – теперь для доступа к данным переменной можно использовать выражение «*pV». Если значение динамической переменной не равно значению входа блока x, ей присваивается новое значение, после чего все блоки-приемники информируются об изменении значения переменной при помощи функции rdsNotifyDynVarSubscribers.

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

Помимо двух стандартных сигналов, блок-приемник будет иметь единственный вещественный выход «y»:

Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | --- | --- | ---
0 | Start | Сигнал | 1 | Вход | ✓ | 0
1 | Ready | Сигнал | 1 | Выход |  | 0
2 | y | double | 8 | Выход |  | 0

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

Модель блока будет выглядеть следующим образом:

      extern "C" __declspec(dllexport)
           int RDSCALL TestTunnelOut(int CallMode,
                                     RDS_PBLOCKDATA BlockData,
                                     LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S)))
        // Вспомогательная переменная – указатель на структуру подписки
        RDS_PDYNVARLINK Link;
        // Вспомогательная переменная – структура описания блока
        RDS_BLOCKDESCRIPTION Descr;

        switch(CallMode)
         { // Очистка
            case RDS_BFM_CLEANUP:
              // Запомненный указатель на структуру подписки
              Link=(RDS_PDYNVARLINK)BlockData->BlockData;
              // Прекратить подписку на переменную
              rdsUnsubscribeFromDynamicVar(Link);
              break;

            // Проверка типа статических переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              // Запомненный указатель на структуру подписки
              Link=(RDS_PDYNVARLINK)BlockData->BlockData;
              // Получение описания блока (с комментарием)
              Descr.servSize=sizeof(Descr);
              rdsGetBlockDescription(BlockData->Block,&Descr);
              // Проверка наличия комментария
              if(*Descr.BlockComment==0) // Пустая строка
                { // Прекратить подписку на переменную
                  rdsUnsubscribeFromDynamicVar(Link);
                  // Очистить запомненный указатель на структуру подписки
                  BlockData->BlockData=NULL;
                  break;
                }
              // Если переменной нет (Link==NULL) или ее имя не
              // соответствует комментарию блока (strcmp... !=0),
              // нужно подписаться на новую переменную
              if(Link==NULL ||
                 strcmp(Link->VarName,Descr.BlockComment)!=0)
                { // Прекратить подписку на старую переменную
                  rdsUnsubscribeFromDynamicVar(Link);
                  // Подписаться на новую
                  Link=rdsSubscribeToDynamicVar(RDS_DVROOT,
                                                Descr.BlockComment,
                                                "D",
                                                FALSE);
                  // Запомнить новый указатель на структуру подписки
                  BlockData->BlockData=Link;
                }
              // В конце реакции на запуск расчета нет оператора break,
              // поэтому сразу после нее выполнится реакция на
              // изменение динамической переменной (чтобы ее значение
              // было немедленно передано на выход блока)

            // Реакция на изменение динамической переменной
            case RDS_BFM_DYNVARCHANGE:
              // Запомненный указатель на структуру подписки
              Link=(RDS_PDYNVARLINK)BlockData->BlockData;
              // Проверка существования переменной
              if(Link!=NULL && Link->Data!=NULL)
                { // Присвоить выходу блока значение динамической
                  // переменной
                  y=*(double*)Link->Data;
                  // Взвести сигнал Ready, чтобы значение выхода
                  // было передано по связям
                  Ready=1;
                }
              break;
          }

        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

В этой модели, как и в модели блока-передатчика, нет реакции на инициализацию блока. Чтобы можно было отслеживать изменение комментария блока пользователем, подписка на динамическую переменную для получения данных от блока-передатчика производится не при инициализации, а при реакции на запуск расчета, то есть заведомо после того, как пользователь мог изменить комментарий. Указатель на структуру подписки, так же, как и в предыдущих двух моделях, будет храниться в поле BlockData->BlockData, которому RDS автоматически присваивает значение NULL при создании блока (в данном случае это будет означать, что блок еще не подписывался на переменную). При вызове с параметром RDS_BFM_CLEANUP модель прекращает подписку блока на динамическую переменную, вызывая функцию rdsUnsubscribeFromDynamicVar, в которую передается указатель на структуру подписки из BlockData->BlockData (предварительно он приводится к типу RDS_PDYNVARLINK и присваивается вспомогательной переменной Link).

При запуске расчета (вызов с параметром RDS_BFM_STARTCALC) модель блока-приемника должна подписаться на динамическую переменную для связи с передатчиком, если она еще не подписана на нее, или если комментарий блока изменился. Запомненный в BlockData->BlockData указатель на структуру подписки присваивается вспомогательной переменной Link, после чего при помощи сервисной функции rdsGetBlockDescription заполняется структура описания блока Descr, в поле BlockComment которой эта функция записывает указатель на строку комментария блока (получение указателя на текст комментария было подробно описано выше, в пояснениях к модели блока-передатчика). Затем, модель проверяет, содержит ли комментарий блока какой-либо текст. Если комментарий пуст, модель не сможет продолжать работу – для подписки на динамическую переменную необходимо знать ее имя. Если первый символ строки комментария имеет код 0, значит, строка комментария пуста – в этом случае модель прекращает подписку на старую переменную при помощи функции rdsUnsubscribeFromDynamicVar и возвращает управление RDS (если в данный момент блок не был подписан ни на какую переменную, в функцию rdsUnsubscribeFromDynamicVar вместо указателя на структуру подписки будет передано значение NULL, и она завершится, на выполнив никаких действий).

После того, как модель установила, что комментарий блока содержит какой-то текст, она может сравнить этот текст с именем переменной, на которую подписан блок. Если блок вообще не подписан на переменную (значение Link равно NULL) или имя переменной не совпадает с текстом комментария (строки Link->VarName и Descr.BlockComment не совпадают, проверка производится при помощи функции strcmp), модель прекращает подписку на старую переменную, если она существовала, и подписывается на новую при помощи функции rdsSubscribeToDynamicVar. Переменная типа double (строка «D») ищется в корневой подсистеме (константа RDS_DVROOT) без поиска по иерархии (параметр Search равен FALSE), в качестве имени переменной передается строка комментария блока Descr.BlockComment. Функция возвращает указатель на структуру подписки, который запоминается в BlockData->BlockData для дальнейшей работы.

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

Каждый раз, когда модель блока-передатчика вызывает функцию rdsNotifyDynVarSubscribers после изменения своей динамической переменной, модели блоков-приемников, подписанных на эту переменную, будут вызваны с параметром RDS_BFM_DYNVARCHANGE. Реагируя на это событие, модель присваивает выходу блока y значение динамической переменной (для этого указатель на область данных переменной сначала приводится к типу «указатель на double») и взводит стандартный сигнал Ready, что приведет к передаче данных выхода блока по связям в ближайшем такте расчета.

Рис. 41. Связь блоков через
динамическую переменную

Для проверки работы созданных моделей следует поместить в схему блок-приемник и блок-передатчик (они могут находиться в разных подсистемах) и соединить вход передатчика с полем ввода, а выход приемника – с индикатором (рис. 41). В комментарий обоих блоков следует ввести одну и ту же строку имени переменной (например, «Var1»). При запуске расчета значения, вводимые в поле ввода, подключенное к передатчику, должны отображаться на индикаторе, подключенном к приемнику.