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

Описания RDS

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

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

§2.7. Настройка параметров блока

§2.7.4. Хранение настроечных параметров в переменных блока

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

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

### §2.7. Настройка параметров блока

#### §2.7.4. Хранение настроечных параметров в переменных блока

Описывается хранение настроечных параметров блока в значениях по умолчанию его статических переменных. Этот способ не требует введения в модель новых реакций, к тому же, он позволяет пользователю выбирать: задавать ли ему параметр вручную в окне настроек блока или подключить к нему связь и передавать ему значение откуда-нибудь из схемы. Приведен пример блока, вычисляющего y=K×x+C, где x – вход, а K и C могут либо задаваться в окне настройки, либо получать свои значения по связям от других блоков.

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

Чтобы не писать реакции на загрузку и сохранение параметров, а заодно дать пользователю возможность самому выбирать, какие параметры он будет вводить в функции настройки, можно хранить настроечные параметры в значениях по умолчанию статических входов блока. Во-первых, значения переменных по умолчанию автоматически сохраняются при сохранении схемы. Во-вторых, если к такому входу не подключать связь, значение переменной останется равным значению по умолчанию, то есть значению, введенному в функции настройки, а если пользователь соединит этот вход с выходом другого блока, значение входа будет определяться сработавшей связью. Таким образом один и тот же параметр может и вводиться в функции настройки, и работать, как обычный вход блока. Нужно только отслеживать наличие соединенной с этим входом связи и не разрешать пользователю изменять параметр, если эта связь существует. Многие блоки из стандартной библиотеки «Common.dll» устроены именно так.

Для примера создадим блок, вычисляющий значение выхода «y» по формуле «y=K×x+C», где «x» – обычный вход блока, а «K» и «C» – входы блока, которые также могут задаваться в функции настройки. Блок будет иметь следующую структуру переменных:

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

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

      // Проверка наличия связи у входа блока
      BOOL CheckBlockInputConnection(
        RDS_BHANDLE Block,             // Идентификатор блока
        int num,                       // Номер входа
        RDS_PVARDESCRIPTION pVarDescr) // Указатель на структуру
                                       // описания переменной
      { RDS_CHANDLE c;                // Идентификатор связи
        RDS_POINTDESCRIPTION PtDescr; // Структура описания точки связи

        // Заполнение служебных полей структур их размерами
        PtDescr.servSize=sizeof(PtDescr);
        pVarDescr->servSize=sizeof(RDS_VARDESCRIPTION);

        // Получение описания переменной блока по номеру
        if(rdsGetBlockVar(Block,num,pVarDescr)==NULL)
          return FALSE; // Нет такой переменной

        // Перебор всех связей, подключенных к этому блоку
        c=NULL;
        for(;;)
          { // Найти связь, следующую за c, и заполнить структуру
            // описания точки соединения PtDescr
            c=rdsGetBlockLink(Block,c,TRUE,FALSE,&PtDescr);
            if(c==NULL) // Больше нет связей
              break;
            // Найдена очередная связь – сравнение имени заданной
            // переменной с именем переменной точки этой связи
            if(strcmp(PtDescr.VarName,pVarDescr->Name)==0)
              return TRUE; // Имена совпали – есть связь, соединенная
                           // с переменной блока
          }
        // Все подключенные связи перебраны, а связь, подключенная
        // к заданной переменной так и не была найдена
        return FALSE;
      }
      //=========================================

Функция принимает три параметра: идентификатор блока, для переменной которого проверяется наличие связи (Block), порядковый номер переменной в блоке (num) и указатель на внешнюю структуру описания переменной (pVarDescr). Возвращаемое функцией значение должно сигнализировать о наличии (TRUE) или отсутствии (FALSE) связи, соединенной с указанной переменной. Кроме того, функция будет записывать описание этой переменной в структуру, указатель на которую передан в параметре pVarDescr – из этой структуры можно будет считать имя переменной для формирования заголовка поля ввода.

В начале функции описаны две вспомогательные переменные: идентификатор связи c и структура описания точки связи PtDescr. Идентификатор связи будет использоваться при поиске связей, соединенных с блоком Block, а через структуру описания точки можно будет определить имя переменной блока, к которой присоединена найденная связь.

