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

Описания RDS

Описание пользователя

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

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

§3.6.13. Вызов функций блоков

§3.6.13.5. Объекты функций в автокомпилируемых моделях

# Описание пользователя

## Глава 3. Использование стандартных модулей автокомпиляции

### §3.6. Принципы создания автокомпилируемых моделей блоков

#### §3.6.13. Вызов функций блоков

##### §3.6.13.5. Объекты функций в автокомпилируемых моделях

Рассматриваются технические особенности классов и объектов, автоматически создаваемых в программе модели для работы с функциями блоков.

В §3.6.13.1 объяснялось, что для каждой функции блока модуль автокомпиляции создает объект специально сформированного для нее класса, и перечислялись основные функции-члены такого класса. При этом для некоторых из этих функций было указано, что их можно вызывать только из модели блока, то есть только из функций, являющихся членами класса самого блока (например, из любой функции реакции на событие). Может возникнуть вопрос: почему доступность публичной функции-члена объекта C++ может зависеть от места ее вызова?

Дело в том, что, на самом деле, для каждой функции создается не один класс и объект этого класса, а два разных класса и два объекта этих классов: один такой объект является глобальным, второй принадлежит классу блока. Функции-члены, относящиеся к функции блока в целом (например, вызов ее у конкретного блока или всех блоков конкретной подсистемы) описаны как в классе для глобального объекта, так и в классе объекта, принадлежащего блоку. Функции-члены, относящиеся к действиям, связанным с конкретным блоком (например, регистрация этого блока как исполнителя функции), описаны только в классе, объект которого добавляется в класс блока, и их нельзя вызвать из глобальных функций, поскольку оттуда этот объект недоступен. Имя глобального объекта и объекта в классе блока совпадают, поэтому разработчик модели везде использует одно и то же имя для обращения к объекту функции. Объект в классе блока перекрывает видимость одноименного глобального объекта, поэтому в функциях класса блока можно использовать расширенный набор функций-членов этого объекта. Из глобальных же функций будет виден только глобальный объект, класс которого не содержит функций-членов, оперирующих данными конкретного блока.

Рассмотрим, например, описания, автоматически добавляемые модулем автокомпиляции в программу модели блока проверки уровня, рассмотренного в §3.6.13.4 для функции «UserManual.Message» (см. рис. 479). Их можно увидеть, выбрав в окне редактора модели пункт меню «модель | показать текст C++» и прокрутив текст вниз до комментария «объекты для функций блока» (подсветка синтаксиса добавлена для большей наглядности, редактор модели такой подсветки не имеет):

      //---------------------------
      // Objects for block functions
      //---------------------------
      // Function "UserManual.Message"
      class rdsbcppFunction0G : public rdsbcppFunction // Global
      { public:
          // Call in block
          int Call(RDS_BHANDLE Block,TUserMessageFuncParam* param)
            {return rdsCallBlockFunction(Block,_Id,param);};
          // Call in all blocks inside subsystem
          int Broadcast(RDS_BHANDLE Parent,
              DWORD Flags,TUserMessageFuncParam* param)
            {return rdsBroadcastFunctionCallsEx(Parent,
                    _Id,param,Flags);};
          rdsbcppFunction0G(void):rdsbcppFunction(){};
          ~rdsbcppFunction0G(){};
      };
      rdsbcppFunction0G rdsfuncUM_Message;
      class rdsbcppFunction0L : public rdsbcppFunction // Local
      { public:
          // Call in block
          int Call(RDS_BHANDLE Block,TUserMessageFuncParam* param)
            {return rdsCallBlockFunction(Block,_Id,param);};
          // Call in all blocks inside subsystem
          int Broadcast(RDS_BHANDLE Parent,
              DWORD Flags,TUserMessageFuncParam* param)
            {return rdsBroadcastFunctionCallsEx(Parent,
                    _Id,param,Flags);};
          // Call in registered function provider
          int Call(TUserMessageFuncParam* param)
            {return _Link?
                  rdsCallBlockFunction(_Link->Block,_Id,param):0;};
          // Public access to functions
          inline void RegisterProvider(void){_RegisterProvider();};
          inline void UnregisterProvider(void){_UnregisterProvider();};
          inline void SubscribeToProvider(void){_SubscribeToProvider();};
          inline void UnsubscribeFromProvider(void)
              {_UnsubscribeFromProvider();};
          rdsbcppFunction0L(void):rdsbcppFunction(){};
          ~rdsbcppFunction0L(){};
      };
      //---------------------------

