Меню


Site Logo

На языке H разрабатываются кросс-платформенные модули, которые при компиляции упаковываются в байт-код. Сам язык реализован таким образом, чтобы в нем не было ничего лишнего, кроме минимально необходимого набора конструкций для обеспечения событийно-управляемого взаимодействия. Как и модуль, созданный на языке C++, модуль, написанный на языке H, имеет стандартную структуру, включающаю - мета-описания модуля, модель конечного автомата и код обработчиков.

Заготовка модуля

Заготовка модуля генерируется специальным генератором и располагается в папке modules. Для выполнения задания предоставляется готовый скелет модуля. Содержимое типовой заготовки показано ниже:

Структура модуля

Порядок реализации кода модуля логики

  • Доделать сгенерированный описатель модуля;
  • Конвертировать конечный автомат из формата Visio в формат M;
  • Подключить к модулю используемые и реализуемые протоколы;
  • Реализовать необходимые обработчики переходов.

Исправление описания модуля

Любой модуль имеет описание на языке M. Оно в обязательном порядке содержит: версию (в значении ключа Version), список поддерживаемых поколений (массив в значении ключа Generations), имя динамической библиотеки (в значении ключа Module) и зависимости (имена и поколения других модулей, которые потребуются данному модулю для работы). Каждый модуль предоставляет точное описание необходимых ему протоколов в своих мета-данных (при этом используется стандартный механизм указания зависимостей).

; Описание системной библиотеки-расширения микро-ядра 
<root>: Uniteller\Images
Uniteller.Framework.Kernel.System
  Version : 1.0.0       ; Версия
  Generations : {1}     ; Поддерживаемые модулем поколения    
  Protocols             ; Поддерживаемые модулем протоколы
    Kernel.System : 1   ; Поддерживаемый протокол, поколение
  Module : Uniteller.Framework.Kernel.System.dll ; Имя модуля
  Dependencies ; Зависимости  
    Uniteller.Framework.Kernel : 1
    Uniteller.InterchangeFormats.Kernel.System : 1

Включение в модуль модели конечного автомата

Конечный автомат не программируется в модуле вручную. В модуль встраивается модель конечного автомата, заданная на языке M. Изначально эта модель разрабатывается в графическом редакторе. Для встраивания модели в модуль выполняется ее трансформация в декларативное описание. Такое описание предполагает перечисление всех состояний, событий и переходов в автомате. Ядро реализует интерпретатор этого описания. В момент прихода сообщения срабатывает переход. В этот момент вызывается указанная явно в обрамлении << >> функция-обработчик перехода, найденная в файле с обработчиками (handlers.utx). Имя этой функции определяется автоматически по машине состояний (если под активатором перехода укзать <<Handler>>, то в файле handlers.utx будет найден обработчик с именем Handler).

Декларативное описание не разрабатывается вручную. Для получения декларативного описателя по модели конечного автомата в формате Visio необходимо использовать специальную утилиту — VisioExporter. Для запуска этой утилиты следует использовать следующую командную строку:

VisioExporter.exe -model <путь к файлу-модели (в формате vsd)> -out <выходная папка>

Подробнее:

  • Аргумент <путь к файлу-модели (в формате vsd)> задает путь к файлу модели конечного автомата в формате Visio для загрузки и конвертирования в декларативное описание в формате UniText. Если не будет указан абсолютный путь, то используемый скрипт ищется относительно текущей рабочей папки (той папки откуда вызывается команда на запуск VisioExporter).
  • Аргумент <выходная папка> задает путь к папке, в которую будет сгенерировано декларативное описание конечного автомата.Аргументы <путь к файлу-модели (в формате vsd)> и <выходная папка> следует брать в кавычки («»).

Полученный файл <имя модуля>.utx следует скопировать в папку модуля.

Если для выполнения задания использовать среду разработки, то весь процесс преобразования может быть выполнен значительно проще - через нажатие кнопки конвертировать в панели инструментов редактора машины состояний.

Подключение необходимых протоколов

Протокол подключается к модулю на уровне заголовочного файла <имя протокола>.h. Если используется несколько протоколов, то нужно получить все их заголовочные файлы. Прежде чем переходить к следующему этапу следует убедиться в том, что все необходимые файлы протоколов находятся в папке [references].

Создание кода обработчиков

Обработчики на языке H пишутся в файле handlers.utx. Заготовка этого файла генерируется автоматически. Заготовку следует доработать вручную. Файл имеет простую структуру:

<Имя машины состояний>
  Имя обработчика 1
    Код обработчика 1
    ...
  Имя обработчика 2
    Код обработчика 2
    ...    
  и так далее.

На первом этапе доработки следует открыть модель конечного автомата модуля и определить все переходы, которые должны обрабатываться. Для каждого такого перехода необходимо указать имя обработчика в обрамлении << >> (Например: <<ActivationHandler>>). Далее следует перейти в файл handlers.utx и определить там код всех обработчиков, использованных в машине состояний (По-хорошему, для каждого перехода должен быть свой обработчик. Однако это не является обязательным условием, можно назначать один и тот же обработчик на разные переходы, или вообще не указывать обработчик - тогда при переходе ничего не вызовется).

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

Формально обработчики являются функциями, принимающими одно сообщение (сообщение, активировавшее переход, вызвавший функцию-обработчик). Само сообщение-активатор доступно в теле обработчика через переменную ?activator. У нее можно получать параметры, например: ?activator.SourceId - вернет адрес источника сообщения.

В теле обработчика можно описать его логику, с применением базовых конструкция языка H. В основном обработчики обеспечивают проверку параметров входящих сообщений (для этого используется конструкция Verify) или отправку других сообщений иным модулям - для этого используется конструкция Send.

Для иллюстрации реализуем обработчик, проверяющий параметры сообщения и отправляющий ответ:

  TestHandler
    Verify  
      01 : * ; Не важно, кто отправитель сообщения
        Protocol.MessageExamble : 1 ; Сообщение
          SignalCount : 0 ; Проверка параметра сообщения, атрибут SignalCount должен быть равен нулю.
        Send : <Controller> ; Отправка сообщения модулю с адресом 
          Controller.Initialize : 1 
      02 : * ; Не важно, кто отправитель сообщения
        Protocol.MessageExamble : 1 ; Сообщение 
        ; Параметры сообщения не проверяются
        Send : <Controller> ; Отправка сообщения модулю с адресом 
          Controller.Tick : 1 

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

Следует отметить, что в некоторых случаях модуль может генерировать сообщения для самого себя. Для этого он использует конструкции Send : <Self> или Raise. Важно понимать, что отправка сообщения для модуля не является событием!

После того как все обработчики занесены в карту и их тела реализованы, код компилируется без ошибок, можно перейти к тестированию модуля.