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

Описания RDS

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

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

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

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

§3.6.13.1. Общие принципы работы с функциями блока

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

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

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

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

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

##### §3.6.13.1. Общие принципы работы с функциями блока

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

Модуль автокомпиляции облегчает разработчику реализацию в моделях блоков функций, которые могут непосредственно вызываться другими блоками. Каждой такой функции блока в RDS присваивается произвольное текстовое имя, по которому ее опознают другие блоки. Эти блоки могут находиться в разных DLL, написанных на разных языках программирования, поэтому всю передачу данных между ними берет на себя RDS. Чтобы добавить в модель функцию блока (не важно – будет она вызываться у этого блока или, наоборот, этот блок будет вызывать ее у других), необходимо добавить эту функцию на вкладку «функции» левой панели редактора модели, указав для нее текстовое имя, тип ее параметра и имя внутреннего объекта, который будет создан в модели для работы с ней. Для вызова функции у других блоков нужно будет вызывать функции-члены этого объекта, передавая в них параметры функции и идентификаторы вызываемых блоков. После того, как функция будет добавлена в модель, на вкладке «события» редактора появится новое событие – вызов этой функции у данного блока. В реакции на это событие записываются все действия, которые блок должен выполнить, если у него вызвана функция с таким именем.

Функции блоков создаются программистами их моделей, и пользователь не может как-то повлиять на их работу. Как правило, все начинается с того, что создатель модели решает включить в свой блок не только обработку данных на входах и выдачу результатов на выходы, но и возможность непосредственного выполнения команд от других блоков – то есть добавляет в модель реакцию на вызов функции. Прежде всего необходимо придумать для этой функции уникальное текстовое имя и набор данных, который будет передаваться при ее вызове (у функции может быть только один параметр, поэтому обычно такие данные оформляют в виде структуры и передают указатель на нее). Для обеспечения уникальности имени функции это имя обычно делают длинным и каким-либо образом отражающим смысл выполняемых ей действий. Чтобы избежать возможных проблем с кодировкой строк, в именах функций обычно используют только латинские буквы, цифры и знаки препинания, хотя в RDS и нет жестких требований к этим именам. Общие рекомендации по выбору имен для новых функций даются в §2.13.1 руководства программиста, примеры имен стандартных функций приведены в §1.6.

Придумав имя функции, необходимо решить, будут ли у нее параметры, и если будут, то какие именно. Формально у функции блока есть только один параметр типа void*, то есть «указатель на что-либо». Если функции не нужны параметры, она просто игнорирует этот указатель (при этом в нем чаще всего передают значение NULL). Если же параметры нужны, этот указатель обычно ссылается на какую-либо область памяти, содержащую эти параметры. RDS передает указатель на область параметров от вызвавшей модели к вызванной без каких-либо проверок, поэтому разработчику модели вызываемого блока следует самостоятельно проверять, соответствуют ли переданные параметры вызванной функции. Такая проверка очень важна, поскольку модель вызывающего блока может быть разработана другим программистом, и нельзя гарантировать того, что он вызовет функцию, правильно передав ей параметры. Передача неправильных параметров может привести к серьезным ошибкам. Допустим, например, что функция блока принимает один параметр типа double. Можно сделать параметром функции указатель на это число, при этом реакция на вызов этой функции будет преобразовывать полученный указатель типа void* к типу double* и обращаться через него к переданному числу. Однако, разработчик вызывающей модели может по ошибке (или из-за недостаточно ясно написанного описания функции) передать в нее не указатель на double, а указатель на четырехбайтовое целое число int. RDS приведет этот указатель к типу void* и передаст его модели вызываемого блока. Эта модель, в свою очередь, приведет этот указатель к типу double* и попытается считать по нему вещественное восьмибайтовое число. В лучшем случае она при этом считает неправильное значение, в худшем – обращение к восьми байтам по адресу, по которому отведено только четыре, вызовет ошибку общей защиты приложения (GPF).

