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

Описания RDS

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

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

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

§2.15.2. Пример использования функций передачи и приема данных

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

- Server – блок, включающий серверные функции в RDS;
- NetSend – блок, передающий данные;
- 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; // Имя канала

          int ConnId; // Идентификатор соединения

          // Переменные состояния блока-передатчика
          BOOL Connected;   // Соединение установлено
          BOOL DataWaiting; // Передача данных отложена

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

          void SendValue(double value); // Передать число в канал
          BOOL ReceiveValue(RDS_NETRECEIVEDDATA *rcv, // Реакция на
                            double *pOut);            // пришедшие данные

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

          // Конструктор класса
          TNetSendRcvData(int mode)
            { ConnId=-1; // Нет соединения
              Connected=DataWaiting=FALSE;
              ChannelName=NULL;
              Mode=mode; // Режим передается в параметре конструктора
            };
          // Деструктор класса
          ~TNetSendRcvData()
            { Disconnect(); // Разорвать соединение
              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);
      }
      //=========================================

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

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

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

      // Настройка параметров
      // ВАЖНО: Исходный текст программы должен быть записан в UTF8,
      //   в противном случае необходимо использовать версии функций
      //   с суффиксом "W" и символьные константы с префиксом "L"
      int TNetSendRcvData::Setup(char *title)
      { char *NewName;

        // Запрос строки у пользователя
        NewName=rdsInputString(
                  title,         // Заголовок окна
                  "Имя канала:", // Текст перед полем
                  ChannelName,   // Исходное значение
                  200);          // Ширина поля

        if(NewName==NULL) // Нажата кнопка "Отмена"
          return 0;

        if(ChannelName!=NULL && strcmp(NewName,ChannelName)==0)
          { // Имя канала не изменилось
            rdsFree(NewName); // Освобождаем вовращенную строку
            return 0;
          }

        // Освобождаем старое имя канала
        rdsFree(ChannelName);
        // Запоминаем новое
        ChannelName=NewName;
        // Имя канала изменилось – устанавливаем связь заново
        Disconnect();
        Connect();
        return 1; // Параметры блока изменены
      }
      //=========================================

      //=========================================
      // Блок-передатчик
      //=========================================
      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_MODEL:
              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
      }
      //=========================================