Ориентация главы
XRT — не «ещё одна библиотека утилит», а систематическая кроссплатформенная C-инфраструктура: от памяти, ошибок, контейнеров и корутин до сетевого движка с пятью бэкендами, собственного TLS и ядра протоколов HTTP/WebSocket. Первый урок при изучении XRT — не зубрёжка API, а понимание двухслойной структуры «ядро + расширения»: именно она определяет, на каком слое стоит каждая ваша строка. Глава сначала строит общую картину, затем завершает всю подготовку от нуля до компилируемости: получить исходники, изучить каталоги, понять два способа интеграции, скомпилировать первую программу и взглянуть на мощь обрезки возможностей. Прочитав главу, вы получите среду разработки, готовую к свободным экспериментам.
2.1引入
Предположим, вам нужно написать на C небольшой сервис, читающий файл конфигурации и пересылающий данные. По пути разрозненных библиотек вам понадобятся: библиотека разбора JSON, HTTP-клиент, библиотека TLS, пул потоков, assorted контейнеры… Они от разных авторов, со своими стилями обработки ошибок (коды возврата, errno, setjmp, abort), своими соглашениями о памяти (кто выделил, тот освобождает; подсчёт ссылок; GC-макросы) и своими системами сборки (CMake, Meson, самописные Makefile). Время на «сшивку» быстро превышает саму бизнес-логику, а любое обновление безопасности апстрима заставляет переоценивать всю цепочку зависимостей.
Ответ XRT — сделать эти возможности единым технологическим стеком: единая модель ошибок (глава 4), единые соглашения о памяти и владении (глава 5), единое соглашение о данных-представлениях (глава 3), пронизывающие всё снизу вверх до сетевого и протокольного уровней. Понимание этой унификации важнее запоминания любого API — именно эту панораму и строит глава.
Есть и скрытая цена, которую стоит назвать: пути обновления разрозненных зависимостей существуют каждый сам по себе — уязвимость в библиотеке TLS требует отдельной оценки, смена лицензии библиотеки JSON — отдельной миграции, покрытие тестами везде разное. Единый стек сводит всё к одному решению: подняли номер версии — и патчи безопасности со регрессионными тестами всего стека (в репозитории тысячи тестовых файлов) приходят вместе. Глава 8 расскажет о соглашениях версий и совместимости; том 12 — об управлении этим путём обновления в инженерии.
2.2概念
Ядро и расширения: строгая двухслойная структура
- Ядро (
src/+include/xrt/, 91 публичный заголовок, сотни обрезаемых модулей): фундамент (core / error / atomic / memory), контейнеры, текст и данные, логирование и файлы/процессы, корутины и асинхронность, сетевой движок с пятью бэкендами (IOCP / epoll / kqueue / io_uring / select), собственный TLS 1.2/1.3 с X.509 и ядро протоколов HTTP/1.x и WebSocket. - Библиотеки расширений (
extlibs/, пять независимых продуктов):xhttp(рантаймы HTTP-клиента/сервера),xws(рантайм WebSocket),xruntime(динамические типы и объектная система),xmail(полный набор почтовых протоколов),xssh(стек протокола SSH2).
Зависимости строго односторонние: расширения вызывают только ядро; ядро не ссылается на расширения ни разу. Тома 1–9 этой книги посвящены ядру; тома 10 и 11 — расширениям. Прямая выгода для пользователя: «возможности ядра самодостаточны» — напишите небольшой инструмент разбора конфигурации только на ядре, вообще не замечая расширений; а любая функция расширений, прослеженная до дна, — это уже изученные вами примитивы ядра. Порядок обучения складывается сам собой: после томов 1–9 документацию расширений вы читаете почти напрямую.
Одна легко путающаяся точка: ядро хранит ядро протоколов HTTP и WebSocket (разбор, кодирование, семантическая проверка), а высокоуровневые рантаймы — «клиент», «сервер», «маршрутизация», «пул соединений» — живут в расширениях xhttp / xws. Увидели xrtHttp1RequestParse — это ядро; увидели клиентские API xhttp — это расширение: разные стили префиксов, разные продукты.
Обзор структуры репозитория
После получения исходников в повседневной разработке вы чаще всего касаетесь лишь пяти каталогов:
| Каталог | Содержимое | Роль в книге |
|---|---|---|
include/xrt/ | 91 публичный заголовок ядра; экспортируемые функции несут XRT_API и комментарии-контракты | «Первоисточник» каждой главы |
single/ | Однозаголовочные артефакты xrt.h / xrt_decl.h (самодостаточная реализация всего ядра) | База компиляции всех примеров книги |
src/ | Реализация ядра и приватные контрактные заголовки src/internal/ | «Ключ ответов» при погружении в детали |
extlibs/ | Пять расширений, каждое со своими src / include / tests / examples / single | Учебники томов 10 и 11 |
examples/ | Запускаемые примеры, организованные по модулям | «Внешнее чтение» к каждой главе |
Остальные каталоги: tools/ — цепочка инструментов сборки; tests/ — набор тестов (тому 12 посвящено их чтение); config/modules.json — манифест модулей, единый источник истины всей библиотеки; dev/ хранит бенчмарки и архивы.
Два способа интеграции
От исходников до исполняемого файла однозаголовочный путь — четыре шага:
На инженерном этапе можно перейти на второй путь: инструментами сборки получить статическую или динамическую библиотеку либо включить исходники ядра в свою систему сборки с путями заголовков, единообразно <xrt/xxx.h>. Этот путь разворачивается в томе 12. Примеры книги единообразно идут однозаголовочным путём: его достоинство — «скопировал один файл и начал», а поведение обрезки видно как на ладони.
2.3示例
Первая полная программа
Вот первая полная программа книги. Она собирает фразу через xrtFormat (форматирование по правилам printf, возвращает управляемую XRT-строку), а по завершении вызывает xrtFree — эти два вызова уже задействуют единый сток памяти ядра:
/* hello.c —— первая программа книги */ #define XRT_MODULE_ALL #define XRT_IMPLEMENTATION #include <xrt.h> #include <stdio.h> int main(void) { str s = xrtFormat("Hello, XRT %s!", xrtVersion()); if ( s == NULL ) { return 1; } printf("%s\n", s); xrtFree(s); return 0; }
Что только что произошло. Две строки макросов стоят перед включением: XRT_MODULE_ALL выбирает включение всех модулей (можно перечислить только нужные — см. следующий раздел); XRT_IMPLEMENTATION заставляет эту единицу трансляции «обрасти» телами функций — в однозаголовочном режиме реализация и объявления живут в одном файле и переключаются этим макросом. Тип возвращаемого значения xrtFormat — str (управляемая XRT-строка; глава 3 раскрывает псевдонимы типов), и кто получил возвращаемое значение, тот и отвечает за xrtFree — это общебиблиотечное соглашение о владении, полностью раскрытое в главе 5.
Заодно обратите внимание на запись пути отказа: при неудаче выделения памяти xrtFormat возвращает NULL, программа проверяет и возвращает ненулевой код выхода. В реальной инженерии здесь ещё следовало бы прочитать слот ошибки потока и напечатать причину — это материал главы 4; здесь программа минимальна. Ещё одна деталь, которую стоит сделать привычкой прямо сейчас: возврат из main чего-то отличного от 0 означает неудачу; все примеры книги следуют соглашению «ноль — успех, не ноль — неудача», согласованному с направлением возвращаемых API XRT значений bool.
Флаги -lws2_32 -liphlpapi в команде сборки — сетевые системные библиотеки платформы Windows: хотя программа не пишет сетевого кода, сетевые модули ядра XRT ссылаются на них при включении всех модулей; Linux/macOS эти два флага не нужны. Эта команда будет повторяться на протяжении книги; глава 8 объяснит, почему после обрезки ссылаемые зависимости тоже уменьшаются.
Мощь обрезки: одна программа, два набора модулей
Замените первую строку макросов только нужными модулями, не меняя больше ни символа:
/* hello-min.c —— включаем только модуль string и его замыкание зависимостей */ #define XRT_MODULE_STRING #define XRT_IMPLEMENTATION #include <xrt.h> #include <stdio.h> int main(void) { str s = xrtFormat("Hello, XRT %s!", xrtVersion()); if ( s == NULL ) { return 1; } printf("%s\n", s); xrtFree(s); return 0; }
Что только что произошло. XRT_MODULE_STRING включает только модуль строк; фундамент, от которого он зависит (core, error и др.), разворачивается автоматически замыканием зависимостей в features.h — перечислять вручную не нужно: «выбрал модуль — принеси всё, что ему нужно», в этом смысл замыкания. Различия в размере зависят от версии и инструментария; числа выше лишь иллюстративны. Запустите ls -l сами — и «поставка по потребности» станет осязаемой. Заметьте и вторую выгоду: обрезка — это не только экономия размера; ссылаемые системные библиотеки, таблица инициализируемых модулей и даже поверхность атаки сужаются вместе; при встраивании в другие небольшие инструменты все три пункта одинаково ценны. Полный механизм модульных макросов раскрывается в главе 8.
2.4契约
- Односторонняя зависимость: расширения вызывают только ядро; в коде ядра не появится ссылок на xhttp/xws.
- Соглашения однозаголовочных макросов:
XRT_IMPLEMENTATIONможет встретиться ровно в одной единице трансляции проекта; модульные макросы должны быть определены до включения<xrt.h>. - Замыкание зависимостей: включение одного макроса семейства
XRT_MODULE_автоматически приносит все его зависимости; ручное перечисление чревато пропусками — доверьте это замыканию. - Версионная линия: макросы компиляции и рантайм-функция
xrtVersion()из одного источника (демонстрация в главе 3); при интеграции со связываемой библиотекой ими сверяют совпадение версий заголовка и библиотеки. - Базлайн примеров: примеры книги используют корень репозитория как рабочий каталог и вывод Windows + GCC как эталон; на Linux/macOS просто уберите два флага линковки, совпадающее поведение повторно не оговаривается.
Типичные ошибки
Симптомы: линковщик выдаёт лавину «multiple definition» (дублируются символы реализации XRT).
Причина: XRT_IMPLEMENTATION заставляет включающую единицу трансляции генерировать тела функций; две единицы с ним = две реализации.
/* xrt_util.h —— ваш общий заголовок */ #define XRT_IMPLEMENTATION /* включается из нескольких .c → несколько реализаций */ #include <xrt.h>
/* xrt_util.c —— единственная единица реализации */ #define XRT_MODULE_ALL #define XRT_IMPLEMENTATION #include <xrt.h> /* в xrt_util.h только #include <xrt.h> (без двух макросов) */
Симптомы: компилятор сообщает «implicit declaration», или компиляция проходит, но линковка сообщает о неопределённых символах — заголовок включён, а функций нет.
Причина: выбран неверный набор модульных макросов — включённые модули не покрывают используемые API. Запись имён макросов по памяти — верный путь к такой ошибке; в частности, фундамент ядра (core/error) присутствует всегда и отдельного модульного макроса не имеет — «добавить макрос фундамента» не существует.
#define XRT_MODULE_JSON /* включён только JSON — он не зависит от форматирования строк */ #define XRT_IMPLEMENTATION #include <xrt.h> /* ниже вызов xrtFormat → string не включён, линковка падает */
#define XRT_MODULE_STRING /* сверьтесь с include/xrt/features.h или шпаргалкой главы 8 */ #define XRT_IMPLEMENTATION #include <xrt.h>
Практические задания
Скомпилируйте и запустите hello.c; измените вывод на два приветствия (дважды вызовите xrtFormat — не забудьте два xrtFree).
hello.c использует только форматирование и запрос версии. Обрежьте его до минимального набора модулей, при котором программа ещё компилируется, и запишите включённые макросы и размер бинарника. Подсказка: начните с XRT_MODULE_STRING и смотрите, чего ещё не хватает линковщику.
Скомпилируйте одну и ту же программу с XRT_MODULE_STRING, XRT_MODULE_REGEX и XRT_MODULE_ALL; запишите три размера бинарников и объясните происхождение различий (что regex несёт сверх строк? во сколько раз все модули больше одного?). Критерий приёмки: таблица из трёх строк плюс два предложения анализа; ступень XRT_MODULE_ALL не должна быть меньше ступени одного модуля.
Итоги
| Тема | Краткая справка |
|---|---|
| Двухслойная структура | Ядро (include/xrt/) + пять расширений (extlibs/); зависимости строго односторонние |
| Три шага одним заголовком | Модульные макросы → XRT_IMPLEMENTATION → #include <xrt.h> (-I single) |
| Макрос реализации | Один раз на весь проект, в единственном .c |
| Обрезка | XRT_MODULE_* выбирает модули, замыкание зависимостей разворачивает автоматически; имена макросов по features.h |
| Управляемая строка | xrtFormat возвращает str; по завершении xrtFree |
| Граница протоколов | Ядро HTTP/WS — модули http, http1, websocket; рантаймы — xhttp/xws |
📖 Справочники API в этой главе:core (25 записей)string (92 записей)
Полные сигнатуры, ограничения параметров, возвращаемые значения и ошибки