Чтобы уменьшить вероятность возникновения подобных проблем, параметры функции, какими бы они ни были, обычно оформляют в виде структуры, в первое поле которой записывают ее же размер. Например, для функции, принимающей вещественное число double, можно описать такую структуру параметров:

      typedef struct
      { DWORD servSize; // Размер структуры параметров
        // ... параметры функции ...
        double Value;   // Передаваемое вещественное число
      } TMyFuncParam;

При вызове функции в поле servSize этой структуры нужно записать ее размер, полученный стандартным оператором языка C sizeof, а в поле Value – передаваемое при вызове функции вещественное значение:

      TMyFuncParam param;           // Структура параметров
      param.servSize=sizeof(param); // Размер структуры
      param.Value=12.34;            // Передаваемое число
      // При вызове функции в качестве параметра
      // будет передаваться &param

В реакции на вызов функции переданный указатель будет приводиться к типу TMyFuncParam* («указатель на TMyFuncParam») – модуль автокомпиляции вставляет операцию приведения типа автоматически. Вызванная модель при этом может сравнить значение поля servSize этой структуры с ее размером. Если значение поля не меньше размера структуры, значит, передано достаточно данных для работы. В реакциях на вызов функции их параметр всегда имеет имя Param, поэтому текст реакции на вызов функции, вводимый пользователем, будет выглядеть следующим образом:

      // Param имеет тип TMyFuncParam*
      if(Param!=NULL && Param->servSize>=sizeof(TMyFuncParam))
        { // Можно выполнять функцию
          ...
        }

Здесь не проверяется точное равенство значения поля (то есть размера структуры у вызвавшей модели) размеру структуры у вызываемой, вместо этого проверяется достаточность размера переданной структуры. Это делается для того, чтобы, если в будущем к структуре параметров функции будут добавлены дополнительные поля для расширения ее возможностей, все ранее написанные модели, не пользующиеся этим новыми возможностями, продолжили бы нормально работать. Если размер структуры, переданной в качестве параметра функции, окажется больше ожидаемого, старая модель все равно может выполнить функцию, поскольку все нужные ей старые поля в этой структуре присутствуют. Дополнительные поля в этом случае можно добавлять только в конец структуры, не изменяя ту ее часть, с которой будут работать старые модели. При этом новая модель, выполняющая функцию, должна по переданному размеру структуры определить, есть ли в структуре новые поля, и, если их нет, но размер структуры достаточен для старой версии функции, выполнить ее. Например, можно написать реакцию так:

      // Param имеет тип TMyFuncParam*
      if(Param!=NULL)
        { if(Param->servSize>=sizeof(TMyFuncParam))
           { // Можно выполнять новую версию функции
               ...
           }
          else if(Param->servSize>=размер_для_старой_версии)
           { // Можно выполнять старую версию функции
               ...
           }
        }

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

- одновременный вызов функции у всех блоков схемы;
- одновременный вызов функции у всех блоков родительской подсистемы вызывающего блока (идентификатор этой подсистемы содержится в поле Parent структуры данных блока, доступной по указателю rdsbcppBlockData);
- вызов функции некоторого блока в ответ на вызов этим блоком функции данного блока (идентификатор вызвавшего функцию блока может быть считан из структуры RDS_FUNCTIONCALLDATA, указатель на которую доступен из реакции модели на вызов функции);
- вызов функции у блока, идентификатор которого получен путем анализа схемы при помощи сервисных функций RDS (например, можно перебрать все блоки, соединенные связями с данным, и вызвать у них какую-либо функцию);
- вызов функции у блока, зарегистрировавшего себя в качестве исполнителя этой функции.

