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

Описания RDS

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

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

§2.16. Программное изменение схемы

§2.16.1. Изменение структуры переменных блока

Полный исходный текст на языке C++ для библиотеки (DLL) с моделями блоков, изменяющих собственную структуру переменных. Библиотека содержит три модели:

- TestSub – блок, вычисляющей разность входов x1 и x2, и выдающей ее на выход y, рассмотренный ранее в §2.5.1, в который добавлена автоматическая установка структуры переменных согласно требованиям модели;
- NetSendRcvStruct – блок, передающий и принимающий по сети всю свою структуру переменных, которую может произвольно задавать плоьзователь (модель создана на основе блоков и класса личной области данных, рассмотренных в §2.15.2);
- Server – блок, включающий серверные функции в RDS (копия модели из §2.15.2, добавлена сюда для того, чтобы все модели из рассматриваемого примера схемы находились в одной библиотеке).

Изменения относительно предыдущих версий моделей выделены цветом.

      // Программное изменение структуры переменных
      #include <windows.h>
      #include <RdsDef.h>
      // Подготовка описаний сервисных функций
      #define RDS_SERV_FUNC_BODY GetInterfaceFunctions
      #include <RdsFunc.h>

      // Глобальная переменная для значения ошибки
      double DoubleErrorValue;

      //========== Главная функция DLL ==========
      int WINAPI DllMain(HINSTANCE /*hinst*/,
                               unsigned long reason,
                               void* /*lpReserved*/)
      { if(reason==DLL_PROCESS_ATTACH) // Загрузка DLL
          { // Получение доступа к функциям RDS
            if(!GetInterfaceFunctions())
              RDS_SERV_ERROR_MSGW // Сообщение: старая версия RDS
            else
              rdsGetHugeDouble(&DoubleErrorValue);
          }
        return 1;
      }
      //========= Конец главной функции =========

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

      // Создать структуру переменных из двух входов x1,x2 и выхода y
      BOOL CreateVarStruct_x1x2y(RDS_BHANDLE Block)
      { // Строка описания структуры переменных
        static char str[]=
          "struct\nbegin\n"
          "signal name \"Start\" in run default 0\n"
          "signal name \"Ready\" out default 0\n"
          "double name \"x1\" in menu run default 0\n"
          "double name \"x2\" in menu run default 0\n"
          "double name \"y\" out menu default 0\n"
          "end";
        RDS_HOBJECT dv; // Вспомогательный объект
        BOOL ok;
        // Создаем объект для работы со структурой переменных
        dv=rdsVSCreateEditor();
        // Создаем в объекте набор переменных по строке описания
        ok=rdsVSCreateByDescr(dv,str);
        if(ok) // Переписываем структуру переменных из объекта в блок
          ok=rdsVSApplyToBlock(dv,Block,NULL);
        // Удаляем вспомогательный объект
        rdsDeleteObject(dv);
        // Возвращаем успешность операции
        return ok;
      }
      //=========================================
      // Модель блока вычитания
      extern "C" __declspec(dllexport)
        int RDSCALL TestSub(int CallMode,
                            RDS_PBLOCKDATA BlockData,
                            LPVOID ExtParam)
      {
      // Макроопределения для статических переменных
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
      #define x1 (*((double *)(pStart+2*RDS_VSZ_S)))
      #define x2 (*((double *)(pStart+2*RDS_VSZ_S+RDS_VSZ_D)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S+2*RDS_VSZ_D)))
      BOOL ok;
        switch(CallMode)
          {
            // Инициализация
            case RDS_BFM_INIT:
              BlockData->Tag=0; // Сброс флага
              break;
            // Проверка типа переменных
            case RDS_BFM_VARCHECK:
              if(strcmp((char*)ExtParam,"{SSDDD}")==0)
                return RDS_BFR_DONE; // Тип переменных правильный
              // Тип переменных неправильный
              if(BlockData->Tag) // Флаг взведен – программное изменение
                return RDS_BFR_ERROR; // Ошибка
              // Модель вызвана не из-за программного изменения
              // структуры переменных
              // Взводим флаг на время программного изменения структуры
              BlockData->Tag=1;
              // Пытаемся изменить структуру переменных блока
              ok=CreateVarStruct_x1x2y(BlockData->Block);
              // Сбрасываем флаг обратно
              BlockData->Tag=0;
              // Возвращаем результат попытки изменения
              return ok?RDS_BFR_DONE:RDS_BFR_BADVARSMSG;

            // Выполнение такта расчета
            case RDS_BFM_MODEL:
              // Вычисление значения выхода
              if(x1==DoubleErrorValue || x2==DoubleErrorValue)
                y=DoubleErrorValue;
              else
                y=x1-x2;
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef x2
      #undef x1
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      //================================================================================
      // Модель блока, включающего сервер (нужна только для обеспечения работы примера)
      //================================================================================
      extern "C" __declspec(dllexport)
        int RDSCALL Server(int CallMode,
              RDS_PBLOCKDATA BlockData,
              LPVOID ExtParam)
      { int *pConnId;

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Отводим место под личную область данных размером в int
              BlockData->BlockData=pConnId=new int;
              // Запускаем сервер и соединяемся с ним
              *pConnId=rdsNetServer(-1, // Порт по умолчанию
                                    "ProgrammersGuide.Server", // Канал
                                    FALSE); // Не принимаем данные
              break;

            // Очистка
            case RDS_BFM_CLEANUP:
              // Личная область данных – целое число
              pConnId=(int*)(BlockData->BlockData);
              // Разрываем связь с сервером (он завершится сам)
              rdsNetCloseConnection(*pConnId);
              // Удаляем личную область
              delete pConnId;
              break;
          }
        return RDS_BFR_DONE;
      }
      //=========================================

      //================================================================================
      // Модель блока, передающего по сети все свои переменные, дающего пользователю
      // менять структуру переменных из функции настройки
      //================================================================================

      // Личная область данных блоков приема и передачи по сети
      class TNetSendRcvData
      { public:
          int Mode; // Режим данного блока: прием или передача
            #define NETSRMODE_SENDER    0 // Передатчик
            #define NETSRMODE_RECEIVER  1 // Приемник
          char *ChannelName; // Имя канала
          BOOL LimitSpeed;   // Задан минимальный интервал передачи
          DWORD Delay;       // Минимальный интервал в мс
          BOOL StructInOut;  // Этот блок передает или принимает структуру
          int ConnId; // Идентификатор соединения

          // Переменные состояния блока-передатчика
          BOOL Connected;   // Соединение установлено
          BOOL DataWaiting; // Передача данных отложена
          RDS_TIMERID Timer;// Таймер для отсчета интервала
          BOOL WaitingForTimer;// Таймер запущен - ждем
          DWORD LastSendTime; // Время последней отправки

          // Функции класса
          void Connect(void);    // Установить соединение
          void Disconnect(void); // Разорвать соединение

          void SendValue(double value); // Передать число в канал
          BOOL ReceiveValue(RDS_NETRECEIVEDDATA *rcv, // Реакция на
                            double *pOut);            // пришедшие данные
          void SendArray(void *input); // Передать массив в канал
          BOOL ReceiveArray(RDS_NETRECEIVEDDATA *rcv, // Реакция на
                            void *output);            // пришедшие данные
          void SendStruct(RDS_BHANDLE Block); // Передать структуру
          BOOL ReceiveStruct(RDS_NETRECEIVEDDATA *rcv, // Реакция на
                            RDS_BHANDLE Block); // пришедшую структуру
          void CreateTimer(void);  // Создать таймер
          void DeleteTimer(void);  // Удалить таймер
          BOOL CheckSendTimer(void); // Проверить, можно ли передавать,
                                     // и запустить таймер, если нельзя

          int Setup(char *title);   // Функция настройки блока
          void SaveText(void);      // Сохранить параметры
          void LoadText(char *text);// Загрузить параметры

          // Конструктор класса
          TNetSendRcvData(int mode)
            { ConnId=-1; // Нет соединения
              Connected=DataWaiting=FALSE;
              LimitSpeed=WaitingForTimer=FALSE;Timer=NULL;Delay=100;
              ChannelName=NULL; StructInOut=FALSE;
              Mode=mode; // Режим передается в параметре конструктора
            };
          // Деструктор класса
          ~TNetSendRcvData()
            { Disconnect(); // Разорвать соединение
              DeleteTimer(); // Удалить таймер
              rdsFree(ChannelName); // Освободить строку имени канала
            };
      };
      //=========================================

      // Установка соединения
      void TNetSendRcvData::Connect(void)
      { char *PrefixedName; // Полное имя канала

        // Если имя канала пустое, соединение невозможно
        if(ChannelName==NULL || (*ChannelName)==0)
          return;

        // Добавляем префикс к имени канала
        PrefixedName=rdsDynStrCat("ProgrammersGuide.",ChannelName,FALSE);
        // Устанавливаем соединение с сервером
        ConnId=rdsNetConnect(NULL, // Сервер по умолчанию
                             -1,   // Порт по умолчанию
                             PrefixedName,// Имя канала с префиксом
                             Mode==NETSRMODE_RECEIVER); // Прием данных
        // Освобождаем динамически отведенную строку
        rdsFree(PrefixedName);
        // Создаем или уничтожаем таймер
        CreateTimer();
      }
      //=========================================

      // Разорвать соединение
      void TNetSendRcvData::Disconnect(void)
      { if(ConnId!=-1) // Соединение было создано
          rdsNetCloseConnection(ConnId); // Разорвать
        // Сбрасываем переменные состояния
        ConnId=-1; // Соединения больше нет
        Connected=FALSE; // Связи тоже больше нет
      }
      //=========================================

      // Создать таймер
      void TNetSendRcvData::CreateTimer(void)
      {
        if(Mode!=NETSRMODE_SENDER || // Приемнику таймер не нужен
           (!LimitSpeed) ) // Интервал передачи не ограничивается
          { DeleteTimer(); // Удаляем таймер, если он создан
            return;
          }
        if(Timer) // Таймер уже создан
          return;
        // Создаем таймер
        Timer=rdsSetBlockTimer(
                NULL, // Создается новый таймер
                0,    // Задержка задается при запуске
                RDS_TIMERM_STOP | RDS_TIMERS_TIMER, // Однократный
                FALSE); // Создается остановленным
      }
      //=========================================

      // Удалить таймер
      void TNetSendRcvData::DeleteTimer(void)
      {
        if(Timer) // Таймер есть
          { rdsDeleteBlockTimer(Timer);
            Timer=NULL;
          }
        WaitingForTimer=FALSE; // Сбрасываем флаг ожидания
      }
      //=========================================

      // Проверить, можно ли передать данные немедленно, и запустить
      // таймер, если нельзя
      BOOL TNetSendRcvData::CheckSendTimer(void)
      { DWORD interval;

        if(!Connected) // Нет связи с сервером
          return TRUE; // Разрешаем отправку, чтобы попытка отправки
                       // была зафиксирована в функции SendValue

        if(!LimitSpeed) // Интервал не ограничивается
          return TRUE;

        if(WaitingForTimer) // Уже запустили таймер и ждем срабатывания
          return FALSE;

        // Интервал передачи ограничен, таймер сейчас выключен

        interval=GetTickCount()-LastSendTime; // Время с прошлой отправки
        if(interval>=Delay) // Прошло много времени – можно передавать
          return TRUE;
        // С прошлой отправки прошло менее Delay мс
        // Нужно подождать (Delay-interval)
        WaitingForTimer=TRUE; // Взводим флаг ожидания таймера
        rdsRestartBlockTimer(Timer,Delay-interval); // Запускаем таймер
        return FALSE; // Передавать сейчас нельзя – ждем таймера
      }
      //=========================================

      // Передать данные
      void TNetSendRcvData::SendValue(double value)
      {
        if(!Connected) // Нет связи с сервером
          { // Взводим флаг наличия данных, ожидающих передачи
            DataWaiting=TRUE;
            return;
          }

        // Связь есть – передаем всем блокам канала
        rdsNetBroadcastData(ConnId, // Соединение
                            RDS_NETSEND_UPDATE|RDS_NETSEND_UDP, // Флаги
                            0,NULL, // Не передаем целое число и строку
                            &value, // Указатель на данные
                            sizeof(value)); // Размер данных
        // Сбрасываем флаг ожидания – мы только что передали данные
        DataWaiting=FALSE;
        // Запоминаем время последней передачи
        LastSendTime=GetTickCount();

      }
      //=========================================

      // Прием данных
      BOOL TNetSendRcvData::ReceiveValue(
            RDS_NETRECEIVEDDATA *rcv, // Указатель на структуру с данными
            double *pOut)             // Указатель на выход блока
      {
        if(rcv==NULL || pOut==NULL) // Нет одного из указателей
          return FALSE;

        // Проверяем, есть ли среди принятых данных двоичные,
        // и равен ли размер этих данных размеру double
        if(rcv->Buffer==NULL || rcv->BufferSize!=sizeof(double))
          return FALSE; // Нет данных или не совпал размер

        // Копируем принятое числов pOut
        memcpy(pOut,rcv->Buffer,sizeof(double));
        return TRUE; // Приняты правильные данные
      }
      //=========================================

      // Передать массив
      void TNetSendRcvData::SendArray(void *input)
      { int N;

        if(!Connected) // Нет связи с сервером
          { // Взводим флаг наличия данных, ожидающих передачи
            DataWaiting=TRUE;
            return;
          }

        // Связь есть – определяем размер массива input
        N=RDS_ARRAYEXISTS(input)?
            (RDS_ARRAYROWS(input)*RDS_ARRAYCOLS(input)):0;
        if(N==0) // Массив пуст – передавать нечего
          return;

        // Передаем N чисел double всем блокам канала
        rdsNetBroadcastData(
            ConnId,               // Соединение
            RDS_NETSEND_UPDATE|RDS_NETSEND_UDP, // Флаги
            N,                    // Целое число – размер массива
            NULL,                 // Строка не передается
            RDS_ARRAYDATA(input), // Начало данных массива
            N*sizeof(double));    // Размер массива в байтах
        // Сбрасываем флаг ожидания – мы только что передали данные
        DataWaiting=FALSE;
        // Запоминаем время передачи
        LastSendTime=GetTickCount();
      }
      //=========================================

      // Прием массива
      BOOL TNetSendRcvData::ReceiveArray(
          RDS_NETRECEIVEDDATA *rcv, // Указатель на структуру с данными
          void *output) // Указатель на выход блока (массив)
      { int N;
        if(rcv==NULL||output==NULL) // Нет одного из указателей
          return FALSE;

        // Проверяем, есть ли среди принятых данных двоичные,
        // и кратен ли размер блока данных размеру double
        if(rcv->Buffer==NULL || // Нет буфера с данными
           rcv->BufferSize%sizeof(double)!=0) // Размер не кратен 8
          return FALSE;

        // Вычисляем число элементов в принятом массиве
        N=rcv->BufferSize/sizeof(double);
        // Вычисленное число элементов должно совпасть с переданным
        if(N!=rcv->Id)
          return FALSE;
        // Принято N чисел double – отводим массив под них
        if(!rdsResizeVarArray(output,1,N,FALSE,NULL))
          return FALSE;
        // Копируем принятые данные в отведенный массив выхода
        memcpy(RDS_ARRAYDATA(output),rcv->Buffer,rcv->BufferSize);
        return TRUE;
      }
      //=========================================

      // Передать структуру переменных блока
      void TNetSendRcvData::SendStruct(RDS_BHANDLE Block)
      { void *buf;
        int len;

        // Является ли данный блок передатчиком?
        if(Mode!=NETSRMODE_SENDER)
          return; // Не является – это ошибка

        if(!Connected) // Нет связи с сервером
          { // Взводим флаг наличия данных, ожидающих передачи
            DataWaiting=TRUE;
            return;
          }
        // Связь с сервером есть

        // Формируем буфер со всеми переменными блока
        buf=rdsBlockVarToMem(Block,-1,TRUE,&len);
        if(buf==NULL) return; // Не удалось сформировать буфер

        // Передаем по сети сформированный буфер
        rdsNetBroadcastData(ConnId,
                            RDS_NETSEND_UPDATE|RDS_NETSEND_UDP,
                            0,NULL,
                            buf,len);
        // Освобождаем буфер – он уже передан
        rdsFree(buf);
        // Сбрасываем флаг ожидания – мы только что передали данные
        DataWaiting=FALSE;
        // Запоминаем время последней передачи
        LastSendTime=GetTickCount();
      }
      //=========================================
      // Принять структуру переменных
      BOOL TNetSendRcvData::ReceiveStruct(RDS_NETRECEIVEDDATA *rcv,
                  RDS_BHANDLE Block)
      {
        // Проверяем первый параметр и является ли блок приемником
        if(rcv==NULL || Mode!=NETSRMODE_RECEIVER)
          return FALSE;
        // Есть ли среди принятых данных двоичные?
        if(rcv->Buffer==NULL)
          return FALSE;
        // Пытаемся записать принятые данные в структуру переменных блока
        return rdsBlockVarFromMem(Block,-1,rcv->Buffer,rcv->BufferSize);
      }
      //=========================================

      // Сохранение параметров блока
      void TNetSendRcvData::SaveText(void)
      { RDS_HOBJECT ini; // Вспомогательный объект
        // Создаем вспомогательный объект
        ini=rdsINICreateTextHolder(TRUE);
        // Создаем в объекте секцию "[General]"
        rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"General");
        // Записываем в эту секцию имя канала
        rdsINIWriteString(ini,"Channel",ChannelName);

        // Записываем режим для приемопередатчика структур
        if(StructInOut)
          rdsINIWriteInt(ini,"Mode",Mode);
        if(Mode==NETSRMODE_SENDER) // Передатчик
          { // Создаем новую секцию
            rdsSetObjectStr(ini,RDS_HINI_CREATESECTION,0,"Timer");
            // Записываем параметры
            rdsINIWriteInt(ini,"On",LimitSpeed);
            rdsINIWriteInt(ini,"Delay",Delay);
          }
        // Сохраняем текст, сформированный объектом, как параметры блока
        rdsCommandObject(ini,RDS_HINI_SAVEBLOCKTEXT);
        // Удаляем вспомогательный объект
        rdsDeleteObject(ini);
      }
      //=========================================

      // Загрузка параметров блока
      void TNetSendRcvData::LoadText(char *text)
      { RDS_HOBJECT ini; // Вспомогательный объект
        char *str;
        // Создаем вспомогательный объект
        ini=rdsINICreateTextHolder(TRUE);
        // Записываем в объект полученный текст с параметрами блока
        rdsSetObjectStr(ini,RDS_HINI_SETTEXT,0,text);
        // Начинаем чтение секции "[General]", если она есть
        if(rdsINIOpenSection(ini,"General")) // Секция есть
          { // Освобождаем старое имя канала
            rdsFree(ChannelName);
            ChannelName=NULL;
            // Получаем у объекта указатель на строку с именем
            str=rdsINIReadString(ini,"Channel","",NULL);
            // Если такая строка есть в тексте, копируем ее в ChannelName
            if(str)
              ChannelName=rdsDynStrCopy(str);

            // Считываем режим для приемопередатчика структур
            if(StructInOut)
              Mode=rdsINIReadInt(ini,"Mode",Mode);
          }
        if(Mode==NETSRMODE_SENDER && // Передатчик
           rdsINIOpenSection(ini,"Timer")) // Есть секция "[Timer]"
          { LimitSpeed=rdsINIReadInt(ini,"On",LimitSpeed)!=0;
            Delay=rdsINIReadInt(ini,"Delay",Delay);
          }
        // Удаляем вспомогательный объект
        rdsDeleteObject(ini);
        // Поскольку имя канала и режим работы могли измениться,
        // соединяемся с сервером заново
        Disconnect(); // Разрываем старое соединение
        Connect(); // Создаем новое
      }
      //=========================================

      // Функция обратного вызова окна настройки блока (прототип)
      void RDSCALL TNetSendRcvData_Setup_Check(
        RDS_HOBJECT window, // Объект-окно
        RDS_PFORMSERVFUNCDATA data); // Описание события
      //=========================================
      // Функция настройки блока
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      int TNetSendRcvData::Setup(char *title)
      { RDS_HOBJECT win; // Вспомогательный объект-окно
        BOOL ok;         // Пользователь нажал "OK"
        char *str;
        // Создаем окно
        win=rdsFORMCreate(FALSE,-1,-1,title);

        if(StructInOut) // Блок работает со структурами
          { // Выпадающий список "прием/передача"
            rdsFORMAddEdit(win,0,10,RDS_FORMCTRL_COMBOLIST,
                "Действие:",150);
            rdsSetObjectStr(win,10,RDS_FORMVAL_LIST,
                "Передача данных\nПрием данных");
            rdsSetObjectInt(win,10,RDS_FORMVAL_VALUE,
                (Mode==NETSRMODE_RECEIVER)?1:0);

            // Получение строки описания переменных блока
            str=rdsCreateVarDescriptionString(
                rdsGetBlockVar(NULL,-1,NULL),TRUE,0,NULL);
            // Установка этой строки как невидимого поля ввода 1000
            rdsFORMAddEdit(win,0,1000,RDS_FORMCTRL_NONVISUAL,NULL,0);
            rdsSetObjectStr(win,1000,RDS_FORMVAL_VALUE,str);
            rdsFree(str); // Строка больше не нужна

            // Кнопка редактирования переменных
            rdsFORMAddEdit(win,0,11,
                RDS_FORMCTRL_BUTTON | RDS_FORMFLAG_LINE,
                "Переменные:",150);
            rdsSetObjectStr(win,11,RDS_FORMVAL_VALUE,"Изменить...");
          }
        // Поле ввода имени канала
        rdsFORMAddEdit(win,0,1,RDS_FORMCTRL_EDIT,
                       "Имя канала:",200);
        rdsSetObjectStr(win,1,RDS_FORMVAL_VALUE,ChannelName);

        if(Mode==NETSRMODE_SENDER || StructInOut)
          { // Для передатчика – ввод интервала
            rdsFORMAddEdit(win,0,2,
                RDS_FORMCTRL_EDIT | RDS_FORMFLAG_CHECK,
                "Интервал передачи, мс:",80);
            rdsSetObjectInt(win,2,RDS_FORMVAL_VALUE,Delay);
            rdsSetObjectInt(win,2,RDS_FORMVAL_CHECK,LimitSpeed);
          }

        // Открытие окна
        ok=rdsFORMShowModalServ(win,TNetSendRcvData_Setup_Check);
        if(ok)
          { // Пользователь нажал OK – запись параметров в блок
            char *NewName=rdsGetObjectStr(win,1,RDS_FORMVAL_VALUE);
            if(ChannelName==NULL || strcmp(NewName,ChannelName)!=0)
              { // Имя канала изменилось – запоминаем новое
                rdsFree(ChannelName);
                ChannelName=rdsDynStrCopy(NewName);
              }
            // Флаг ограничения интервала и сам интервал
            LimitSpeed=rdsGetObjectInt(win,2,RDS_FORMVAL_CHECK)!=0;
            Delay=rdsGetObjectInt(win,2,RDS_FORMVAL_VALUE);

            if(StructInOut) // Блок работает со структурами
              { RDS_HOBJECT dv; // Объект для работы с переменными
                DWORD flags,mask;
                RDS_VARDESCRIPTION vdescr;
                // Прием или передача
                Mode=rdsGetObjectInt(win,10,RDS_FORMVAL_VALUE)?
                    NETSRMODE_RECEIVER:NETSRMODE_SENDER;
                // Чтение строки описания переменных из невидимого поля
                str=rdsGetObjectStr(win,1000,RDS_FORMVAL_VALUE);
                // Создаем объект для работы с переменными
                dv=rdsVSCreateEditor();
                // Создаем структуру переменных по строке описания
                if(rdsVSCreateByDescr(dv,str))
                  { // Установка флагов переменных – входы или выходы
                    if(Mode==NETSRMODE_SENDER) // Передатчик (входы)
                      flags=RDS_VARFLAG_INPUT|RDS_VARFLAG_RUN|
                            RDS_VARFLAG_MENU|RDS_VARFLAG_SHOWNAME;
                    else // Приемник (выходы)
                      flags=RDS_VARFLAG_OUTPUT|RDS_VARFLAG_MENU|
                            RDS_VARFLAG_SHOWNAME;
                    // Маска изменяемых флагов
                    mask=RDS_VARFLAG_INPUT|RDS_VARFLAG_OUTPUT|
                         RDS_VARFLAG_RUN|RDS_VARFLAG_MENU|
                         RDS_VARFLAG_SHOWNAME;
                    // Получение числа переменных в объекте dv
                    vdescr.servSize=sizeof(vdescr);
                    rdsVSGetVarDescription(dv,-1,&vdescr);
                    // Установка флагов начиная со второй переменной
                    for(int i=2;i<vdescr.StructFields;i++)
                      rdsVSSetVarFlags(dv,i,flags,mask);
                    // Запись переменных из объекта в блок
                    if(!rdsVSApplyToBlock(dv,NULL,NULL))
                      rdsMessageBox("Невозможно установить переменные "
                          "блока","Ошибка",MB_OK | MB_ICONWARNING);
                  }
                // Удаление вспомогательного объекта
                rdsDeleteObject(dv);
              }
            Disconnect();
            Connect();
          }
        // Уничтожение окна
        rdsDeleteObject(win);
        // Возвращаемое значение
        return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE;
      }
      //=========================================

      // Функция обратного вызова окна настройки
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      void RDSCALL TNetSendRcvData_Setup_Check(RDS_HOBJECT window,
                RDS_PFORMSERVFUNCDATA data)
      { RDS_HOBJECT dv=NULL;
        char *descr;
        RDS_VARDESCRIPTION vdescr;
        BOOL sender;

        // Реагируем на событие, из-за которого вызвана функция
        switch(data->Event)
          { case RDS_FORMSERVEVENT_CLICK: // Нажатие кнопки
              if(data->CtrlId!=11) // Это не кнопка "Изменить"
                break;
              // Создаем объект для манипуляций с переменными
              dv=rdsVSCreateEditor();
              // Считываем текст описания переменных из поля 1000
              descr=rdsGetObjectStr(window,1000,RDS_FORMVAL_VALUE);
              // Заполняем объект dv описанием переменных descr
              if(!rdsVSCreateByDescr(dv,descr))
                break;
              // Удаляем из объекта две первых переменных
              rdsVSDeleteVar(dv,0); // Start
              rdsVSDeleteVar(dv,0); // Ready
              // Открываем окно редактора переменных
              if(!rdsVSExecuteEditor(dv,FALSE,RDS_HVAR_FALLNS|
                      RDS_HVAR_FNOSTRUCTNAME|RDS_HVAR_FNOOFFSET,-1,
                      "Переменные"))
                break; // Пользователь нажал кнопку "Отмена"
              // Добавляем сигналы Start и Ready
              // Ready
              rdsVSAddVar(dv,0,"Ready",RDS_VARTYPE_SIGNAL,NULL,
                  RDS_VARFLAG_OUTPUT|RDS_VARFLAG_SHOWNAME|
                  RDS_VARFLAG_EXT_CHGNAME,0,"0");
              // Start
              rdsVSAddVar(dv,0,"Start",RDS_VARTYPE_SIGNAL,NULL,
                  RDS_VARFLAG_INPUT|RDS_VARFLAG_RUN|
                  RDS_VARFLAG_SHOWNAME|RDS_VARFLAG_EXT_CHGNAME,0,"0");
              // Формируем текст описания получившихся переменных
              vdescr.servSize=sizeof(vdescr);
              if(!rdsVSGetVarDescription(dv,-1,&vdescr))
                break;
              descr=rdsCreateVarDescriptionString(vdescr.Var,TRUE,0,NULL);
              if(descr)
                { // Записываем новый текст в невидимое поле
                  rdsSetObjectStr(win,1000,RDS_FORMVAL_VALUE,descr);
                  rdsFree(descr);
                }
              break;

            case RDS_FORMSERVEVENT_CHANGE: // Изменилось поле
              // Считываем режим работы блока из выпадающего списка
              sender=rdsGetObjectInt(window,10,RDS_FORMVAL_VALUE)==0;
              // sender истинно, если блок - передатчик
              // Ограничение интервала разрешено только для передатчика
              rdsSetObjectInt(window,2,RDS_FORMVAL_ENABLED,sender);
              break;
          }

        // Если в процессе работы функции был создан объект, удаляем его
        if(dv)
          rdsDeleteObject(dv);
      }
      //=========================================
      // Модель блока приема/передачи структуры
      extern "C" __declspec(dllexport)
        int RDSCALL NetSendRcvStruct(int CallMode,
            RDS_PBLOCKDATA BlockData,
            LPVOID ExtParam)
      { TNetSendRcvData *data=(TNetSendRcvData*)(BlockData->BlockData);
      // Макроопределения для первых двух переменных блока
      #define pStart ((char *)(BlockData->VarTreeData))
      #define Start (*((char *)(pStart)))
      #define Ready (*((char *)(pStart+RDS_VSZ_S)))
        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              BlockData->BlockData=data=
                  new TNetSendRcvData(NETSRMODE_SENDER);
              // Взводим флаг работы со структурами
              data->StructInOut=TRUE;
              break;

            // Очистка
            case RDS_BFM_CLEANUP:
              delete data;
              break;

            // Установлено соединение с сервером
            case RDS_BFM_NETCONNECT:
              data->Connected=TRUE;
              // Если были данные, ожидающие передачи – передаем их
              if(data->DataWaiting)
                data->SendStruct(BlockData->Block);
              break;

            // Соединение разорвано
            case RDS_BFM_NETDISCONNECT:
              data->Connected=FALSE;
              break;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              // При первом запуске передаем данные
              if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart)
                data->SendStruct(BlockData->Block);
              break;

            // Срабатывание таймера
            case RDS_BFM_TIMER:
              data->WaitingForTimer=FALSE;
              // Передаем данные
              data->SendStruct(BlockData->Block);
              break;

            // Такт расчета
            case RDS_BFM_MODEL:
              if(data->CheckSendTimer())
                data->SendStruct(BlockData->Block);
              break;

            // Вызов функции настройки
            // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
            //   в противном случае необходимо использовать версии функций
            //   с суффиксом "W" и символьные константы с префиксом "L"
            case RDS_BFM_SETUP:
              return data->Setup("Прием/передача структуры");

           // Сохранение параметров
           case RDS_BFM_SAVETXT:
              data->SaveText();
              break;

           // Загрузка параметров
           case RDS_BFM_LOADTXT:
              data->LoadText((char*)ExtParam);
              break;

            // По сети получены данные
            case RDS_BFM_NETDATARECEIVED:
              // Если данные совместимы с блоком – принимаем и
              // взводим сигнал готовности
              Ready=data->ReceiveStruct((RDS_NETRECEIVEDDATA*)ExtParam,
                        BlockData->Block)?1:0;
              // Сбрасываем сигнал запуска
              Start=0;
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================