Прежде чем можно будет вызывать сервисные функции RDS, заполняющие структуры описания переменной и точки связи, необходимо присвоить полю servSize каждой из них размер соответствующей структуры – чтобы избежать конфликта версий, сервисные функции проверяют размер структур, с которыми они работают. Затем описание переменной с номером num записывается в структуру по указателю pVarDescr при помощи сервисной функции rdsGetBlockVar:

      RDS_VHANDLE RDSCALL rdsGetBlockVar(
        RDS_BHANDLE Block,           // Блок
        int varnum,                  // Номер переменной блока
        RDS_PVARDESCRIPTION pDescr); // Заполняемая структура описания переменной

В функцию передается идентификатор блока (параметр Block) и порядковый номер переменной в этом блоке, начиная с нуля (параметр varnum). Если переменная с таким номером существует в блоке, функция возвращает уникальный идентификатор этой переменной и заполняет ее описанием структуру, указатель на которую передан в параметре pDescr. Если же такой переменной нет в блоке , функция возвращает NULL.

В этом примере функции CheckBlockInputConnection не требуется уникальный идентификатор переменной – возвращенное функцией значение просто проверяется на NULL. Если CheckBlockInputConnection вернула NULL, значит, переменной с номером num нет в блоке Block, и никакой связи у этой отсутствующей переменной быть не может – функция возвращает FALSE. В противном случае переменная существует, и структура pVarDescr (типа RDS_VARDESCRIPTION) заполнена ее описанием. Эта структура подробно описана в приложении А, сейчас в ней нас будет интересовать единственное поле Name – указатель на строку с именем переменной.

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

      RDS_CHANDLE RDSCALL rdsGetBlockLink(
        RDS_BHANDLE Block,                  // Идентификатор блока
        RDS_CHANDLE Conn,                   // Предыдущая связь
        BOOL Inputs,                        // Искать соединенные со входами
        BOOL Outputs,                       // Искать соединенные с выходами
        RDS_PPOINTDESCRIPTION pPointDescr); // Заполняемая структура описания точки связи

Если параметр Conn равен NULL, функция ищет первую связь, соединенную с блоком Block. Если Conn не NULL, функция ищет следующую после Conn связь. Таким образом, последовательно вызывая rdsGetBlockLink и передавая в параметре Conn результат прошлого вызова, можно перебрать все связи, соединенные с блоком. Если в параметре Conn передать идентификатор последней связи, функция вернет NULL, сигнализируя о том, что больше у блока связей нет. Параметр Inputs указывает на необходимость искать связи, соединенные со входами блока, Outputs – на необходимость искать связи, соединенные с выходами. Хотя бы один из этих параметров должен быть истинным, иначе функция сразу вернет NULL – бессмысленно искать связь блока, не соединенную ни с входом, ни с выходом. В параметре pPointDescr передается указатель на структуру, которая заполняется описанием точки связи, соответствующей входу или выходу данного блока.

Связи блока перебираются в цикле for, перед которым вспомогательной переменной c присваивается значение NULL. В цикле вызывается rdsGetBlockLink, в которую в качестве идентификатора предыдущей связи передается значение c (сначала c равно NULL, поэтому при первом вызове функция найдет первую связь, соединенную с блоком). Поскольку мы проверяем наличие связей у входа блока, в параметре Inputs передается TRUE, а в параметре Outputs – FALSE. Функция должна вернуть идентификатор найденной связи (присваивается переменной c) и заполнить структуру PtDescr типа RDS_POINTDESCRIPTION описанием точки, которая соединяет эту связь с блоком. Обычно такая точка размещается на границе картинки блока и около нее отображается имя переменной, к которой подсоединена связь. Описание структуры RDS_POINTDESCRIPTION приведено в приложении А, нам потребуется только ее поле VarName, в котором содержится указатель на строку с именем переменной блока, к которой присоединена связь.

