На этой странице

Двоичный интерфейс приложения (Application Binary Interface, ABI) — это способ, которым программы вызывают функции и используют структуры данных из других скомпилированных программ. Это скомпилированная версия интерфейса программирования приложений (Application Programming Interface, API). Иными словами, заголовочные файлы, описывающие классы, функции, структуры данных, перечисления и константы, которые позволяют приложению выполнить нужную задачу, через компиляцию соответствуют набору адресов, ожидаемых значений параметров, а также размеров и раскладок структур памяти, с которыми был скомпилирован поставщик ABI.

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

Поскольку поставщик ABI и пользователь ABI могут быть скомпилированы в разное время разными версиями компилятора, часть ответственности за обеспечение совместимости ABI лежит на компиляторе. Разные версии компилятора, возможно от разных вендоров, должны все производить один и тот же ABI из заголовочного файла с определённым содержимым и должны производить код для приложения, использующего ABI, который обращается к описанному API в заданном заголовке согласно соглашениям ABI, вытекающего из описания в заголовке. У современных компиляторов довольно хороший послужной список в плане того, чтобы не ломать совместимость ABI приложений, которые они компилируют.

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

Node.js предоставляет заголовочные файлы, сопровождаемые несколькими независимыми командами. Например, заголовочные файлы вроде node.h и node_buffer.h сопровождаются командой Node.js. v8.h сопровождается командой V8, которая, хотя и в тесном сотрудничестве с командой Node.js, независима и имеет собственное расписание и приоритеты. Таким образом, команда Node.js лишь частично контролирует изменения, вносимые в предоставляемые проектом заголовки. В результате проект Node.js принял семантическое версионирование. Это гарантирует, что API, предоставляемые проектом, дадут стабильный ABI для всех минорных и патч-версий Node.js, выпущенных в рамках одной мажорной версии. На практике это означает, что проект Node.js взял на себя обязательство гарантировать, что нативный аддон Node.js, скомпилированный под заданную мажорную версию Node.js, успешно загрузится при загрузке любой минорной или патч-версией Node.js в рамках той мажорной версии, под которую он был скомпилирован.

Возникла потребность оснастить Node.js API, дающим ABI, который остаётся стабильным на протяжении нескольких мажорных версий Node.js. Мотивация для создания такого API следующая:

  • Язык JavaScript оставался совместимым сам с собой с самых ранних дней, тогда как ABI движка, выполняющего JavaScript-код, меняется с каждой мажорной версией Node.js. Это значит, что приложения, состоящие из пакетов Node.js, написанных целиком на JavaScript, не нужно перекомпилировать, переустанавливать или передеплоивать, когда новая мажорная версия Node.js внедряется в production-окружение, в котором работают такие приложения. Напротив, если приложение зависит от пакета, содержащего нативный аддон, приложение приходится перекомпилировать, переустанавливать и передеплоивать всякий раз, когда новая мажорная версия Node.js внедряется в production-окружение. Это несоответствие между пакетами Node.js, содержащими нативные аддоны, и теми, что написаны целиком на JavaScript, добавило бремени сопровождения production-систем, которые полагаются на нативные аддоны.

  • Другие проекты начали производить JavaScript-интерфейсы, которые по сути являются альтернативными реализациями Node.js. Поскольку эти проекты обычно построены на другом JavaScript-движке, а не на V8, их нативные аддоны неизбежно принимают иную структуру и используют иной API. Тем не менее использование единого API для нативного аддона в разных реализациях JavaScript-API Node.js позволило бы этим проектам воспользоваться экосистемой JavaScript-пакетов, накопившейся вокруг Node.js.

  • В будущем Node.js может содержать другой JavaScript-движок. Это значит, что внешне все интерфейсы Node.js остались бы теми же, но заголовочный файл V8 отсутствовал бы. Такой шаг вызвал бы сбой в экосистеме Node.js в целом и нативных аддонов в частности, если бы API, не зависящий от JavaScript-движка, не был сначала предоставлен Node.js и принят нативными аддонами.

С этими целями Node.js внедрил N-API в версии 8.6.0 и пометил его как стабильный компонент проекта начиная с Node.js 8.12.0. API определён в заголовках node_api.h и node_api_types.h и предоставляет гарантию прямой совместимости (forward-compatibility), пересекающую границу мажорной версии Node.js. Эту гарантию можно сформулировать так:

Заданная версия n N-API будет доступна в той мажорной версии Node.js, в которой она была опубликована, и во всех последующих версиях Node.js, включая последующие мажорные версии.

Автор нативного аддона может воспользоваться гарантией прямой совместимости N-API, обеспечив, чтобы аддон использовал только API, определённые в node_api.h, а также структуры данных и константы, определённые в node_api_types.h. Делая так, автор облегчает принятие своего аддона, показывая production-пользователям, что бремя сопровождения их приложения возрастёт от добавления нативного аддона в проект не больше, чем оно возросло бы от добавления пакета, написанного чисто на JavaScript.

N-API версионируется, потому что новые API время от времени добавляются. В отличие от семантического версионирования, версионирование N-API кумулятивно. То есть каждая версия N-API несёт тот же смысл, что и минорная версия в системе semver, а значит, все изменения в N-API будут обратно совместимы. Кроме того, новые N-API добавляются под экспериментальным флагом, чтобы дать сообществу возможность проверить их в production-окружении. Экспериментальный статус означает, что, хотя приняты меры для гарантии того, что новый API не придётся модифицировать ABI-несовместимым образом в будущем, он ещё не достаточно проверен в production на предмет корректности и полезности в задуманном виде и, как следствие, может претерпеть ABI-несовместимые изменения, прежде чем будет окончательно включён в будущую версию N-API. То есть экспериментальный N-API пока не покрывается гарантией прямой совместимости.