Можно получить идентификатор блока и другим способом (например, если известно полное имя блока, можно получить его идентификатор при помощи функции rdsBlockByFullName), но перечисленные выше способы используются чаще всего. В любом случае, вызов функции блока, как правило, используется для передачи этому блоку каких-либо данных, и при этом, обычно, известно, какому именно блоку передаются данные. Если адресат данных неизвестен (например, нужно выполнить какую-то команду, и не важно, кто именно ее выполнит), функцию вызывают сразу у всех блоков схемы или у блока, который заявил RDS о том, что он выполняет данную функцию.

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

      TMyFuncParam param;            // Структура параметров
      param.servSize=sizeof(param);  // Размер структуры
      param.Value=12.34;             // Передаваемое число
      MyFunc.Broadcast(
        rdsbcppBlockData->Parent,    // Родительская подсистема
        0,                           // Флаги вызова (нет)
        &param);                     // Параметр функции

Перечислим коротко основные функции-члены класса объектов, создаваемых для каждой функции:

int Id(void)
  Уникальный целый идентификатор функции, присвоенный ей в RDS. Идентификатор функции может потребоваться в тех случаях, когда вызов функции производится не при помощи объекта, созданного модулем автокомпиляции, а напрямую, при помощи сервисных функций RDS. Пример использования функции:

        rdsBroadcastFuncCallsDelayed(   // Отложенный вызов
          rdsbcppBlockData->Parent,     // Родительская подсистема
          MyFunc.Id(),                  // Идентификатор функции
          &param,                       // Область параметров функции
          sizeof(param),                // Размер области
          0);                           // Флаги вызова (нет)

int Call(RDS_BHANDLE Block,структура_параметров *param)
  Вызов функции у блока с идентификатором Block, если у функции есть параметр. В качестве параметра передается указатель param, имеющий тип «указатель на данные типа структура_параметров». Функция возвращает целое число, которое возвратила модель блока, среагировавшего на вызов функции. Пример использования функции:

        // Вызов функции у своего собственного блока
        MyFunc.Call(               // Вызов
          rdsbcppBlockData->Block, // Этот блок
          &param);                 // Область параметров функции

int Call(RDS_BHANDLE Block)
  Вызов функции у блока с идентификатором Block, если у функции нет параметра (вместо указателя на параметр в модель вызываемого блока передается NULL). Используется точно так же, как и предыдущая функция. Пример использования этой функции-члена приведен в §3.6.13.3.
int Call(структура_параметров *param)
  Вызов функции у блока, зарегистрированного как исполнитель данной функции, если у функции есть параметр. От двух предыдущих версий функции-члена Call эта версия отличается тем, что в ней не указывается идентификатор вызываемого блока – RDS определяет его самостоятельно, находя блок, объявивший себя исполнителем этой функции. В качестве параметра передается указатель param, имеющий тип «указатель на данные типа структура_параметров». Функция возвращает целое число, которое возвратила модель блока, среагировавшего на вызов функции. Эту функцию-член можно вызывать только из модели блока, то есть из реакций блока на различные события и из функций, объявленных членами класса блока. Пример использования этой функции-члена приведен в §3.6.13.4.
int Call(void)
  Вызов функции у блока, зарегистрированного как исполнитель данной функции, если у функции нет параметра (вместо указателя на параметр в модель вызываемого блока передается NULL). Для этой функции-члена справедливы те же ограничения в использовании, что и для предыдущей – ее нельзя использовать вне модели блока, то есть в функциях из глобальных описаний (не являющихся членами класса блока).
int Broadcast(RDS_BHANDLE Sys,DWORD Flags,структура_параметров *param)
  Вызов функции у всех блоков подсистемы Sys, если у функции есть параметр. В качестве параметра передается указатель param, имеющий тип «указатель на данные типа структура_параметров». Flags – флаги, управляющие вызовом функций. Функция возвращает общее число блоков, модели которых были вызваны. Пример использования функции:

        // Вызов функции у всех блоков в родительской подсистеме
        MyFunc.Broadcast(           // Вызов у блоков подсистемы
          rdsbcppBlockData->Parent, // Родительская подсистема
          0,                        // Флаги вызова (нет)
          &param);                  // Область параметров функции

