Содержание:
На языке H разрабатываются кросс-платформенные модули, которые при компиляции упаковываются в байт-код. Сам язык реализован таким образом, чтобы в нем не было ничего лишнего, кроме минимально необходимого набора конструкций для обеспечения событийно-управляемого взаимодействия. Как и модуль, созданный на языке C++, модуль, написанный на языке H, имеет стандартную структуру, включающаю - мета-описания модуля, модель конечного автомата и код обработчиков.
Заготовка модуля генерируется специальным генератором и располагается в папке modules. Для выполнения задания предоставляется готовый скелет модуля. Содержимое типовой заготовки показано ниже:
Любой модуль имеет описание на языке 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 <выходная папка>
Подробнее:
Полученный файл <имя модуля>.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. Важно понимать, что отправка сообщения для модуля не является событием!
После того как все обработчики занесены в карту и их тела реализованы, код компилируется без ошибок, можно перейти к тестированию модуля.