Здесь описаны два класса с именами rdsbcppFunction0G и rdsbcppFunction0L (эти имена модуль автокомпиляции создает автоматически). Оба класса являются потомками класса rdsbcppFunction, описанного в «CommonAC.hpp» и содержащего основные поля и функции, необходимые для работы с функциями блока. Класс rdsbcppFunction0G используется для создания глобального объекта: сразу за описанием этого класса следует объявление глобальной переменной rdsfuncUM_Message этого типа (такое имя мы выбрали для объекта функции при ее создании – см. рис. 479). Класс rdsbcppFunction0L, содержащий дополнительные функции-члены для регистрации и поиска исполнителя функции, будет использован для описания поля класса блока, которое тоже будет иметь имя rdsfuncUM_Message (это описание выделено цветом):

      //---------------------------
      // Block class
      //---------------------------
      class rdsbcppBlockClass
      { public:
          // RDS internal block data structure
          RDS_PBLOCKDATA rdsbcppBlockData;
          // Static variables
          rdsbcstSignal Start;
          rdsbcstSignal Ready;
          rdsbcstDouble x;
          rdsbcstDouble L;
          rdsbcstLogical out;
          rdsbcstDouble delta;

          // Objects for block functions (share their names with the global ones)
          rdsbcppFunction0L rdsfuncUM_Message;

          // Vars initialization
          void rdsbcppInitVars(void *base)
          {
          …

Таким образом, использование имени rdsfuncUM_Message внутри реакций на события и любых других функций, принадлежащих классу блока, будет обращением к объекту типа rdsbcppFunction0L. Использование этого же имени в функциях, не принадлежащих к классу блока, будет обращением к объекту типа rdsbcppFunction0G.

Ситуация, когда из глобальной функции необходимо обратиться к функциям, относящимся к регистрации и поиску исполнителей, возникает крайне редко: любая работа с исполнителем функции связана с каким-либо конкретным блоком, который либо объявляет себя таким исполнителем, либо запрашивает его поиск, то есть приказывает RDS постоянно сообщать данному конкретному блоку о появлении и исчезновении исполнителей заданной функции. Таким образом, регистрация исполнителей обычно производится из модели регистрирующегося блока, а поиск – из модели блока, запрашивающего этот поиск. Разработчик модели может захотеть вынести какие-либо действия из модели блока в отдельную функцию просто для своего удобства, и, если среди этих действий будет вызов специфических функций-членов рассматриваемых здесь объектов, лучше всего оформить такую функцию как член класса блока, то есть описать ее в разделе «описания внутри класса блока». Есть также возможность, при необходимости, вызвать функцию у блока-исполнителя не через объект внутри класса блока (в примере выше – объект типа rdsbcppFunction0L), а через глобальный объект. Для этого нужно просто явно указать идентификатор блока, у которого вызывается функция.

В примере, рассматриваемом в §3.6.13.4, мы выводили сообщение пользователю, вызывая функцию «UserManual.Message» у ее исполнителя следующим образом:

      rdsfuncUM_Message.Call(&param);

(здесь param – структура параметров функции типа TUserMessageFuncParam). В эту версию функции Call не передается идентификатор блока – он будет автоматически определен, и функция будет вызвана у ближайшего найденного исполнителя. Допустим, мы, по каким-то причинам, хотим вынести этот вызов в отдельную глобальную функцию. В этом случае мы уже не сможем пользоваться Call с одним параметром – в глобальном объекте, который будет виден из глобальной функции, такой версии функции-члена Call просто нет. Но у него есть обычная версия Call, в первом параметре которой явно передается идентификатор вызываемого блока. В нашей глобальной функции мы можем воспользоваться только ей, поэтому в параметрах этой функции нужно будет передать идентификатор блока:

      // Наша глобальная функция для вызова "UserManual.Message"
      void OurGlobalCaller(RDS_BHANDLE block,            // вызываемый блок
                           TUserMessageFuncParam *param) // параметр
      { rdsfuncUM_Message.Call(block,param);}

Этот идентификатор блока-исполнителя можно получить у объекта типа rdsbcppFunction0L при помощи функции-члена Provider. Таким образом, изнутри нашей модели вызов OurGlobalCaller выглядел бы так:

      OurGlobalCaller(rdsfuncUM_Message.Provider(),&param);

В большинстве случаев, разработчику модели можно не задумываться об организации классов для функций блоков: если он обращается к этим функциям из модели, ему всегда доступны все возможности. Принимать во внимание наличие двух разных классов для одной функции блока нужно только при обращении к ним из глобальных функций – например, из функций обратного вызова, подобных рассматриваемой в §3.6.13.3: такие функции не могут быть членами какого-либо класса по требованиям RDS. В этом случае нужно каким-либо образом передавать в глобальные функции идентификатор блока – например, описанным выше способом. К счастью, все функции обратного вызова в RDS имеют дополнительный параметр типа void*, через который в них можно передать указатель на любые необходимые данные.