Аннотация
Чаще всего D‑Bus используется для реализации сервиса, который будет применяться несколькими клиентскими программами, соответственно все интерфейсы, экспортируемые на шину, образуют общедоступный API. Проектирование D-Bus API похоже на проектирование любого другого API: здесь много гибкости, но есть шаблоны проектирования, которым нужно следовать, и антишаблоны, которых следует избегать.
Это руководство призвано объяснить лучшие практики написания D‑Bus API-интерфейсов. Они были усовершенствованы за несколько лет использования D-Bus во многих проектах. Будут даны указания по реализации API с использованием общих библиотек D‑Bus, таких как GDBus, но подробные инструкции по реализации оставлены для документации библиотек. Обратите внимание, что вы не должны использовать dbus-glib для реализации сервисов D-Bus, поскольку она устарела и не поддерживается. Большинству сервисов также следует избегать libdbus (dbus-1), которая является библиотекой низкого уровня и ее неудобно правильно использовать: она спроектирована для использования через языковой биндинг, такой как QtDBus.
Для документации по самой D-Bus см. Спецификацию D-Bus (Руcский перевод).
API
D-Bus API – это спецификация одного или нескольких интерфейсов, которые будут реализованы объектами, предоставляемыми сервисом на шину. Обычно API разрабатывается как набор файлов описания интерфейсов (интерфейсные файлы), и реализация соответствует этим файлам. Некоторые проекты, однако, предпочитают определять API в коде сервиса и экспортировать интерфейсные XML-файлы из работающего сервиса с использованием интроспекции D-Bus. Оба подхода допустимы.
Для простоты в этом документе в качестве канонического представления используются XML‑описания интерфейсов D-Bus.
Интерфейсные файлы
Интерфейсный файл D-Bus – это файл XML, который описывает один или несколько интерфейсов D-Bus и является лучшим способом описания API D-Bus в машиночитаемой форме. Формат описан в спецификации D-Bus и поддерживается такими инструментами, как gdbus‑codegen.
Интерфейсный файлы для общедоступного API должны быть установлены в $(datadir)/dbus-1/interfaces, чтобы другие сервисы могли их загружать. Приватные API не должны устанавливаться. Для каждого интерфейса D-Bus должен быть установлен один файл, названный по имени интерфейса, содержащий единственный элемент <node> верхнего уровня с единственным <interface> под ним. Например, интерфейс com.example.MyService1.Manager будет описываться файлом
$(datadir)/dbus-1/interfaces/com.example.MyService1.Manager.xml:
Пример XML для интерфейса D-Bus
Краткий пример интерфейсного XML-документа.
<!DOCTYPE node PUBLIC
"-//freedesktop//DTD D-BUS Object Introspection 1.0//EN"
"http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd" >
<node xmlns:doc="http://www.freedesktop.org/dbus/1.0/doc.dtd">
<interface name="com.example.MyService1.InterestingInterface">
<method name="AddContact">
<arg name="name" direction="in" type="s">
<doc:doc><doc:summary>Имя нового контакта</doc:summary></doc:doc>
</arg>
<arg name="email" direction="in" type="s">
<doc:doc><doc:summary>Электронный адрес нового контакта</doc:summary></doc:doc>
</arg>
<arg name="id" direction="out" type="u">
<doc:doc><doc:summary>ID добавляемого контакта</doc:summary></doc:doc>
</arg>
<doc:doc>
<doc:description>
<doc:para>
Добавляет в адресную книгу новый контакт с его именем и электронным адресом.
</doc:para>
</doc:description>
</doc:doc>
</method>
</interface>
</node>
Если интерфейс, определенный сервисом A, должен использоваться клиентом B, клиент B должен декларировать зависимость от сервиса A на этапе сборки и использовать установленную копию интерфейсного файла для любой генерации кода, которую он должен сделать. У него не должно быть локальной копии интерфейса, так как она может рассинхронизироваться с канонической копией в git‑репозитории сервиса A.
Управление версиями API
Как и Си API, интерфейсы D‑Bus должны быть разработаны таким образом, чтобы их можно было использовать параллельно с версиями, несовместимыми с API. Это достигается включением номера версии в каждое имя интерфейса, имя сервиса и объектный путь, который увеличивается при каждом обратно несовместимом изменении.
Номера версий должны быть включены во все API‑интерфейсы от первого релиза, поскольку это означает, что переход к новой версии так же прост, как увеличение номера, а не повсеместная вставка номера, что требует больше усилий.
Новый API может быть добавлен к D‑Bus интерфейсу без увеличения номера версии, поскольку такие дополнения по-прежнему имеют обратную совместимость. Однако, клиенты, если они хотят работать со старыми версиями сервиса, которые не реализуют новые методы, должны аккуратно обрабатывать ответ об ошибке org.freedesktop.DBus.Error.UnknownMethod для всех вызовов методов D‑Bus. (Это также предотвращает использование сгенерированных биндингов; любой метод, от которого клиент хочет элегантно отказаться, должен вызываться с использованием общего вызова метода D‑Bus, а не конкретного сгенерированного биндинга.)
Когда API сломан, изменен или удален, номер версии сервиса должен быть увеличен; например, от com.example.MyService1 до com.example.MyService2. Если обратная совместимость в сервисе поддерживается за счет реализации как старого, так и нового интерфейсов, сервис может владеть общеизвестными именами для обоих, чтобы клиенты могли использовать то, что они поддерживают.
Как обсуждается в разделе "Аннотации", новые или устаревшие API‑интерфейсы должны быть помечены в интерфейсном XML с помощью аннотаций.
Однако, обратите внимание, что для одновременной поддержки нескольких версий интерфейса требуется, чтобы пути к объектам также были версионированными, поэтому объекты не должны располагаться на шине по корневому пути (‘/’). Этому есть технические причины: сигналы, отправленные из сервиса D-Bus, содержат имя исходного сервиса, представленное его уникальным именем (например, com.example.MyService2 перезаписывается как: "1:15"). Если пути к объектам являются общими для объектов, реализующих две версии интерфейса сервиса, клиентские программы не смогут определить, от какого объекта пришел сигнал. Решение состоит в том, чтобы включить номер версии во все пути к объектам, например /com/example/MyService1/Manager вместо "/" или /com/example/MyService/Manager.
Таким образом, номера версий должны быть включены во все имена сервисов, имена интерфейсов и объектные пути:
com.example.MyService1
com.example.MyService1.InterestingInterface
com.example.MyService1.OtherInterface
/com/example/MyService1/Manager
/com/example/MyService1/OtherObject
Дизайн API
Дизайн D‑Bus API в целом аналогичен дизайну Си API, но есть несколько дополнительных моментов, которые следует учитывать, они возникают как из-за функционировании D‑Bus (явные ошибки, сигналы и свойства), так и при его реализации в качестве IPC системы.
Вызов методов D-Bus намного дороже, чем вызовы функций Си, обычно для завершения цикла туда и обратно требуется порядка миллисекунд. Следовательно, дизайн должен минимизировать количество вызовов методов, необходимых для выполнения операции.
Система типов очень выразительна, особенно по сравнению с Си, иAPI‑интерфейсы должны в полной мере использовать ее.
Точно так же его поддержка сигналов и свойств отличает его от обычныхAPI‑интерфейсов Си, а хорошо написанные API‑интерфейсы D‑Bus в полной мере используют эти функции там, где это необходимо. Они могут быть объединены со стандартными интерфейсами, определенными в спецификации D-Bus, чтобы обеспечить согласованный доступ к свойствам и объектам в иерархии.
Минимизация задержек
Каждый вызов метода D-Bus – это путь туда и обратно между клиентской программой и сервисом, что дорого и занимает порядка миллисекунд. Одним из главных приоритетов при разработке D-Bus API является минимизация количества таких циклов, необходимых клиентам. Это может быть достигнуто за счет комбинации предоставления конкретных удобных API‑интерфейсов и разработки API‑интерфейсов, которые работают с большими наборами данных за один вызов, вместо того, чтобы выполнять вызовы для каждого элемента из набора данных.
Рассмотрим API адресной книги com.example.MyService1.AddressBook. Для добавления контакта он может иметь метод AddContact(ss) → (u) (принимающий параметры имени и адреса электронной почты и возвращающий уникальный идентификатор контакта) и метод RemoveContact(u) для удаления контакта по идентификатору. В обычном случае, для работы с отдельными контактами в адресной книге эти вызовы оптимальны. Однако, если пользователь хочет импортировать список контактов или удалить несколько контактов одновременно, необходимо делать вызов для каждого контакта – для больших списков контактов это может занять много времени.
Методы AddContact и RemoveContact в интерфейсе может заменить метод UpdateContacts(a(ss)au) → (au), который принимает массив структур, содержащих сведения о новых контактах, и массив идентификаторов контактов для удаления, и возвращает массив идентификаторов для новых контактов. Это сокращает количество необходимых циклов до одного.
Добавление удобных API‑интерфейсов для замены серии вызовов общих методов на вызов одного, специального для этой задачи метода лучше всего делать после профилирования API и выявления узких мест, в противном случае это может привести к преждевременной оптимизации. Одна из областей, где обычно могут быть добавлены удобные методы, – это время инициализации клиента, когда требуется несколько вызовов методов, чтобы настроить его взаимодействие с сервисом.
Использование преимуществ системы типов
Система типов D-Bus похожа на систему Python, но с более лаконичным синтаксисом, который может быть незнаком Си‑разработчикам. Ключ к эффективному использованию системы типов – раскрыть как можно больше структуры типов. В частности, следует избегать отправки структурированных строк по D-Bus, поскольку они должны быть построены и проанализированы; и то и другое является сложной операцией, подверженной ошибкам.
Для API, используемых в ограниченных ситуациях, перечисляемые значения должны передаваться как целые числа без знака. Для API-интерфейсов, которые должны быть расширены третьими сторонами или которые используются в более слабосвязанных системах, перечисляемые значения должны быть строками в определенном формате.
Передача значений в виде целых чисел означает, что можно избежать синтаксического анализа и сопоставления строк, сообщения более компактны, а разработчикам легче избежать опечаток (например, если в реализации используются перечисления Си).
Передача значений в виде строк означает, что дополнительные значения могут быть определены третьими сторонами, не опасаясь конфликта из-за целочисленных значений; например, используя то же пространство имен обратного доменного имени, что и интерфейсы D-Bus.
В обоих случаях документация по интерфейсу должна дать описание каждого значения. В нем должно быть указано, может ли тип быть расширен в будущем, и если да, то как сервис и клиент должны обрабатывать нераспознанные значения – обычно полагая их равными «неизвестному» или «неудачному» значению. Обычно в качестве «неизвестного» значения используется ноль.
Например, вместо:
<!--
Status:
Статус объекта.
Допустимые статусы: ‘неизвестен’, ‘готов’, ‘завершен’.
-->
<property name="Status" type="s" access="read" />
используйте:
<!--
Status:
Статус объекта.
Допустимые статусы: 0 = Неизвестен, 1 = Готов, 2 = Завершен.
Нераспознанные статусы следует расматривать как равные статусу Завершен.
-->
<property name="Status" type="u" access="read" />
Точно так же следует использовать перечисляемые значения вместо логических, поскольку они позволят добавить в будущем дополнительные значения, и нет двусмысленности в толковании логического значения.
Например, вместо:
<!--
MoveAddressBook:
@direction: %TRUE чтобы сдвинуть вверх по списку, %FALSE чтобы сдвинуть вниз.
Сдвигает эту адресную книжку вверх или вниз пользовательского списка
адресных книг. Контакты из верхней адресной книги в результатах поиска
будут показаны первыми.
-->
<method name="MoveAddressBook">
<arg name="direction" type="b" direction="in" />
</method>
сделаем более понятным, чем логическое значение:
<!--
MoveAddressBook:
@direction: 0 = Двигать ввер по списку, 1 = Двигать вниз по списку.
Сдвигает эту адресную книжку вверх или вниз пользовательского списка
адресных книг. Контакты из верхней адресной книги в результатах поиска
будут показаны первыми.
Не определенные в перечислении значения @direction не будут приводить к
перемещению книг в списке.
-->
<method name="MoveAddressBook">
<arg name="direction" type="u" direction="in" />
</method>
Перечисляемые значения также следует использовать вместо удобочитаемых строк, которые по возможности не следует пересылать по шине. Сервис и клиент могут работать в разных регионах и, следовательно, по-разному интерпретировать любые читаемые человеком строки или представлять их пользователю на неправильном языке. Передайте перечислимое значение и преобразуйте его в удобочитаемую строку на клиенте.
В ситуациях, когда сервис получил удобочитаемую строку откуда-то еще, он должен передать ее клиенту без изменений, в идеале вместе с локалью. Лучше передать клиенту удобочитаемую информацию, чем ничего не передавать.
Например, вместо:
<!--
ProgressNotification:
@progress_message: Текстовое сообщение с удобочитаемым статусом прогресса.
Излучается при заметном прогрессе какой-либо операции. Для удобства
пользователя сообщение @progress_message может быть показано в диалоге
визуального интерфейса.
-->
<signal name="ProgressNotification">
<arg name="progress_message" type="s" />
</method>
о прогрессе следует сообщать в виде пронумерованного значения:
<!--
ProgressNotification:
@progress_state: 0 = Подготовка, 1 = В процессе, 2 = Выполнено.
Излучается в моменты когда достигается видимый прогресс в каком-либо
из состояний. Аргумент @progress_state обычно преобразуется в
удобочитаемую строку и предоставляется пользователю.
Нераспознанные значения @progress_state будут рассматриваться как
состояние 1, т.е. как «В процессе».
-->
<signal name="ProgressNotification">
<arg name="progress_state" type="u" />
</method>
D‑Bus не имеет проблем со знаковыми и беззнаковыми целыми числами, которые есть у Си (в частности, он не выполняет неявное преобразование знака), поэтому целочисленные типы всегда должны выбираться так, чтобы они соответствовали размеру и сигнатуре данных, которые они могли бы содержать. Как правило, значения без знака требуются чаще, чем значения со знаком.
Структуры можно использовать практически в любом месте типа D‑Bus, и особенно полезны массивы структур. Структуры следует использовать везде, где есть связанные поля данных. Однако, обратите внимание, что структуры не нельзя будет расширить в будущем, поэтому всегда учитывайте расширяемость.
Например, вместо нескольких массивов с одинаковым индексом, содержащих разные свойства одного и того же набора элементов:
<!--
AddContacts:
@names: Массив имен контактов для добавления.
@emails: Соответствующий массив электронных адресов контактов.
@ids: Возвращаемый массив с ID новых контактов. Он будет той же
длины что и @names.
Добавляет в адресную книгу ноль и больше контактов, используя
их имена и электронные адреса. Аргументы @names и @emails
должны иметь одинаковую длину.
-->
<method name="AddContacts">
<arg name="names" type="as" direction="in" />
<arg name="emails" type="as" direction="in" />
<arg name="ids" type="au" direction="out" />
</method>
массивы можно объединить в единый массив структур:
<!--
AddContacts:
@details: Массив (имя контакта, электронный адрес контакта) для добавления.
@ids: Возвращаемый массив идентификаторов новых контактов.
Он будет той же длины, что и @details.
Добавляет в адресную книгу ноль или более контактов, используя
их имена и электронные адреса.
-->
<method name="AddContacts">
<arg name="details" type="a(ss)" direction="in" />
<arg name="ids" type="au" direction="out" />
</method>
Обратите внимание, что массивы D-Bus автоматически передаются с указанием своей длины, поэтому нет необходимости завершать их нулем или отдельно кодировать их длину.
Расширяемость
Некоторые API-интерфейсы D-Bus имеют четко определенные сценарии использования и никогда не нуждаются в расширении. Другие используются в более слабосвязанных системах, которые могут меняться со временем, и, следовательно, должны с самого начала должны быть спроектированы так, чтобы оставаться расширяемыми без необходимости изменения API в будущем. Это компромисс между наличием более сложного API и возможностью его легко расширять в будущем.
Ключевым инструментом расширяемости в D-Bus является a{sv}, словарь, сопоставляющий варианты строкам. Если в качестве ключей словаря используются четко определенные строки с пространством имен, произвольные узлы D‑Bus могут добавлять в словарь любую информацию, которая им нужна. Любой другой клиент, который это понимает, может запрашивать и извлекать информацию; другие же узлы это проигнорируют.
Каноническим примером расширяемого API, использующего a{sv}, является проект Telepathy. Он использует значения a{sv} в качестве последнего элемента в структурах, чтобы позволить их расширять в будущем.
Дополнительным инструментом является использование в вызовах методов полей флагов. Набор принятых флагов полностью находится под контролем разработчика интерфейса и, как в случае с перечисляемыми типами, может быть расширен в будущем без нарушения API. Добавление новых флагов позволяет настроить функциональность вызова метода.
Использование сигналов, свойств и ошибок
Очевидно, что из-за задержки, присущей IPC, вызов методов D-Bus асинхронен. Это означает, что одноранговые узлы не должны блокироваться до получения ответа от вызываемого метода; они должны запланировать другую работу (в основном цикле) и обработать ответ, когда он будет получен. Несмотря на то, что ответы метода могут занять некоторое время, вызывающая сторона в конечном итоге гарантированно получит ровно один ответ. Этот ответ может быть значением, возвращаемым методом, ошибкой метода или ошибкой демона D‑Bus, указывающей на то, что сервис каким-либо образом не сработал (например, из-за сбоя).
Из-за этого реализации сервисов на каждый вызов метода должны всегда отвечать ровно один раз. Ответ в конце длительной операции является правильным – клиент будет терпеливо ждать, пока операция не завершится и не будет получен ответ.
Обратите внимание, что биндинги клиентов D‑Bus могут реализовывать синтетические тайм‑ауты в несколько десятков секунд, если они явно не запрещают вызов. Для очень длительных операций следует отключить тайм-аут в биндинге клиента и указать в пользовательском интерфейсе клиента, что приложение не зависло, а просто выполняет длительную операцию.
Антишаблон, которого следует избегать в такой ситуации, состоит в том, чтобы начать длительную операцию при получении вызова метода, и затем, вместо ответа на вызов метода, уведомить клиента о завершении операции с помощью сигнала. Этот подход неверен, поскольку излучение сигналов не имеет однозначной связи с вызовами методов - сигнал теоретически может быть излучен несколько раз или никогда, и клиент его не сможет обработать.
Точно так же используйте ответ об ошибке D-Bus, чтобы обозначить сбой операции, инициированной вызовом метода, вместо использования пользовательского кода ошибки в ответе метода. Это означает, что ответ всегда указывает на успех, а ошибка всегда указывает на сбой – а не ответ, либо зависящий от его параметров, либо с необходимостью возвращать фиктивные значения в других параметрах. Использование ответов об ошибках D-Bus также означает, что такие сбои могут быть выявлены инструментами отладки, это упрощает отладку.
Клиенты должны обрабатывать все возможные стандартные и задокументированные ошибки D-Bus для каждого вызова метода. IPC по своей сути имеет больше потенциальных сбоев, чем обычные вызовы функций Си, и клиенты должны быть готовы правильно их обработать.
Использование стандартных интерфейсов
Используйте стандартные интерфейсы D-Bus где только можно.
Properties (Свойства)
Спецификация D-Bus определяет интерфейс org.freedesktop.DBus.Properties, который должен использоваться всеми объектами для уведомления клиентов об изменениях значений их свойств с помощью сигнала PropertiesChanged. Этот сигнал устраняет необходимость в отдельных сигналах
PropertyNameChanged и позволяет за одно излучение сигнала уведомлять о нескольких свойствах, сокращая количество циклов IPC. Точно так же методы Get и Set могут использоваться для управления свойствами объекта, устраняя избыточные методы GetPropertyName и SetPropertyName.
Например, рассмотрим объект, реализующий интерфейс
com.example.MyService1.SomeInterface с методами:
GetName() → (s)
SetName(s) → ()
GetStatus() → (u)
RunOperation(ss) → (u)
и сигналами:
NameChanged(u)
StatusChanged(u)
Интерфейс можно было сократить до одного метода:
RunOperation(ss) → (u)
Затем объект может реализовать интерфейс org.freedesktop.DBus.Properties и определить свойства:
Name с типом s, запись–чтение
Status с типом u, только чтение
Сигналы NameChanged и StatusChanged могли бы быть заменены на org.freedesktop.DBus.Properties.PropertiesChanged.
Object Manager (Менеджер объектов)
В спецификации также определяется интерфейс
org.freedesktop.DBus.ObjectManager, который следует использовать всякий раз, когда сервису необходимо предоставить переменное количество объектов одного и того же класса в плоской или древовидной структуре, и ожидается, что клиенты будут заинтересованы в большинстве или всех объектах. Например, это может использоваться сервисом адресной книги, которая предоставляет несколько адресных книг, каждая из которых является отдельным объектом. Метод GetManagedObjects позволяет запрашивать полное дерево объектов, возвращая также все свойства объектов, устраняя необходимость в дальнейших циклах IPC для запроса свойств.
Если предполагается, что клиентам не будет интересно получать все объекты, то ObjectManager не следует использовать, поскольку он в любом случае отправит все объекты каждому клиенту, тратя впустую полосу пропускания шины. Следовательно, файловый менеджер не должен использовать ObjectManager для получения всей иерархии файловой системы.
Например, рассмотрим объект, реализующий интерфейс
com.example.MyService1.AddressBookManager с методами:
GetAddressBooks() → (ао)
и сигналы:
AddressBookAdded(o)
AddressBookRemoved(o)
Если объект менеджера находится по пути
/com/example/MyService1/AddressBookManager, каждая адресная книга является дочерним объектом, например /com/example/MyService1/AddressBookManager/SomeAddressBook.
Интерфейс можно было бы исключить, а вместо этого реализовать интерфейс org.freedesktop.DBus.ObjectManager в объекте менеджера.
Вызовы GetAddressBooks станут вызовами GetManagedObjects, а излучение сигналов AddressBookAdded и AddressBookRemoved станет излучением InterfacesAdded и InterfacesRemoved.
Соглашения об именах
Все имена D‑Bus, от имен сервисов до параметров методов, соответствуют набору соглашений. Соблюдение этих соглашений делает использование API более естественным и совместимым со всеми другими сервисами в системе.
Сервисы используют обратную запись доменного имени, основанную на доменном имени проекта, предоставляющего сервису (все в нижнем регистре), плюс уникальное имя сервиса (в верблюжьем регистре).
Например, версия 2 приложения адресной книги под названием ContactDex, предоставляемая поставщиком программного обеспечения, чей веб-сайт имеет адрес chocolateteapot.com, будет использовать имя сервиса com.chocolateteapot.ContactDex2.
Почти во всех именах используется верблюжий регистр без подчеркивания, как в примерах ниже. Исключение составляют параметры метода и сигнала, в которых используется нижний_регистр_с_подчеркиваниями. Информация о типе никогда не кодируется в имени параметра (т.е. это не венгерская нотация).
Имя сервиса
com.example.MyService1
Имя интерфейса
com.example.MyService1.SomeInterface
Объектный путь (корневой объект)
/com/example/MyService1
Объектный путь (дочерний объект)
/com/example/MyService1/SomeChild
Объектный путь (внучатый объект)
/com/example/MyService1/AnotherChild/Grandchild1
Имя метода
com.example.MyService1.SomeInterface.MethodName
Имя сигнала
com.example.MyService1.SomeInterface.SignalName
Имя свойства
com.example.MyService1.SomeInterface.PropertyName
Генерация кода
Вместо того, чтобы вручную реализовывать как серверную, так и клиентскую стороны интерфейса D‑Bus, часто проще написать XML‑описание интерфейса и для создания типобезопасных API‑интерфейсов Си воспользоваться таким инструментом, как gdbus‑codegen, а затем используя их построить реализацию. Это позволяет избежать утомительного и подверженного ошибкам процесса написания кода для создания и чтения вариантов параметров D‑Bus для каждого вызова метода.
Использование генераторов кода выходит за рамки этого руководства; для получения дополнительной информации см. руководство по gdbus‑codegen.
Аннотации
Аннотации могут быть добавлены к XML‑файл интерфейса для предоставления метаданных в API, которые могут использоваться документацией или инструментами генерации кода для изменения их вывода. Некоторые стандартные аннотации приведены в спецификации D‑Bus, но другие аннотации могут быть определены в специальных инструментах.
Например, gdbus‑codegen определяет несколько полезных аннотаций, перечисленных на странице его руководства.
Следующие аннотации являются наиболее полезными:
org.freedesktop.DBus.Deprecated
Помечает символ как устаревший. Её следует использовать всякий раз, когда API изменяется, указывая версию, вводящую в статус устаревшего, причину устаревания и символ замены.
org.gtk.GDBus.Since
Помечает символ как добавленный в API после первоначального релиза. Она должно включать версию, в которую символ был впервые добавлен.
org.gtk.GDBus.C.Name и org.freedesktop.DBus.GLib.CSymbol
Обе используются взаимозаменяемо, для указания на имя функции Си, которое будет использоваться при генерации кода для символа. Использование этой аннотации может сделать сгенерированные биндинги намного удобнее в использовании.
org.freedesktop.DBus.Property.EmitsChangedSignal
Указывает, будет ли свойство излучать сигналы об изменении. Это может повлиять на генерацию кода, но также является полезной документацией, поскольку клиентские программы тогда узнают, когда ожидать уведомлений об изменении свойств, а когда их нужно запросить.
Документация
Также, как и Си API, API D‑Bus должен быть задокументирован. Существует несколько методов документирования интерфейсов, методов, свойств и сигналов в XML-файле интерфейса D-Bus, у каждого из которых, к сожалению, есть свои недостатки. Вам следует выбрать метод, который лучше всего соответствует используемым инструментам и рабочему процессу.
Комментарии XML
Комментарии XML, содержащие документацию в формате gtk‑doc, являются рекомендуемым форматом для использования с gdbus‑codegen. Используя gdbus‑codegen, эти комментарии могут быть извлечены, преобразованы в формат DocBook и включены в руководство к API проекта. Например:
Документация в XML комментариях интерфейса D-Bus
Примеры комментариев документации в стиле gtk‑doc в XML-коде интроспекции для интерфейса org.freedesktop.DBus.Properties.
<!--
org.freedesktop.DBus.Properties:
@short_description: Standard property getter/setter interface
Интерфейс для всех объектов, которые предоставляют свойства на шине,
позволяет чтение, установку и отправку сигналов уведомления о
значениях свойств.
-->
<interface name="org.freedesktop.DBus.Properties">
<!--
Get:
@interface_name: Имя интерфейса в котором определено свойство.
@property_name: Имя свойства которое требуется прочитать.
@value: Значение свойства упакованное в вариант.
Запрашивает значение свойства @property_name из интерфейса
@interface_name этого объекта. Если в @interface_name пустая строка,
свойство @property_name будет разыскиваться на всех интерфейсах,
если будут найдены несколько свойств результат неопределен.
Если @interface_name или @property_name не существуют, будет
возвращена ошибка #org.freedesktop.DBus.Error.InvalidArgs
-->
<method name="Get">
<arg type="s" name="interface_name" direction="in"/>
<arg type="s" name="property_name" direction="in"/>
<arg type="v" name="value" direction="out"/>
</method>
<!--
PropertiesChanged:
@interface_name: Имя интерфейса свойство которого изменилось.
@changed_properties: Словарь с названиями свойств и их
обновленными значениями.
@invalidated_properties: Список имен других изменившихся свойств,
но чьи изменения не нотифицируются.
Излучается когда одно или более свойств интерфейса @interface_name
меняют значения. Свойства могут быть перечислены в
@changed_properties или @invalidated_properties в зависимости
от желания сервиса транслировать новые значения свойств или ожидать
запросы клиентов. Если значение большое или используется нечасто,
сервис возможно не захочет рассылать его широковещательно и будет
ожидать вместо этого запросы от клиентов.
-->
<signal name="PropertiesChanged">
<arg type="s" name="interface_name"/>
<arg type="a{sv}" name="changed_properties"/>
<arg type="as" name="invalidated_properties"/>
</signal>
</interface>
Аннотации XML
Документация также может быть добавлена в XML в виде элементов аннотации. Этот формат тоже поддерживается gdbus‑codegen, но комментарии gtk‑doc предпочтительны. Например:
Аннотации документации в XML интерфейса D-Bus
Пример аннотаций документации GDBus в XML интроспекции для интерфейса org.freedesktop.DBus.Properties.
<interface name="org.freedesktop.DBus.Properties">
<annotation name="org.gtk.GDBus.DocString" value="Интерфейс для
всех объектов, которые выставляют свойства на шину,
позволяющий свойствам быть полученными, установленными,
а сигналам для уведомления об изменениях значений свойства
быть излученнымии."/>
<annotation name="org.gtk.GDBus.DocString.Short"
value="Standard property getter/setter interface"/>
<method name="Get">
<annotation name="org.gtk.GDBus.DocString" value="Запрашивает
значение свойства @property_name на @interface_name
данного объекта. Если в @interface_name пустая строка,
то свойство @property_name будет разыскиваться во
всех интерфейсах. Если свойство будет обнаружено
в нескольких местах то результат не определен.
If @interface_name or @property_name do not exist, a
#org.freedesktop.DBus.Error.InvalidArgs error is returned."/>
<arg type="s" name="interface_name" direction="in">
<annotation name="org.gtk.GDBus.DocString"
value="Name of the interface the property is defined on."/>
</arg>
<arg type="s" name="property_name" direction="in">
<annotation name="org.gtk.GDBus.DocString"
value="Name of the property to get."/>
</arg>
<arg type="v" name="value" direction="out">
<annotation name="org.gtk.GDBus.DocString"
value="Property value, wrapped in a variant."/>
</arg>
</method>
<signal name="PropertiesChanged">
<annotation name="org.gtk.GDBus.DocString" value="Испускается,
когда одно или несколько свойств изменяют значения
@interface_name. Свойство может быть указано в
@changed_properties или @invalidated_properties в
зависимости от того, хочет ли сервис транслировать
новое значение свойства. Если значение большое или
редко используется, служба может не захотеть его
передавать широковещательно и вместо этого будет
ждать, пока клиенты его запросят."/>
<arg type="s" name="interface_name">
<annotation name="org.gtk.GDBus.DocString"
value="Имя интерфейса чьи свойства изменились."/>
</arg>
<arg type="a{sv}" name="changed_properties">
<annotation name="org.gtk.GDBus.DocString"
value="Словарь имен свойств, которые обновились и их
новые значения."/>
</arg>
<arg type="as" name="invalidated_properties">
<annotation name="org.gtk.GDBus.DocString"
value="Список названий других свойств, которые
изменились, но без нотификации."/>
</arg>
</signal>
</interface>
Файлы сервисов
Каждый сервис D‑Bus должен инсталлировать файл .service с описанием его имени и команды для запуска сервиса. Это позволяет активировать сервис (см. Спецификацию D‑Bus Спецификацию D-Bus (Русcкий перевод)).
Файлы сервисов должны быть названы в соответствии с общеизвестным именем сервиса, например файл com.example.MyService1.service для общеизвестного имени com.example.MyService1. Файлы должны быть установлены в $(datadir)/dbus-1/services для системной шины. Однако, обратите внимание, что сервисы на системной шине почти всегда также нуждаются в политике безопасности.
Политики безопасности
Модель безопасности D‑Bus на верхнем уровне:
Имеется системная шина и ноль или более сессионных шин.
К системной шине может подключаться любой процесс. Системная шина ограничивает, кто может владеть именами или отправлять вызовы методов, и только процессы, запущенные от привилегированных пользователей, могут получать одноадресные сообщения, адресованные не им. Каждый процесс может получать широковещательные сообщения.
У каждой сеансовой шины есть владелец (пользователь). Подключиться может только её владелец; в Linux общего назначения сессионная шина не рассматривается как граница привилегий, поэтому дальнейшего разделения привилегий между процессами на ней нет.
Полный охват сервисов D-Bus выходит за рамки этого руководства, однако, есть некоторые шаги, которые вы можете предпринять при разработке API, чтобы упростить реализацию политики безопасности.
Политики безопасности D-Bus записываются в виде XML‑файлов в каталогах $(datadir)/dbus-1/system.d, $(datadir)/dbus-1/session.d, $(sysconfdir)/dbus-
1/system.d и $(sysconfdir)/dbus-1/session.d и используйте модель разрешения/запрета, в которой каждое сообщение (вызов метода, передача сигнала и т.д.) может быть разрешено или отклонено в соответствии с суммой всех соответствующих ему правил политики. В политике каждое правило <allow> или <deny> должно иметь собственный атрибут send_destination или receive_sender.
При разработке API помните о необходимости написания и установки такой политики безопасности и рассмотрите возможность разделения методов или предоставления более ограниченных версий, которые принимают ограниченные параметры, чтобы они могли быть представлены с менее строгими политиками безопасности, если это необходимо для менее доверенных клиентов. Начиная с dbus‑daemon 1.10, политики безопасности следует устанавливать в $(datadir), а не в (файл ($ (sysconfdir)); последний предназначен для системных администраторов.
Во-вторых, политика безопасности D‑Bus по умолчанию для системной шины является достаточно строгой, чтобы позволить безопасно пересылать по шине в одноадресных сообщениях (включая одноадресные сигналы) конфиденциальные данные, такие как пароли; так что нет необходимости усложнять API за счет дополнительной безопасности. Однако, обратите внимание, что конфиденциальные данные не должны отправляться в широковещательных сигналах, так как они могут быть видны всем одноранговым узлам на шине. Для сессионной шины политика по умолчанию не является ограничительной, но обычно не является границей безопасности.
Отладка
Отладка сервисов, работающих на D‑Bus, может быть сложной задачей, особенно если они запускаются через активацию сервисы и, следовательно, в неподконтрольной вам среде.
Доступны три основных инструмента: D‑Bus Monitor, Bustle и D‑Feet.
Монитор D-Bus
dbus‑monitor – это основной инструмент D-Bus, который позволяет подслушивать сессионную или системную шину, распечатывая все сообщения, которые он видит. Чтобы сделать поток сообщений более управляемым, они могут быть отфильтрованы с использованием стандартного правила сопоставления D-Bus.
Предыдущие версии D-Bus требовали, чтобы политика безопасности для системной шины была ослаблена вручную, чтобы разрешить перехват всех сообщений. Это означало, что отладка была сложной и небезопасной. В последних версиях D‑Bus добавлена поддержка подключений только для монитора с пользователем root, что означает, что dbus‑monitor можно запускать как root, чтобы безболезненно отслеживать все сообщения на системной шине без изменения политики безопасности.
Bustle
Bustle – это графический вариант программы dbus‑monitor с пользовательским интерфейсом, ориентированным на профилирование производительности D-Bus путем нанесения сообщений на временную шкалу. Он идеально подходит для поиска узких мест в производительности IPC между сервисом и клиентом.
D‑Feet
D‑Feet – это инструмент интроспекции для D-Bus, который графически отображает все, доступные для просмотра, одноранговые узлы на шине с их объектами, интерфейсами, методами, сигналами и свойствами. Это полезно для отладки всевозможных проблем, связанных с присутствием сервисов на шине и объектами, которые они предоставляют.
pdf-версию этой статьи вы можете скачать по этому линку (Google диск).