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

Описания RDS

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

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

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

§2.5.4. Работа со строками

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

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

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

#### §2.5.4. Работа со строками

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

Для строк, как и для матриц, в дереве переменных хранится только указатель на динамически отводимую область памяти (рис. 23). В тридцатидвухбитной версии RDS этот указатель занимает только первые 4 байта восьмибайтной области в дереве, остальные 4 байта не используются. В шестидесятичетырехбитной версии указатель занимает все восемь байтов.

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

Строки в RDS всегда имеют кодировку UTF-8. Для того, чтобы можно было использовать стандартные функции обработки строк, символы строки завершаются нулевым байтом. Таким образом, размер области памяти строки должен быть на единицу больше длины этой строки в байтах (длина строки в символах в общем случае не равна длине строки в байтах, поскольку в UTF-8 один символ может занимать до четырех байтов). Например, для размещения строки латинских символов «ABCD» необходимо отвести область памяти размером в 5 байтов, в которой последовательно будут записаны однобайтовые коды символов «A», «B», «C», «D» и 0. Для пустой строки память не отводится, при этом указатель на строку в дереве переменных блока принимает значение NULL.

Модель блока может самостоятельно отводить память для строк при помощи сервисной функций rdsAllocate и освобождать ее при помощи rdsFree. Не следует пользоваться для этого стандартными библиотечными функциями malloc и free или операторами C++ new и delete. Функции отведения и освобождения памяти рассчитаны на совместную работу: память, отведенная функцией malloc, должна быть освобождена функцией free; память, отведенная функцией rdsAllocate, должна быть освобождена функцией rdsFree. Если модель блока отведет память под строку при помощи malloc, при удалении блока или перед загрузкой другой схемы RDS попытается удалить эту память функцией rdsFree вместо free, что приведет к возникновению ошибки.

Для отведения памяти под строку можно также использовать любые сервисные функции RDS, формирующие динамические строки, поскольку все эти функции базируются на rdsAllocate. Например, для объединения двух строк можно вызвать функцию rdsDynStrCat и записать возвращенный ей указатель в дерево переменных блока.

Важное замечание. Сервисные функции RDS, работающие со строками, существуют в двух вариантах: для кодировки UTF-8 и для кодировки UTF-16. По умолчанию используются варианты для UTF-8, поэтому для работы со строкой в переменных блока можно использовать вызов rdsDynStrCat. Однако, если перед командой включения файла «RdsDef.h» определены константы RDS_NO_UTFALIASES или RDS_UTF16DEFAULT, имя rdsDynStrCat будет не определено (в первом случае) или по нему будет вызываться функция для кодировки UTF-16 (во втором) – в обоих случаях компилятор выдаст ошибку. Если по каким-либо причинам в модели используются указанные выше макроопределения, при вызове функция для работы со строками необходимо явно указывать в их именах суффикс «A»: например, вместо rdsDynStrCat записывать rdsDynStrCatA.

В качестве примера рассмотрим блок, формирующий выходную строку str, добавляя к строке prefix текстовое представление целого числа val (например, если подать на вход val число 10 и на вход prefix строку «v=», блок должен сформировать на выходе str строку «v=10»). Блок будет иметь следующую структуру переменных:

Смещение | Имя | Тип | Размер | Вход/выход | Пуск | Начальное значение
--- | --- | --- | --- | --- | --- | ---
0 | Start | Сигнал | 1 | Вход | ✓ | 1
1 | Ready | Сигнал | 1 | Выход |  | 0
2 | prefix | Строка | 8 | Вход | ✓ | v=
10 | val | int | 4 | Вход | ✓ | 0
14 | str | Строка | 8 | Выход |  |

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

      extern "C" __declspec(dllexport)
        int RDSCALL TestIntStr(int CallMode,
                               RDS_PBLOCKDATA BlockData,
                               LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart  ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define prefix (*((char **)(pStart+2*RDS_VSZ_S)))
      #define val (*((RDSINT32 *)(pStart+2*RDS_VSZ_S+RDS_VSZ_A)))
      #define str (*((char **)(pStart+2*RDS_VSZ_S+RDS_VSZ_A+RDS_VSZ_I)))

        char buf[40];  // Буфер, в котором формируется строка
        int l1,l2;

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

            // Выполнение такта моделирования
            case RDS_BFM_MODEL:
              // Преобразование числа val в строку buf
              itoa(val,buf,10);
              // Определение длин строк prefix и buf
              l1=prefix==NULL?0:strlen(prefix);
              l2=strlen(buf);
              // Освобождение прежнего значения str
              rdsFree(str);
              // Отведение памяти под новую строку
              str=(char*)rdsAllocate(l1+l2+1);
              // Занесение строки в отведенную память
              if(prefix!=NULL) // Строка prefix не пуста
                { strcpy(str,prefix); // Копировать prefix
                  strcat(str,buf);    // Дописать buf
                }
              else // Строка prefix пуста
                strcpy(str,buf);    // Копировать buf
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef str
      #undef val
      #undef prefix
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

Поскольку в этом примере используется стандартная библиотечная функция itoa, для успешной компиляции необходимо включить в исходный текст стандартный файл заголовка «stdlib.h» (файл «string.h», необходимый для стандартных функций strlen, strcpy и strcat, уже должен быть включен из-за присутствия в тексте модели функции strcmp, которая используется для сравнения строк при проверке типов переменных).

Вызов функции с параметром RDS_BFM_VARCHECK уже неоднократно описывался. В этом примере реализована точно такая же проверка типа переменных, как и в остальных. В данном случае строка, с которой сравнивается переданная в функцию строка типа, состоит из двух букв «S» для обязательных сигналов Start и Ready, буквы «A» для строки prefix, буквы «I» для целой переменной val и еще одной «A» для строки str.

При вызове модели с параметром RDS_BFM_MODEL в локальном массиве buf стандартной функцией itoa формируется текстовое представление значения переменной val (параметр 10 указывает на использование десятичной системы счисления). Размер массива buf выбран равным 40 поскольку тридцати девяти символов заведомо хватит для текстового представления тридцатидвухбитного целого числа типа int. Далее определяются длины строк prefix и buf и заносятся во вспомогательные переменные l1 и l2 соответственно. Макрос prefix представляет собой ссылку на одноименный вход блока, то есть указатель на данные входной строки. Для пустой строки этот указатель будет равен NULL, поэтому длина l2 вычисляется при помощи условного оператора: если prefix равен NULL, l2 присваивается значение 0 и вызов функции strlen не производится.

Далее необходимо объединить строки prefix и buf и присвоить получившуюся строку выходу блока str. Сначала вызовом rdsFree(str) освобождается память, занятая прежним значением строки (функцию rdsFree можно безопасно вызывать для нулевых указателей, поэтому если прежнее значение выхода было пустой строкой, и str равнялась NULL, ошибок не будет). Затем при помощи функции rdsAllocate отводится память для новой строки. В функцию передается размер отводимой области, который должен быть на единицу большим суммарной длины обеих объединяемых строк – один лишний байт требуется для нуля, завершающего строку. Функция возвращает указатель на отведенную область, который, после приведения к типу char*, записывается в переменную str. После того, как память отведена, в ней формируется объединение строк. Если строка prefix не пустая, она копируется в str при помощи функции strcpy, после чего функцией strcat к ней в конец дописывается строка buf. Если же строка prefix пуста, в str копируется только строка buf.

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

          case RDS_BFM_MODEL:
            // Преобразование числа в строку
            itoa(val,buf,10);
            // Освобождение прежнего значения str
            rdsFree(str);
            // Формирование выходной строки
            str=rdsDynStrCat(prefix,buf,TRUE);
            break;

Сначала, как и в предыдущем варианте модели, целое число val преобразуется в строку во вспомогательном массиве buf и освобождается старое значение str. Все остальные действия по отведению памяти, проверке строки prefix на пустоту и объединению строк выполняет функция rdsDynStrCat. Ей передаются указатели на объединяемые строки (любой из них может равняться NULL) и значение TRUE, указывающее на то, что если в результате объединения строк получается пустая строка, функция должна вернуть значение NULL, не отводя память. Возвращаемый функцией указатель записывается в переменную str. Вспомогательные переменные l1 и l2 в этом варианте модели не используются.

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