Если функция rdsGetBlockLink не нашла очередной связи блока, значение переменной c будет равно NULL – в этом случае цикл будет прерван оператором break. В противном случае имя переменной из структуры описания переменной блока pVarDescr сравнивается с именем переменной из структуры описания точки связи PtDescr при помощи стандартной функции сравнения строк strcmp. При совпадении имен strcmp вернет 0. Это означает, что найденная при помощи rdsGetBlockLink связь соединена с заданной переменной. В этом случае возвращается TRUE – мы проверяли, подключена ли связь к переменной блока с номером num, и только что нашли такую связь. Если же имена не совпали, цикл продолжается, при этом в переменной c содержится идентификатор найденной связи, и при следующем вызове rdsGetBlockLink найдет следующую связь. Так будет продолжаться до тех пор, пока имя переменной в структуре PtDescr не совпадет с именем заданной переменной и не выполнится команда «return TRUE», либо пока не будут перебраны все связи и цикл не будет прерван оператором break – в этом случае возвращается FALSE, поскольку все связи блока перебраны, и ни одна из них не соединяется с заданной переменной.

Следует отметить, что функцию CheckBlockInputConnection нельзя применять для проверки наличия связей у входов-структур и входов-массивов – она может работать только с простыми переменными. Дело в том, что у сложных переменных связь может быть присоединена не только ко всей переменной целиком, но и к ее части, например, к полю структуры или элементу массива. Например, если переменная «M» – массив, то связь может быть присоединена к «M[0]», «M[1]» и т.д. Внутри функции имя заданной переменной сравнивается с именем переменной, взятым из точки связи, и, поскольку строки «M» и «M[0]» не совпадают, связь, идущая к элементу массива не будет найдена. Однако, поскольку нам эта функция будет нужна для проверки наличия связи у хранящегося в переменной блока параметра, на это ограничение можно не обращать внимания – параметры обычно хранятся в простых переменных.

Может возникнуть вопрос: почему в функцию CheckBlockInputConnection передается номер переменной, если для поиска связей используется ее имя? Если бы в функцию сразу передавалось имя переменной, можно было бы не вызывать rdsGetBlockVar. Однако, в этом случае при изменении имени переменной пришлось бы переписывать модель блока, поскольку строка имени переменной была бы жестко «зашита» в вызов CheckBlockInputConnection. Использование номеров переменных позволяет привязываться не к их именам, а к порядку их следования, изменять который в любом случае не следует – большинство моделей рассчитано на жесткую структуру статических переменных, проверяемую в вызове модели с параметром RDS_BFM_VARCHECK.

Теперь напишем функцию, которая, в зависимости от наличия подключенной к вещественному (double) входу связи, будет добавлять в окно настройки, созданное сервисной функцией rdsFORMCreate (см. §2.7.2), поле для ввода значения переменной (если связи нет) или поле для его индикации (если связь есть). Кроме того, при наличии связи к заголовку поля ввода будет добавляться текст «подключена связь». Как и CheckBlockInputConnection, эта функция будет возвращать логическое значение, соответствующее наличию или отсутствию связи у заданного входа.

      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      BOOL AddWinEditOrDisplayDouble(
        RDS_HOBJECT window, // Идентификатор объекта-окна
        RDS_BHANDLE Block,  // Блок
        int varnum,         // Номер переменной в блоке
        int ctrlnum,        // Идентификатор поля ввода в окне
        char *title)        // Заголовок поля или NULL
      { // Структура описания переменной блока
        RDS_VARDESCRIPTION VarDescr;
        // Проверка наличия связи у переменной varnum в блоке Block
        // и заполнение структуры VarDescr описанием переменной
        BOOL conn=CheckBlockInputConnection(Block,varnum,&VarDescr);

        if(conn) // К переменной подключена связь
          { // Вспомогательные переменные
            char *caption; // Заголовок поля
            double *cur;   // Указатель на данные переменной
            // Заголовок поля формируется из имени переменной и
            // текста "подключена связь"
            caption=rdsDynStrCat(title?title:VarDescr.Name,
                                 " (подключена связь)",
                                 FALSE);
            // Добавление поля для индикации текущего значения
            rdsFORMAddEdit(window,0,ctrlnum,RDS_FORMCTRL_DISPLAY,
                           caption,80);
            // Освобождение динамически сформированной строки
            // заголовка поля
            rdsFree(caption);
            // Получение указателя на данные переменной
            cur=(double*)rdsGetBlockVarBase(Block,varnum,NULL);
            // Проверка – переменная должна существовать и иметь тип double
            if(cur!=NULL && VarDescr.Type=='D')
              // Занесение текущего значения переменной в поле
              rdsSetObjectDouble(window,ctrlnum,RDS_FORMVAL_VALUE,*cur);
          }
        else // К переменной не подключена связь
          { // Вспомогательная переменная для значения по умолчанию
            char *defval;
            // Получение строки со значением переменной по умолчанию
            // (необходимо потом освободить при помощи rdsFree)
            defval=rdsGetBlockVarDefValueStr(Block,varnum,NULL);
            // Добавление поля для ввода параметра
            rdsFORMAddEdit(window,0,ctrlnum,RDS_FORMCTRL_EDIT,
                           title?title:VarDescr.Name,80);
            // Занесение в поле ввода значения перменной по умолчанию
            rdsSetObjectStr(window,ctrlnum,RDS_FORMVAL_VALUE,defval);
            // Освобождение динамически сформированной строки
            rdsFree(defval);
          }
        // Возврат: TRUE – есть связь, FALSE – нет связи
        return conn;
      }
      //=========================================

