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

Описания RDS

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

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

§2.5. Статические переменные блоков

§2.5.6. Работа с переменными произвольного типа

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

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

### §2.5. Статические переменные блоков

#### §2.5.6. Работа с переменными произвольного типа

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

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

Данные переменной произвольного типа в дереве переменных занимают 16 байтов (рис. 29). В первых восьми хранится указатель на динамически отводимую память, в которой размещаются данные того фактического типа, который в данный момент имеет эта переменная (в тридцатидвухбитной версии RDS этот указатель занимает только первые 4 байта восьмибайтной области). Если переменная в данный момент не имеет фактического типа, там содержится нулевой указатель NULL. Во вторых восьми байтах хранится служебная информация, по которой RDS определяет фактический тип переменной (модель не должна изменять их значения).

Рис. 29. Размещение в памяти данных переменной произвольного типа

Для любого фактического типа отводимая область данных устроена точно так же, как у статической переменной такого же типа в дереве переменных блока. На рис. 30 приведено несколько примеров размещения в памяти переменных с разными фактическими типами.

Рис. 30. Примеры отведения памяти в переменной произвольного типа для разных фактических типов

Самое простое и наиболее часто используемое действие над переменными произвольного типа – копирование данных одной переменной в другую при помощи сервисной функции rdsCopyRuntimeType. Именно эта функция используется в моделях различных блоков-переключателей. В качестве примера рассмотрим простой блок со входом произвольного типа «x», логическим входом «Enable» и выходом произвольного типа «y». Если значение входа «Enable» равно единице, блок должен передавать данные со входа «x» на выход «y» независимо от их типа. Если же значение «Enable» равно нулю, блок не должен пропускать данные на выход. Такой блок можно использовать в качестве универсального выключателя, разрешающего или запрещающего передачу данных в зависимости от значения «Enable». Его можно вставить в разрыв связи любого типа − на рис. 31 слева он управляет передачей вещественного числа, справа – передачей матрицы. Блок сам изменит тип своего выхода y согласно типу входа «x».

Рис. 31. Универсальный выключатель, работающий с переменными любых типов

