
Привет, Хабр. Меня зовут Владислав Пурьев. Я занимаюсь разработкой экосистемных и комплексных сервисов в VK. Расскажу в двух статьях о языке определения интерфейсов (Interface Definition Language, IDL) и платформенном механизме, который построен поверх этой идеи в Android — Android Interface Definition Language (AIDL).
Начну рассказ с IDL — что это за язык и какую задачу он решает. Здесь будет только теоретическая информация. Примеры кода, внутренности Android и важные технические детали будут во второй статье.
Что такое IDL
Язык определения интерфейсов (Interface Definition Language, IDL) описывает интерфейс между клиентским и серверным процессами в распределённой системе (источник).
Для распределённых систем важно, чтобы приложения могли быть разработаны с помощью разных языков программирования и технологий. И чтобы эти приложения могли располагаться в разных адресных пространствах, например на виртуальных машинах.
В любом случае у пользователя должен быть доступ к программам и ресурсам распределённой системы, а приложения должны уметь взаимодействовать друг с другом каким‑то удобным и универсальным способом. Для этого в 1 981 году информатик Брюс Джей Нельсон (Bruce Jay Nelson) описал концепцию удалённого вызова процедур (Remote Procedure Call, или RPC) как синхронную передачу управления на уровне языка программирования между программами через узкий канал (narrow channel).
Что хорошего в данной концепции? То, что клиентская сторона никак не заботится о деталях реализации серверной стороны. А что такое узкий канал? Это канал, который соединяет клиентскую и серверную стороны. Одно из главных преимуществ RPC в том, что реализация канала никак не влияет на взаимодействующие стороны. Клиент просто вызывает функции серверной программы, которая возвращает значение или не возвращает ничего.
Это похоже на заказ еды по телефону. Вы звоните и не думаете о специальных технологиях, которые позволяют вам передавать информацию и вас совершенно не волнует, как заказ обрабатывается. Вам не нужно знать, что происходит на каждом шаге.
RPC — это подход к взаимодействию между приложениями. А IDL часто используют вместе с RPC: на IDL фиксируют контракт (операции и типы), а инструменты вокруг него генерируют код для обмена данными между сторонами, о чём расскажу дальше.
Использование IDL на стороне клиента

При использовании IDL на клиенте получается стандартный для языков программирования интерфейс: клиентский код вызывает методы, а детали реализации этого обращения скрыты внутри, поэтому всё работает почти как локальный вызов. Подход обеспечивает способ взаимодействия между программами таким образом, чтобы нелокальные вызовы процедур вели себя так же, как и локальные (источник). Использование интерфейса вместо реализаций — важная часть этого решения.
Предположим, программа, написанная на Kotlin, передаёт значение типа Int в вызов интерфейса серверной программы, которая реализована на C++, и получает строку в ответ. Обе стороны работают только с типами своего языка (строка в Kotlin не то же самое, что строка в C++). На IDL можно описать только контракт — какие вызовы есть и какие у них типы, но не то, как один тип переводится в другой.
Генерация кода
IDL нужен для описания интерфейсов. Чтобы этим описанием можно было воспользоваться, нужны ещё компиляторы — они генерируют код, который и выполняет всю работу.
К примеру, у нас есть клиентское приложение. Требуется использовать некоторую функцию от другой программы, то есть выполнить RPC. Об этой программе известно только одно — она реализует некоторый интерфейс, который доступен клиенту. Значит, в коде программы возможно только вызвать функцию, передать параметры и получить результат. Также можно получить исключение.
И теперь компиляторы начинают работать. Вызов функции не адресован напрямую другому приложению. Выполняется вызов функции класса, который сгенерирован компилятором IDL и реализует интерфейс, описанный на IDL. Но данный класс тоже не выполняет вызов внешнего приложения. Этим методом в сгенерированном классе просто передаются данные входных параметров в узкий канал.
Далее точно такие же процессы выполняются на другой стороне. Внешняя программа получает вызов, выполняет работу и возвращает значение. Но возвращает его не напрямую вызывающему. Вместо этого класс, сгенерированный IDL для серверной стороны, получает результат от серверной программы — и его значение передаётся в канал. На другой стороне сгенерированный код клиента получает возвращаемое значение из этого канала. И только после этого клиентская программа получает результат.

Но как использовать типы, специфические для конкретного языка программирования? Решение реализовано инструментами генерации — они используют подход, который называется «Заглушки уровня исходного кода (source‑level stubs)». Основная идея в том, что код, который выполняет передачу процедур между программами, сгенерирован компилятором IDL для обеих сторон — как для клиентской, так и для серверной — на том языке, который им требуется.
Значит, каждой стороне можно использовать собственные заглушки (stub routines). То есть клиентский вызов выполняется к заглушке сервера, а возврат от серверной стороны выполняется в клиентскую заглушку. Только заглушки взаимодействуют с конкретными приложениями напрямую. Таким образом, оба компонента инкапсулируют тот факт, что вызов процедуры (функции/метода) не является локальным.

Что хорошего в данном подходе? То, что бизнес‑логика по обе стороны может оставаться обычным кодом, а транспортные детали в основном реализованы в сгенерированных заглушках. Всю работу по генерации заглушек делают компиляторы, а разработчик может сфокусироваться на интерфейсах, описанных на IDL.
Связь
Как выполняется вызов между процессами, если приложения находятся на разных виртуальных машинах? Здесь работает «узкий канал».
Есть практическое требование RPC: аргументы и результаты нужно преобразовать в представление, которое можно передать в канал. Логично, что сеть, например, не может передать аргументы типа string от любого языка программирования. Поэтому входные параметры и возвращаемое значение должны быть преобразованы в данные, которые канал может принять.
Такое преобразование входных параметров и результата в форму, пригодную для передачи по каналу, называется маршалинг (marshalling) — это процесс упаковки данных одной программы в сообщение, которое удобно для отправки в программу, находящуюся в другом адресном пространстве.
После преобразования данных в одну форму и их передачи по каналу необходимо выполнить обратное преобразование на другой стороне, чтобы извлечь нужные аргументы из сообщения. Обратный процесс называется демаршалингом (unmarshalling).
В канал передаётся не сам объект конкретного языка, а его сериализованное представление, что помогает передавать значения независимо от языка программирования.
Заключение
В итоге стандартная работа с использованием IDL состоит из следующих шагов:
Клиентская программа вызывает метод интерфейса (IDL).
Внутри вызывается метод клиентской заглушки.
Клиентская заглушка выполняет маршалинг данных, переданных в качестве параметров в метод, и передаёт их в канал.
Данные из канала поступают серверной заглушке.
Серверная заглушка извлекает данные и выполняет их демаршалинг.
Серверная заглушка вызывает метод серверного приложения (удалённая процедура).
Метод сервера вычисляет результат, выполняет возврат значения или выбрасывает исключение, получаемые серверной заглушкой. Обращаю внимание, что void считается нормальным возвратом.
Серверная заглушка выполняет маршалинг результата и передаёт его в канал.
Клиентская заглушка получает сообщение от канала, извлекает данные и выполняет их демаршалинг.
Клиентская заглушка передаёт результат в клиентское приложение.
Статья описывает только общие принципы RPC и IDL, на которых основан инструмент в системе Android — AIDL. Технические подробности о нём будут во второй части, которую я скоро опубликую.