Функция принимает пять параметров: идентификатор объекта-окна (window), идентификатор блока, значение переменной которого будет редактироваться или отображаться (Block), номер переменной в блоке (varnum), идентификатор поля, которое должно быть добавлено в окно (ctrlnum), и строка-заголовок поля ввода (title), вместо которой может быть передано значение NULL для использования в качестве заголовка имени переменной. В начале функции описывается структура VarDescr, после чего вызывается ранее написанная функция CheckBlockInputConnection, которая заносит в нее описание переменной с номером varnum в блоке Block и возвращает логическое значение, соответствующее наличию связи, присоединенной к этой переменной. Это значение присваивается вспомогательной переменной conn.

Если conn истинно (TRUE), у переменной есть связь, и ее значение нельзя редактировать. В этом случае надо добавить в окно window поле для индикации текущего значения переменной и сформировать заголовок этого поля из параметра title или имени переменной (его можно получить из поля Name структуры VarDescr) и текста «подключена связь». Сначала при помощи функции rdsDynStrCat в памяти формируется строка заголовка и указатель на нее присваивается вспомогательной переменной caption – позже эту строку нужно будет освободить функцией rdsFree. В первых двух параметрах функции передаются объединяемые строки, в третьем – значение FALSE, запрещающее функции возвращать NULL вместо пустой строки (в данном случае можно было передать и TRUE, поскольку второй параметр функции – не пустая строковая константа, и в результате объединения строк не может получиться пустая строка). Затем вызывается функция rdsFORMAddEdit, добавляющая в окно window поле индикации (RDS_FORMCTRL_DISPLAY) с идентификатором ctrlnum и шириной в 80 точек экрана. В качестве заголовка поля передается динамически сформированная строка caption. После вызова rdsFORMAddEdit строка caption больше не нужна, и она освобождается при помощи rdsFree.

Теперь в добавленное поле необходимо занести текущее значение переменной. Раньше для доступа к переменным в моделях блоков использовались макросы, вычислявшие начальный адрес переменной блока по начальному адресу дерева переменных из структуры данных блока (RDS_BLOCKDATA) и фиксированному смещению. В эту функцию не передается указатель на структуру данных блока, поэтому макросы использовать нельзя – неоткуда получить начальный адрес дерева переменных. Для получения начального адреса переменной будет использоваться сервисная функция rdsGetBlockVarBase – это медленнее, чем обращение к переменной по фиксированному смещению, но при открытии окна настроек высокие скорости не нужны:

      LPVOID RDSCALL rdsGetBlockVarBase(
        RDS_BHANDLE Block,  // Идентификатор блока
        int num,            // Номер переменной
        int *pSize);        // Возвращаемый размер переменной

В функцию передается идентификатор блока, номер переменной в нем, а также указатель на целую переменную, в которую нужно записать размер переменной блока в дереве. В данном случае нам не нужен размер переменной (мы знаем, что переменная должна иметь тип double и ее размер должен равняться восьми байтам), поэтому в параметре pSize передается NULL. Возвращаемое функцией значение – начальный адрес переменной – приводится к типу «указатель на double» и записывается во вспомогательную переменную cur. Перед тем, как обращаться к переменной, нужно на всякий случай проверить, существует ли в блоке переменная с таким номером и имеет ли она тип double. Для проверки наличия переменной значение cur сравнивается с NULL: если переменной с указанным номером нет в блоке, rdsGetBlockVarBase возвращает нулевое значение. Тип переменной можно считать из поля Type структуры VarDescr – как и везде в RDS, типу double соответствует символ «D». Если оба условия выполнены, текущее значение переменной заносится в поле индикации функцией rdsSetObjectDouble.