Блок будет иметь следующую структуру переменных:

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

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

      extern "C" __declspec(dllexport)
        int RDSCALL TestVarSwitch(int CallMode,
                                  RDS_PBLOCKDATA BlockData,
                                  LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define px ((void **)(pStart+2*RDS_VSZ_S))
      #define Enable (*((char *)(pStart+2*RDS_VSZ_S+RDS_VSZ_V)))
      #define py ((void **)(pStart+2*RDS_VSZ_S+RDS_VSZ_V+RDS_VSZ_L))

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

            // Выполнение такта моделирования
            case RDS_BFM_MODEL:
              if(Enable) // Передача разрешена
                rdsCopyRuntimeType(py,px); // Копирование x в y
              else // Передача запрещена
                Ready=0; // Блокировка передачи данных по связям
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef py
      #undef Enable
      #undef px
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

Для переменных произвольного типа «x» и «y» вместо определений для доступа к самим переменным вводятся определения для указателей px и py. Эти указатели передаются в сервисные функции, обслуживающие переменные произвольного типа (в том числе и в rdsCopyRuntimeType).

Проверка типа переменных производится стандартным образом – в данном случае переменным произвольного типа соответствуют буквы «V» в строке, передаваемой в модель блока при вызове RDS_BFM_VARCHECK. При вызове модели с параметром RDS_BFM_MODEL проверяется значение логического входа Enable. Если это значение ненулевое, то есть передача данных разрешена, вызывается функция rdsCopyRuntimeType(py,px), копирующая значение входа x в выход y. Эта функция самостоятельно отводит память для нового значения y и, при необходимости, освобождает память, занимаемую прежним значением – никаких дополнительных действий от программиста не требуется. Если же значение входа Enable равно нулю, т.е. передача данных запрещена, сигнальному выходу Ready присваивается 0. Как уже упоминалось выше, сигнал Ready – один из двух обязательных сигналов, присутствующих в каждом простом блоке. Если значение Ready будет равно нулю, связи, соединенные с выходом этого блока, не сработают, что и требуется при запрещении передачи данных. Значение 1 присваивается сигналу Ready автоматически при каждом запуске модели, поэтому явно присваивать ему единицу при разрешении передачи не требуется.

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

Модель блока-выключателя получилась очень простой, поскольку, несмотря на сложную структуру переменных произвольного типа, копирование данных из одной переменной в другую производится внутри сервисной функции rdsCopyRuntimeType. Модель блока, самостоятельно анализирующая фактический тип переменной и считывающая ее данные, или самостоятельно присваивающая значение выходу произвольного типа, будет гораздо сложнее. На самом деле, необходимость в создании таких моделей возникает очень редко – как правило, не существует действия, которое можно было бы выполнить и над числами, и над матрицами, и над строками, и над всеми возможными структурами. Для выполнения действий над группой похожих типов (например, над числами всех видов) можно обойтись и без произвольного типа. RDS позволяет соединять связями входы и выходы разных типов, если эти типы могут быть приведены один к другому (например, допускается присоединение выхода типа double ко входу типа int, и наоборот). Для выполнения какого-либо действия обычно достаточно создать модель, выполняющую это действие над переменными наиболее общего типа. Например, блок, вычисляющий сумму двух вещественных чисел типа double, может также использоваться для сложения чисел типа int, char, short и float.

Тем не менее, модель блока может, при необходимости, работать с данными переменной произвольного типа непосредственно. Рассмотрим в качестве примера модель блока с входом произвольного типа «x», выдающую строку фактического типа этого входа на выход «type». Кроме того, если вход «x» имеет фактический тип double или int, эта модель должна выдать его значение на вещественный выход «val». Если «x» – массив или матрица любого типа, на «val» необходимо выдать число элементов в матрице. Если же «x» имеет другой фактический тип, выход «val» должен быть равен нулю. Практическая ценность этого примера сомнительна, но он позволит проиллюстрировать работу с произвольным типом. Блок будет иметь следующую структуру переменных:

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

Для получения строки фактического типа входа «x» и указателя на его область данных будем использовать сервисную функцию rdsGetRuntimeTypeData. Модель блока будет выглядеть следующим образом:

      extern "C" __declspec(dllexport)
        int RDSCALL TestVar1(int CallMode,
                             RDS_PBLOCKDATA BlockData,
                             LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define px ((void **)(pStart+2*RDS_VSZ_S))
      #define type (*((char **)(pStart+2*RDS_VSZ_S+RDS_VSZ_V)))
      #define val (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_V+RDS_VSZ_A)))
        // Вспомогательные переменные
        char *s;  // Строка фактического типа входа x
        void *v;  // Указатель на данные входа x

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

            // Выполнение такта моделирования
            case RDS_BFM_MODEL:
              // Освобождение прежнего значения строки type
              rdsFree(type);
              // Получение указателя на данные (v)
              // и строки фактического типа (s) входа x
              v=rdsGetRuntimeTypeData(px,&s);
              // Занесение строки типа в выход type
              type=s;
              // Анализ типа, если переменная не пуста
              if(v!=NULL) // У входа x есть фактический тип
                switch(*s) // Анализ первого символа строки типа
                  { case 'D': // Фактический тип – double
                      val=*((double*)v);
                      break;
                    case 'I': // Фактический тип – int
                      val=*((int*)v);
                      break;
                    case 'M': // Фактический тип – матрица или массив
                      if(RDS_ARRAYEXISTS(v)) // Матрица не пуста
                        val=RDS_ARRAYROWS(v)*RDS_ARRAYCOLS(v);
                      else // Матрица пуста (0x0)
                        val=0;
                      break;
                    default: // Другой фактический тип
                      val=0.0;
                  } // Конец switch(*s)
              else // У входа x нет фактического типа
                val=0.0;
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef val
      #undef type
      #undef px
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

При вызове модели с параметром RDS_BFM_MODEL сначала вызывается функция rdsFree для освобождения данных прежнего значения выходной строки type. Далее при помощи функции rdsGetRuntimeTypeData определяется фактический тип входа x. В первом параметре функции передается указатель на исследуемую переменную произвольного типа (px), во втором – указатель на переменную, в которую нужно записать указатель на динамически сформированную строку фактического типа (в данном случае передается указатель на вспомогательную переменную s). Функция возвращает указатель на область данных входа, который присваивается вспомогательной переменной v. Если у входа x нет фактического типа, переменным v и s будет присвоено значение NULL. На самом деле, если бы в этой модели не нужна была строка типа, можно было бы не использовать сервисную функцию rdsGetRuntimeTypeData, а получить указатель на область данных входа v при помощи оператора v=*px. Однако, в данном случае удобнее получить оба указателя в одном вызове.

Все динамически сформированные строки, возвращаемые сервисными функциями RDS, совместимы с функцией rdsFree, поэтому указатели на эти строки могут непосредственно присваиваться переменным блока. В данном случае указатель на строку типа, который функция rdsGetRuntimeTypeData записала в переменную s, присваивается выходу type.

Далее необходимо проверить фактический тип входа x, и, в зависимости от него, вычислить значение выхода val. Если значение v, которое вернула функция rdsGetRuntimeTypeData, не равно NULL, значит, тип у входа есть. В этом случае выполнятся оператор switch(*s), анализирующий первый символ строки фактического типа входа.

Если x имеет тип double, строка s будет состоять из единственного символа «D». В этом случае v указывает на восьмибайтовую область, в которой хранится вещественное значение входа. Указатель v приводится к типу double*, и значение входа присваивается выходу блока val. Аналогично, если x имеет тип int, s будет содержать единственный символ «I», и, после приведения типа указателя v к int*, четырехбайтовое целое значение входа будет присвоено выходу блока. Если же x будет матрицей, строка s будет состоять из символа «M» и типа элемента матрицы (например, для матрицы double – «MD», для матрицы строк – «MA», для матрицы упоминавшихся ранее структур «TestComplex» – «M{DD}»). В данной модели выходу val в этом случае присваивается число элементов в матрице, равное произведению числа строк и числа столбцов, поэтому тип элементов матрицы анализировать не нужно – достаточно считать ее размеры. Для проверки матрицы на пустоту и получения ее размеров используются уже знакомые по прошлым примерам макросы RDS_ARRAYEXISTS, RDS_ARRAYROWS и RDS_ARRAYCOLS, только в этом случае в них подставляется не макроопределение для какой-нибудь статической переменной, а указатель на область данных входа (вспомогательная переменная v), возвращенный функцией rdsGetRuntimeTypeData. Область данных переменной произвольного типа устроена точно так же, как статическая переменная соответствующего фактического типа, поэтому эти макросы будут работать для нее так же, как и для обычной статической матрицы.

На рис. 32 изображена схема, собранная для проверки работы созданной модели блока. В ней к входу блока «x» подключены три связи: первая соединяет его с выходом библиотечного блока выбора варианта (его выход – целое число), вторая – с обычным полем ввода вещественного числа, третья – с блоком ввода матрицы. Две из трех связей заблокированы, чтобы работала только одна. К выходу «val» подключен числовой индикатор, а к выходу «type» – блок, отображающий строку.

Рис. 32. Пример блока, обрабатывающего данные входа произвольного типа

В зависимости от того, какая из трех входных связей блока не заблокирована, фактический тип его входа «x» будет либо целым, либо вещественным, либо матрицей вещественных чисел. На рисунке работает только связь, соединяющая наш блок с блоком ввода матрицы, поэтому на выход «type» выдается строка «MD» (матрица double), а на выход «val» – число элементов матрицы – в данном случае, шесть. Если заблокировать эту связь, и разблокировать соединение с блоком выбора варианта, на выходе «type» появится строка «I» (int), если же разблокировать соединение с полем ввода – строка «D» (double), а выход «val» в обоих случаях будет равен поступившему на вход «x» числу, независимо от его типа.

Приведенный пример демонстрирует возможность получения данных из переменной произвольного типа. Рассмотрим другой пример, в котором модель блока будет присваивать переменной произвольного типа данные разных типов, то есть программно задавать фактический тип выхода блока. Пусть у блока будет целый вход «Type» и выход «y» произвольного типа. При нулевом значении «Type» модель должна передать на выход целое число 1, при «Type», равном единице – вещественное число 2, а при любых других значениях «Type» – сформировать на выходе матрицу вещественных чисел 2×2 и заполнить ее значениями 1, 2, 3 и 4. Блок должен иметь следующую структуру переменных:

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

Модель блока будет такой:

      extern "C" __declspec(dllexport)
        int RDSCALL TestVar2(int CallMode,
                             RDS_PBLOCKDATA BlockData,
                             LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define Type (*((RDSINT32 *)(pStart+2*RDS_VSZ_S)))
      #define py ((void **)(pStart+2*RDS_VSZ_S+RDS_VSZ_I))
        // Вспомогательные переменные
        int *i_ptr;    // Указатель на данные для целого типа
        double *d_ptr; // Указатель на данные для вещественного типа
        void *v_ptr;   // Указатель на данные для матрицы

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

            // Выполнение такта моделирования
            case RDS_BFM_MODEL:
              switch(Type)
                { case 0: // Выдать целое число
                    // Установить целый тип выхода
                    i_ptr=(int*)rdsSetRuntimeType(py,"I");
                    // Присвоить выходу значение
                    if(i_ptr) *i_ptr=1;
                    break;
                  case 1: // Выдать вещественное число
                    // Установить вещественный тип выхода
                    d_ptr=(double*)rdsSetRuntimeType(py,"D");
                    // Присвоить выходу значение
                    if(d_ptr) *d_ptr=2.0;
                    break;
                  default: // Выдать матрицу
                    // Тип выхода – матрица double
                    v_ptr=rdsSetRuntimeType(py,"MD");
                    if(v_ptr)
                      { double *array;
                        // Установить размер матрицы
                        rdsResizeVarArray(v_ptr,2,2,FALSE,NULL);
                        // Получить указатель на первый элемент
                        array=(double*)RDS_ARRAYDATA(v_ptr);
                        // Заполнить матрицу числами 1,2,3,4
                        for(int i=0;i<4;i++)
                          array[i]=i+1;
                      }
                } // Конец switch(Type)
              break;

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

При вызове модели для выполнения такта расчета (параметр CallMode равен RDS_BFM_MODEL) анализируется значение входа Type. Если значение Type равно 0, на выход блока необходимо выдать целое число. Для этого вызывается функция rdsSetRuntimeType, присваивающая выходу произвольного типа y фактический тип int. В первом параметре функции передается указатель на переменную произвольного типа (py), во втором – строка типа, присваиваемая переменной (для типа int передается строка «I»). Функция возвращает указатель на созданную область данных заданного типа, в которую, после приведения указателя к типу int*, записывается число 1.

Если значение Type равно 1, на выход блока выдается вещественное число. Для этого в функцию rdsSetRuntimeType передается строка типа «D», соответствующая типу double. Указатель, возвращенный функцией, приводится к типу double* и используется для занесения в созданную область данных числа 2.0.

При любом другом значении Type для выхода блока устанавливается фактический тип «матрица double», для чего в функцию rdsSetRuntimeType передается строка «MD». Указатель, возвращенный этой функцией, передается в уже знакомую нам функцию rdsResizeVarArray (см. §2.5.3), которая устанавливает для созданной матрицы размер 2×2. Далее при помощи описывавшегося ранее макроса RDS_ARRAYDATA указатель на первый элемент матрицы присваивается вспомогательной переменной array. Первому элементу матрицы будет соответствовать значение array[0], второму – array[1] и т.д. В матрице 2×2 содержится четыре элемента, поэтому далее в цикле четырем элементам array присваиваются числа, на единицу большие их индексов, то есть 1, 2, 3 и 4.

Рис. 33. Пример блока, программно задающего
фактический тип своего выхода

Для проверки работы этой модели можно собрать схему, изображенную на рис. 33. Вход блока «Type» связан с выходом стандартного библиотечного блока выбора варианта, а выход «y» одновременно подан на входы числового индикатора и блока отображения матрицы. Выходы произвольного типа могут быть связаны с входами любого типа, поэтому такое соединение допустимо. Если в блоке выбора варианта будут выбраны пункты «int» или «double», на его выходе, а, значит, и на входе «Type» нашего блока, появится целое число 0 или 1 соответственно. При этом фактическим типом выхода «y» станет int или double, и число с этого выхода будет передано по верхней ветви связи на индикатор. Нижняя ветвь связи не сработает, поскольку она соединена с входом типа «матрица double», а целое или вещественное число не может быть передано на вход такого типа. Если же будет выбран пункт «матрица», вход «Type» получит значение 2, в результате чего наш блок сформирует на своем выходе «y» матрицу из четырех вещественных чисел, и она будет передана на вход блока отображения матриц по нижней ветви выходной связи. Верхняя ветвь связи не сработает, поскольку матрица вещественных чисел не может быть передана на вход числового индикатора.

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

Следует, однако, помнить, что в тех случаях, когда для работы блока необходимы входы или выходы, тип которых заранее не известен, но будет определяться не в процессе расчета, а до него (например, при настройке блока на работу с определенными данными), вместо использования переменных произвольного типа лучше в нужный момент программно изменить тип входа или выхода в структуре переменных блока с помощью сервисных функций RDS. При этом переменная сразу окажется нужного типа и будет передаваться по связям значительно быстрее. Модель блока тоже упростится: не нужно будет выяснять фактический тип переменной в каждом такте расчета, достаточно просто запомнить его при вызове модели в момент изменения структуры переменных (режим RDS_BFM_VARCHECK). При этом изменять структуру переменных нужно до того, как к блоку подключены связи: если, например, вход блока имел тип double, а модель сделала его матрицей, то связь, подключенная к этому блоку, не сможет работать и будет помечена как ошибочная.

Примеры программного изменения структуры переменных блока приведены в §2.16.1.