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

Описания RDS

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

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

§2.15. Обмен данными по сети

§2.15.3. Способы снижения нагрузки на сеть

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

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

Уже описанные ранее модели Server и NetReceive включены сюда для того, чтобы все модели из рассматриваемого примера схемы находились в одной библиотеке.

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

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

      //========== Главная функция 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
          }
        return 1;
      }
      //========= Конец главной функции =========

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

        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(Mode==NETSRMODE_SENDER && // Передатчик
           rdsINIOpenSection(ini,"Timer")) // Есть секция "[Timer]"
          { LimitSpeed=rdsINIReadInt(ini,"On",LimitSpeed)!=0;
            Delay=rdsINIReadInt(ini,"Delay",Delay);
          }
        // Удаляем вспомогательный объект
        rdsDeleteObject(ini);

        // Поскольку имя канала могло измениться, соединяемся с
        // сервером заново
        Disconnect(); // Разрываем старое соединение
        Connect(); // Создаем новое
      }
      //=========================================

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

        if(Mode==NETSRMODE_SENDER)
          { // Для передатчика – ввод интервала
            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=rdsFORMShowModalEx(win,NULL);
        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);
            // Устанавливаем соединение с сервером заново и создаем
            // или удаляем таймер, если необходимо
            Disconnect();
            Connect();
          }
        // Уничтожаем вспомогательный объект-окно
        rdsDeleteObject(win);
        // Возвращаемое значение
        return ok?RDS_BFR_MODIFIED:RDS_BFR_DONE;
      }
      //=========================================
      //=========================================
      // Блок-передатчик числа
      //=========================================
      extern "C" __declspec(dllexport)
        int RDSCALL NetSend(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)))
      #define x (*((double *)(pStart+2*RDS_VSZ_S)))

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Создаем объект класса TNetSendRcvData с
              // Mode==NETSRMODE_SENDER (передатчик)
              BlockData->BlockData=new TNetSendRcvData(NETSRMODE_SENDER);
              break;

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

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

            // Связь с сервером установлена
            case RDS_BFM_NETCONNECT:
              // Взводим флаг наличия связи
              data->Connected=TRUE;
              // Если были данные, ожидающие отправки,
              // посылаем значение входа блока
              if(data->DataWaiting)
                data->SendValue(x);
              break;

            // Связь с сервером разорвана
            case RDS_BFM_NETDISCONNECT:
              // Сбрасываем флаг наличия связи
              data->Connected=FALSE;
              break;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              // Если это – первый запуск после сброса,
              // передаем значение входа
              if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart)
                data->SendValue(x);
              break;

            // Срабатывание таймера
            case RDS_BFM_TIMER:
              // Сбрасываем флаг ожидания таймера
              data->WaitingForTimer=FALSE;
              // Передаем значение входа блока
              data->SendValue(x);
              break;
            // Такт расчета
            case RDS_BFM_MODEL:
              if(data->CheckSendTimer()) // Можно передавать немедленно
                data->SendValue(x); // Передаем значение входа
              break;

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

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

           // Загрузка параметров в текстовом виде
           case RDS_BFM_LOADTXT:
              data->LoadText((char*)ExtParam);
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef x
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      //=========================================
      // Блок-приемник числа
      //=========================================
      extern "C" __declspec(dllexport)
        int RDSCALL NetReceive(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)))
      #define y (*((double *)(pStart+2*RDS_VSZ_S)))

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Создаем объект класса TNetSendRcvData с
              // Mode==NETSRMODE_RECEIVER (приемник)
              BlockData->BlockData=
                new TNetSendRcvData(NETSRMODE_RECEIVER);
              break;

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

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

            // По сети получены данные
            case RDS_BFM_NETDATARECEIVED:
              if(data->ReceiveValue((RDS_NETRECEIVEDDATA*)ExtParam,&y))
                Ready=1; // Если данные верны, взводим флаг готовности
              // для передачи выхода по связям
              break;

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

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

           // Загрузка параметров в текстовом виде
           case RDS_BFM_LOADTXT:
              data->LoadText((char*)ExtParam);
              break;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef y
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      //=========================================
      // Блок-передатчик массива
      //=========================================
      extern "C" __declspec(dllexport)
        int RDSCALL NetSendArray(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)))
      #define pX ((void **)(pStart+2*RDS_VSZ_S))

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Создаем объект класса TNetSendRcvData с
              // Mode==NETSRMODE_SENDER (передатчик)
              BlockData->BlockData=new TNetSendRcvData(NETSRMODE_SENDER);
              break;

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

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

            // Связь с сервером установлена
            case RDS_BFM_NETCONNECT:
              // Взводим флаг наличия связи
              data->Connected=TRUE;
              // Если были данные, ожидающие отправки,
              // посылаем значение входа блока
              if(data->DataWaiting)
                data->SendArray(pX);
              break;

            // Связь с сервером разорвана
            case RDS_BFM_NETDISCONNECT:
              // Сбрасываем флаг наличия связи
              data->Connected=FALSE;
              break;

            // Запуск расчета
            case RDS_BFM_STARTCALC:
              // Если это – первый запуск после сброса,
              // передаем значение входа
              if(((RDS_PSTARTSTOPDATA)ExtParam)->FirstStart)
                data->SendArray(pX);
              break;

            // Срабатывание таймера
            case RDS_BFM_TIMER:
              // Сбрасываем флаг ожидания таймера
              data->WaitingForTimer=FALSE;
              // Передаем значение входа блока
              data->SendArray(pX);
              break;

            // Такт расчета
            case RDS_BFM_MODEL:
              if(data->CheckSendTimer()) // Можно передавать немедленно
                data->SendArray(pX); // Передаем значение входа
              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;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef pX
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================

      //=========================================
      // Блок-приемник массива
      //=========================================
      extern "C" __declspec(dllexport)
        int RDSCALL NetReceiveArray(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)))
      #define pY ((void **)(pStart+2*RDS_VSZ_S))

        switch(CallMode)
          { // Инициализация
            case RDS_BFM_INIT:
              // Создаем объект класса TNetSendRcvData с
              // Mode==NETSRMODE_RECEIVER (приемник)
              BlockData->BlockData=
                new TNetSendRcvData(NETSRMODE_RECEIVER);
              break;

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

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

            // По сети получены данные
            case RDS_BFM_NETDATARECEIVED:
              if(data->ReceiveArray((RDS_NETRECEIVEDDATA*)ExtParam,pY))
                Ready=1; // Если данные верны, взводим флаг готовности
              // для передачи выхода по связям
              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;
          }
        return RDS_BFR_DONE;
      // Отмена макроопределений
      #undef pY
      #undef Ready
      #undef Start
      #undef pStart
      }
      //=========================================