Если conn имеет значение FALSE, к переменной не присоединено ни одной связи, и пользователю можно разрешить редактировать ее значение по умолчанию. Для получения значения переменной по умолчанию (оно может отличаться от текущего значения переменной, если ранее к ней была подключена связь и схема некоторое время проработала в режиме расчета), будет использоваться функция rdsGetBlockVarDefValueStr:

      RDSSTR RDSCALL rdsGetBlockVarDefValueStrA( // В UTF8
        RDS_BHANDLE Block, // Идентификатор блока
        int num,           // Номер переменной
        int *pLength);     // Возвращаемая длина строки
      RDSWSTR RDSCALL rdsGetBlockVarDefValueStrW( // В UTF16
        RDS_BHANDLE Block, // Идентификатор блока
        int num,           // Номер переменной
        int *pLength);     // Возвращаемая длина строки
      // Функция-псевдоним
      RDSXSTR RDSCALL rdsGetBlockVarDefValueStr( // В кодировке по умолчанию
        RDS_BHANDLE Block, // Идентификатор блока
        int num,           // Номер переменной
        int *pLength);     // Возвращаемая длина строки

Как и многие другие функции для работы с переменными блока, эта функция принимает идентификатор блока и номер переменной в этом блоке. Независимо от типа переменной, функция возвращает ее значение по умолчанию в виде указателя на динамически сформированную в памяти строку (ее нужно будет освободить при помощи rdsFree), при этом, если параметр pLength не равен NULL, в целую переменную, на которую указывает pLength, записывается длина этой строки. В данном случае мы работаем с переменными блока типа double, поэтому строка, возвращенная функцией, будет содержать символьное представление вещественного числа двойной точности (число знаков после запятой будет подобрано автоматически). Длина строки нас не интересует, поэтому в параметре pLength передается NULL. Указатель на строку, возвращенный функцией, записывается во вспомогательную переменную defval.

Теперь функцией rdsFORMAddEdit в окно window добавляется поле ввода (RDS_FORMCTRL_EDIT) шириной в 80 точек экрана с идентификатором ctrlnum. В качестве заголовка поля используется значение параметра title, или, если оно равно NULL, имя переменной блока из поля Name структуры описания переменной. При помощи функции rdsSetObjectStr в поле ввода заносится строка со значением переменной по умолчанию defval, после чего память, занятая этой строкой, освобождается при помощи rdsFree.

После того, как обе вспомогательные функции готовы, можно заняться моделью блока с функцией настройки:

      // Функция настройки K и C для модели блока y=Kx+C
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      BOOL TestKxCSetup(
        RDS_BHANDLE Block, // Идентификатор блока
        int numK,          // Номер переменной K в блоке
        int numC)          // Номер переменной C в блоке
      { RDS_HOBJECT window; // Идентификатор вспомогательного объекта
        BOOL ok;            // Пользователь нажал "OK"
        BOOL K_conn,C_conn; // Флаги наличия связей у K и C

        // Создание окна
        window=rdsFORMCreate(FALSE,-1,-1,"Kx+C");

        // Добавление полей для ввода или индикации K и C
        // (в зависимости от наличия связей)
        K_conn=AddWinEditOrDisplayDouble(window,Block,numK,1,NULL);
        C_conn=AddWinEditOrDisplayDouble(window,Block,numC,2,NULL);

        // Открытие окна
        ok=rdsFORMShowModalEx(window,NULL);
        if(ok)
          { // Нажата кнопка OK – запись параметров в блок
            if(!K_conn) // У K нет связи
              { // Получение строки из поля ввода
                char *str=rdsGetObjectStr(window,1,RDS_FORMVAL_VALUE);
                // Установка значения переменной K по умолчанию
                rdsSetBlockVarDefValueStr(Block,numK,str);
              }
            if(!C_conn) // У С нет связи
              { // Получение строки из поля ввода
                char *str=rdsGetObjectStr(window,2,RDS_FORMVAL_VALUE);
                // Установка значения переменной C по умолчанию
                rdsSetBlockVarDefValueStr(Block,numC,str);
              }
          }
        // Уничтожение окна
        rdsDeleteObject(window);
        // Возвращаемое значение – истина, если нажата “OK"
        return ok;
      }
      //=========================================

      // Функция модели блока
      extern "C" __declspec(dllexport)
        int RDSCALL TestKxC(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)))
      #define K (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
      #define C (*((double *)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_D)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S+3*RDS_VSZ_D)))
        switch(CallMode)
          { // Проверка типа переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSDDDD}")==0)
                return RDS_BFR_DONE;
              return RDS_BFR_BADVARSMSG;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              // Если это запуск с начала, взвести Start
              if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart)
                Start=1; // Модель запустится в первом же такте
              break;

            // Такт расчета
            case RDS_BFM_MODEL:
              y=K*x+C;
              break;

            // Функция настройки
            case RDS_BFM_SETUP:
              if(TestKxCSetup(BlockData->Block,3,4))
                { // Нажата "OK"
                  Start=1; // Запустить модель в следующем такте
                  return RDS_BFR_MODIFIED;
                }
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef C
      #undef K
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