int Broadcast(RDS_BHANDLE Sys,DWORD Flags)
  Вызов функции у всех блоков подсистемы Sys, если у функции нет параметра (вместо указателя на параметр в модель вызываемого блока передается NULL). Используется точно так же, как и предыдущая функция.
void RegisterProvider(void)
  Зарегистрировать данный блок в RDS в качестве исполнителя функции. Обычно такая регистрация делается автоматически при добавлении функции в редактор модели при помощи флажка «объявить блок исполнителем функции» на панели «дополнительные действия» окна параметров функции (см. рис. 342), поэтому в вызове RegisterProvider нет необходимости. Этот вызов нужен только в том случае, если регистрация блока производится вручную – например, если разработчик хочет разрешать или запрещать ее в настройках блока. Вызывать RegisterProvider можно только из модели блока, то есть из реакций на события и из функций, объявленных членами класса блока.
void UnregisterProvider(void)
  Отменить регистрацию блока в качестве исполнителя функции. Эта функция-член нужна только при ручной регистрации блока вызовом RegisterProvider. Если блок зарегистрирован установкой флажка при добавлении функции в редактор модели, при удалении блока регистрация будет отменена автоматически.
void SubscribeToProvider(void)
  Найти и запомнить блок, объявивший себя исполнителем этой функции. После такого поиска для вызова функции можно пользоваться версией функции-члена Call, в которую не передается идентификатор вызываемого блока. При изменении блока-исполнителя (например, при его стирании и появлении нового) запомненная информация будет обновляться автоматически. Для поиска исполнителя достаточно установить флажок «найти в схеме исполнителя функции» на панели «дополнительные действия» окна параметров функции (см. рис. 342), поэтому в ручном вызове SubscribeToProvider обычно нет необходимости. Вызывать эту функцию-член можно только из модели блока, то есть из реакций на события и из функций, объявленных членами класса блока.
void UnsubscribeFromProvider(void)
  Прекратить слежение за блоками-исполнителями данной функции. Как правило, в ручном вызове этой функции нет необходимости. Как и предыдущую, эту функцию-член можно вызывать только непосредственно из модели блока.
BOOL Subscribed(void)
  Успешность поиска исполнителя функции (TRUE, если исполнитель функции существует в схеме, FALSE в противном случае). Вызывается только непосредственно из модели блока.
RDS_BHANDLE Provider(void)
  Получить идентификатор найденного блока-исполнителя (если его нет, возвращается NULL). Изнутри модели блока вызвать функцию у исполнителя можно и не получая в явном виде его идентификатор – для этого достаточно использовать вариант функции-члена Call, в который не передается идентификатор блока. Однако, если вызов функции блока производится не непосредственно из какой-либо реакции модели (то есть не из функции-члена класса блока), этот вариант Call будет недоступен, и нужно будет пользоваться другим вариантом этой функции, в котором первым параметром будет идентификатор вызываемого блока. Для этого нужно будет как-то передать в то место программы, которое будет вызывать функцию, результат возврата Provider. Эта функция тоже вызывается только непосредственно из модели блока (т. е. из реакций на события и из функций, объявленных членами класса блока), поэтому снаружи модели получить идентификатор блока-исполнителя из самого объекта функции невозможно.
BOOL IsProvider(void)
  Возвращает TRUE, если данный блок зарегистрирован как исполнитель функции, и FALSE в противном случае. Вызывается только непосредственно из модели блока.

Примеры различных способов вызова функций при помощи объектов, создаваемых модулем автокомпиляции, будут рассмотрены далее. Следует учитывать, что эти объекты поддерживают только прямой, то есть немедленный, вызов функций блоков – отложенный вызов, если это потребуется, необходимо выполнять при помощи сервисных функций RDS (назначение и особенности отложенного вызова функций блоков рассматриваются в §2.13.5 руководства программиста).