Для улучшения читаемости программы настройка параметров блока вынесена в отдельную функцию TestKxCSetup. Она принимает три параметра: идентификатор блока (Block) и порядковые номера переменных «K» и «С» (numK и numС соответственно). Внутри функции при помощи rdsFORMCreate создается вспомогательный объект-окно, и его идентификатор присваивается вспомогательной переменной window. Затем при помощи ранее созданной функции AddWinEditOrDisplayDouble в это окно добавляются поля ввода или индикации для параметров «K» и «C» (в обоих вызовах мы будем использовать в качестве заголовка поля ввода имя соответствующей переменной, поэтому в последнем параметре функции передается значение NULL). Для переменной «K» добавляется поле с идентификатором 1, при этом вспомогательной логической переменной K_conn присваивается TRUE, если к переменной присоединена связь, и FALSE в противном случае. Для переменной «C» добавляется поле с идентификатором 2, факт наличия или отсутствия у нее связи заносится в C_conn. Теперь можно открывать окно: вызывается функция «S») и четыре переменных типа double («D»).

Для того, чтобы в самом первом такте моделирования было вычислено значение выхода блока, соответствующее начальным значениям входа и параметров, в функцию модели введена реакция на запуск расчета (RDS_BFM_STARTCALC). При вызове модели в этом режиме в параметре ExtParam передается указатель на структуру RDS_PSTARTSTOPDATA. Она описана в «RdsDef.h» следующим образом:

      typedef struct
      { BOOL FirstStart; // TRUE  - расчет запущен с начала
                         // FALSE – расчет продолжен после остановки
        BOOL Loop;       // TRUE  - запуск непрерывного расчета
                         // FALSE – запуск расчета на один такт
      } RDS_STARTSTOPDATA;
      // Указатель на структуру
      typedef RDS_STARTSTOPDATA *RDS_PSTARTSTOPDATA;

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

В реакции на такт расчета (RDS_BFM_MODEL) модель вычисляет значение выхода y по значениям входа x и параметров K и С. При этом не важно, получает модель значения параметров по связям или они введены пользователем в функции настройки. В обоих случаях можно брать текущее значение соответствующей переменной.

В режиме RDS_BFM_SETUP модель вызывает функцию настройки TestKxCSetup, передавая ей идентификатор данного блока из структуры BlockData и порядковые номера переменных K (3) и С (4). Если функция вернула TRUE, взводится сигнал Start, чтобы в ближайшем такте расчета было вычислено новое значение выхода по изменившимся параметрам, после чего возвращается значение RDS_BFR_MODIFIED, информирующее RDS о наличии изменений в схеме.

Рис. 52. Блок, хранящий настройки в
значениях входов по умолчанию

Чтобы проверить работу этого блока, можно собрать схему, в которой только к одному из двух параметров будет подключено поле ввода – например, к параметру «K» (рис. 52). В окне параметров блока на вкладке «DLL» нужно установить флаг «блок имеет функцию настройки». Теперь при выборе соответствующего пункта контекстного меню блока (или по двойному щелчку, если на вкладке «общие» установить флаг «двойной щелчок в режиме редактирования вызывает функцию настройки») будет открываться окно для ввода значений «K» и «С». В этом окне будут заблокированы значения параметров, к которым подключены связи.