惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

M
MIT News - Artificial intelligence
罗磊的独立博客
Hugging Face - Blog
Hugging Face - Blog
Apple Machine Learning Research
Apple Machine Learning Research
Last Week in AI
Last Week in AI
S
SegmentFault 最新的问题
让小产品的独立变现更简单 - ezindie.com
让小产品的独立变现更简单 - ezindie.com
美团技术团队
人人都是产品经理
人人都是产品经理
WordPress大学
WordPress大学
The Cloudflare Blog
IT之家
IT之家
雷峰网
雷峰网
小众软件
小众软件
博客园 - 叶小钗
博客园 - 聂微东
爱范儿
爱范儿
博客园 - 司徒正美
博客园 - 三生石上(FineUI控件)
V
Visual Studio Blog
博客园 - 【当耐特】
V
V2EX
博客园_首页
T
Tailwind CSS Blog

Все публикации подряд на Хабре

Ловим музу за клавиатуру: как айтишнику стать автором Что умеет Midjourney в 2026? Мой немного грустный разбор этого шикарного инструмента Никто не любит писать тесты, но ИИ может исправить это IPv8 выглядит как мечта. Поэтому почти наверняка не взлетит Производители вернули в продажу материнки с DDR3. Что происходит? Управление агентом с телефона через Telegram теперь в KodaCode От координации к лидерству: как меняется роль руководителя разработки Я сделала родителям бизнес вместо пенсии: зарабатываем 70 тысяч, мама не даёт продать В три раза быстрее приемка товара и оптимизация трудозатрат на 73%: как «РСТ-Инвент» помог Gulliver Group ИИ-шечный мир победил? О влиянии искусственного интеллекта на игропром Кремль снижает давление на Телеграмм пока Европа строит интернет по паспорту Как CEO, CTO и CIO за 8 часов собрали ИИ-директора, который умеет держать позицию под давлением Как (не) потерять домен за выходные Вместо 8 разных VPS: как я организовал практику студентам на одном сервере Почему твой Open Source проект не замечают? R&D: искусство управления неопределенностью в разработке AI-дефляция: вакансий для разработчиков больше, а рост зарплат — худший за 15 лет Мы отдали управление роботами OpenClaw. Что из этого вышло Галактический ID: система идентификации для всех форм разумной жизни Шесть основ бизнес-анализа: начинаем с вопроса «Кто в игре?» Код-ревью, в котором дело не в коде Данные переехали. Команда — нет Системной подход к сдаче OSWE в 2025 Почему комната управления реактором покрашена в цвет морской пены 4 YAML-файла вместо PySpark: как аналитикам строить пайплайны без разработчиков LLM-агент для поиска свободных доменов: автоматизируем подбор Когда, зачем и как правильно начинать новую сессию в Claude Code? Как я заставил нейросеть писать макросы для FreeCAD Анатомия ИИ‑агента для подбора персонала. От тысячи резюме к топ‑10 за минуты Опыт разработчика как экономика внимания
В поисках универсального SDK…
AlexGV · 2026-06-19 · via Все публикации подряд на Хабре

Средний

8 мин

42

Привет Хабр!

Наконец нашёл немного времени для написания статьи. Это моя первая статья на хабре, так что, извиняйте если что...

Цель статьи — показать проблемы при разработке SDK и найти попробовать архитектуру, которая минимизирует проблемы управления памятью, упрощает биндинги для разных языков и сохраняет производительность.

В этой статье мы порассуждаем о поиске некоего «универсального» SDK (Software Development Kit), который бы всем нравился, как новогодняя ёлка.

Так же в статье опущены вопросы безопасности. Мы будем предполагать, что используем проверенные исходники зависимостей для сборки нашего sdk.

И так, поехали «кушать кактус по частям...»

Для начала нужно определиться с требованиями. Чего вообще мы тут будем делать?

Требования SDK:
 — должен использоваться разработчиками на разных языках программирования (java,.net, swift, kotlin, c++, python).
 — должен быть кросс‑платформенный (Linux, Window, MacOS, iOS, Android).
 — не содержит GUI элементов.
 — должен иметь не большой размер.
 — должен быть максимальной простой и понятный в использовании.
 — не использовать проприетарные зависимости без исходников
 — ограничимся 64-битными платформами.

Исходя из таких требований попробуем определить стек нужных нам технологий. Понятно, что это должна быть библиотека, которая собирается под все нужные платформы и у неё должен быть интерфейс, который позволит подключить её в любом языке программирования. И тут к нам приходит С. СИ интерфейс и простые типы дают нам возможность интегрироваться со всеми языками. А вот внутренности мы будем писать на С++. Очень хочется С++20, к сожалению, не на всех платформах пока он поддерживается полностью. Но мы же хотим его!
И в наших требованиях нет указаний на минимальные версии библиотек. Пока будем ориентироваться на 20 стандарт и постараемся избегать возможностей, которые ещё не дошли, например до clang на macos\ios. В связи с тем, что часто бывают проблемы со сборкой зависимостей или поиском готовых бинарей для разных платформ мы возьмём vcpkg, в качестве менеджера пакетов и будем собирать зависимости из исходников и, если нужно, патчить.

Технический стек

С — интерфейс. С++, Cmake, vcpkg, gcc\сlang\msvc, Android NDK/ SDK,Linux host,MacOS host,Windows host (без этого можно обойтись, но он очень полезен)

Пока будем наивно думать, что этого нам хватит и подумаем над ограничениями реализации.

Ограничения

Нам нужно будет ограничить версию рантаймов или таскать их с собой, что делать не хотелось бы.

Ну что... с тем, что нужно вроде определились. Давайте теперь думать над внешним интерфейсом нашего SDK.

Варианты интерфейса

Конечно же это СИ интерфейс! А как мы его организуем? Кто будет аллоцировать и освобождать память? А как сделать так, чтобы разработчики на высокоуровневых языках не сошли с ума при написании очередного враппера для СИ?

Вариант 1:

А давайте просто сделаем методы где память аллоцирует и освобождает SDK!

int getData(const char* key, uint8_t data, uint64_t size);
freeData(data);

И получим на каждый возвращаемый параметр метод free. А там ещё структуры с кучей вложенных параметров.... А если разработчик, который привыкли к GC (garbage collector) не вызовет free? А потом разработчики SDK будут разбирать претензии с падениями и утечкой памяти. Неее... давай думать ещё.

Вариант 2: Ну ок, тогда пускай сами память аллоцируют, а мы будем заполнять данным. Ну как в winapi? Напишем об этом в комментариях.int getData(const char key, uint8_t data, uint64_t *size);

Чтобы узнать размер выходного буфера придётся сначала вызвать getData с пустым буфером, который вернёт размер. Аллоцировать буфер и потом уже вызвать ещё раз getData для получения данных. И после этого у разработчиков на языках с GC начинается боль с трем, чтобы переменные умирали, когда надо и не вызывали sigfault. А если нужно сохранять контекст и передавать его в разные методы? А ещё нужно читать комментарии, чтобы понять, что нужно 2 раза вызвать функцию.

Вариант 3:

А что если сделать хитро, через дескрипторы? Выделять память в SDK и сделать функции free,но не всё подряд возвращаемых структур и параметров, а на дескрипторы? И доступ к полям структур тоже через отдельные методы дескриптора. Дескриптором будет void* в внутри это структура с std::shared_ptr<>. И мы будем её туда сюда кастить. А чтобы нам не передали мусор добавим magic_field и будем его проверять. А ещё нужно возвращать не только код ошибки, а строку!

/// sdk.h
typedef struct Error
{
uint32_t code;
char description[1024];
}

int CreateContext(sdk_descriptor* d, Error* e);
int getData(sdk_descriptor d, const char* key, data_descriptor* out, Error* e);
freeSDKaDescriptor(sdk_descriptor d);
freeDataDescriptor(data_descriptor d);

/// data_descriptor.h
int GetSizeData(uint64_t* size, Error* e);
int GetData(uint64_t* buff, uint64_t size, Error* e);
и тд...

В итоге мы уменьшили проблемы с аллокацией памяти (структуры содержащие массивы байт и массивы других структур теперь передаются через дескрипторы), получили строгую последовательность вызова функций. Контроль ошибок. Высокую гибкость. Легко добавлять новые параметры. Для простых Си методов легче писать врапперы на других языках. А ещё можно использовать SWIG, чтобы генерить биндинги для простых вызовов.

НО! Ещё мы получили огромную кучу методов и различных хедеров. Отдельный метод на каждый параметр структуры. По 2 метода, чтобы получить строку. И ещё кучу вопросов от пользователей SDK, где искать нужный метод. И документацию на десятки страниц.
Формально оно всё работает и соответствует требованиям, но мы вроде не хотели такое?

Вариант 4:

Неее столько методов это всё очень не удобно, особенно в серверной версии с многопоточкой.
А давайте сделаем 2 метода и будем туда передавать JSON.

int get(const char* json_in, uint64_t size_in, const char** json_out, uint64_t* size_out, Error* e);
free(const char* json_out);

Всего 2 метода и они никогда не поменяются! Просто будем описывать новые параметры в json. И враперы писать удобно! Конечно мы в производительности сильно проиграем на парсинге и валидации и копировании этого json, а ещё надо документацию на все эти параметры и ещё надо её держать актуальной. А ещё бинарные буферы преобразовывать в base64, а ещё с кодировкой и utf8 что‑то надо подумать. Простота интерфейса — это хорошо, но у нас же С++ и нужно чтобы всё было быстро.

Вариант 5:

Так.... 2 метода это выглядит круто, нужно только избавить от json... Точно! Есть же thrift, protobuf может их? У них другое предназначение (RPC), но идею хорошая.
Давай возьмём FlatBuffer! Он Идеально подходит! Всё описание будет в формате FlatBuffer. Он бинарный, и с минимумом копирований (zero‑copy). Он умеет генерить код для всех нужных языков программирования и поддерживает все платформы. Всё описание будет структурировано и распространяется вместе с SDК. Мы получаем обратную совместимость и логирование из коробки. Кажется мы нашли то, что искали!

// in.fbs
table GetData {
key:string;
}

// out.fbs
table Data {
data:string;
value:int32;
....
}

/// sdk.h
int get(const char* data_in, uint64_t size_in, const char** data_out, uint64_t* size_out, Error* e);
free(const char* data_out);

А можно вообще без free? А что если мы будем возвращать указатель на память, которую будем переиспользовать постоянно для запросов и удалим только при выгрузке библиотеки?
Тогда не нужно делать free? FlatBuffer позволяет без копирований получить итоговый буфер, то есть будем хранить вот такое:
static thread local DetachBuffer
и он сам уничтожится в конце. Вообще замечательно 1 метод!

Только появляется проблема того, что мы это всё будем держать в памяти каждого потока, а если это «горячие» потоки и они живут до конца программы? Память, то не резиновая...
А если кто‑то сохранит указатель и вызовет метод get ещё раз? Или передаст в лямбду std::async? В общем и тут могут быть проблемы.

Получается метод free нужно вызывать после каждого вызова get. Если пользователь не вызовет free, память не будет потеряна безвозвратно, но будут вышеописанные проблемы. Внутри free мы не будем делать delete указателя, а будем делать swap текущего DetachBuffer с пустым.

Тут могут быть разные мнения на, то можно ли так делать в многопоточных приложения. Надежнее возвращать копию буфера и потом его очищать, но задача была максимально сократить копирование, поэтому оставим так, но укажем в документации на необходимость вызова free. И рекомендации использовать RAII.

В итоге нам удалось сделать максимально простой и легко расширяемый интерфейс, который легко встраивается в другие языки программирования. Мы максимально избежали копирования и проблем с аллокацией памяти. Получили готовое решение для сериализации/десериализации данных, удобный механизм отладки и анализа данных через преобразование FlatBuffers в текстовое представление (FlatBufferToString), кодогенерацию, избежали проблем с кодировками при передаче данных. FlatBuffers предоставляет механизмы для эволюции схемы и позволяет относительно безопасно добавлять новые поля без нарушения совместимости. Метод free не освобождает память операционной системе. Он лишь очищает внутренний буфер и позволяет переиспользовать его при следующих вызовах.

Что ещё можно улучшить?

А если добавить AI? Зачем? А кто будет делать примеры на кучи разных языков и документацию? Doxygen — прошлый век. Берём AI агента и просим всё это сделать. Идеально!

Мы получили быстрый расширяемый, достаточно универсальный SDK с примерами под разные языки, документацией и тестами, но с зависимостью от FlatBuffers.

// in.fbs
table GetData { key:string;}

// out.fbs
table Data { data:string; value:int32; ....}

/// sdk.h
int get(const char* data_in, uint64_t size_in, const char** data_out, uint64_t* size_out, Error* e); free(const char* data_out);

Попробуем свести в табличку все методы.

Решение

Zero Copy

Кодогенерация

Удобство

Размер

Чистый C

Возможно

Нет

Нет

Малый

JSON

Нет

Нет

Частично

Большой

Protobuf (RPC)

Частично

Да

Да

Средний

FlatBuffers

Да

Да

Да

Малый

Для поставленной задачи вариант с FlatBuffers показался наиболее удобным и перспективным. Вероятно, вместо FlatBuffers можно использовать другой похожий фреймворк. Бэнчмарки скорости не проводились. И этот вариант не претендует на идеальный.

По сути мы пришли к модели, напоминающей локальный RPC. Разница в том, что взаимодействие остаётся внутрипроцессным и не требует создания сокетов.

Давайте теперь актуализируем наш стек.

Технический стек
С — интерфейс,
С++,
Cmake,
vcpkg,
FlatBuffer,
gcc\сlang\msvc,
Android NDK/ SDK,
Linux host,
MacOS host,
Windows host
+ то, что зависит от функционала SDK (boost, nlohman, soci, curl и тд)
Можно использовать кросс‑компиляцию, но для проверки лучше иметь «живые» целевые ОС.

В результате мы пришли к выводу, что самая сложная часть SDK — это не алгоритмы и не бизнес‑логика, а границы между языками, управление памятью и совместимость между платформами. FlatBuffers не решает все проблемы, но позволяет существенно сократить объём инфраструктурного кода и сделать интерфейс SDK более предсказуемым для разработчиков на разных языках.

Если вы добрались до этого места, то теперь вы осознаёте боль разработчиков SDK.
Тут ещё не учтены огромное количество проблем при сборки под все эти платформы, разные разрядности x86 \ x64, различия версий рантаймов, проблемы видимости символов при встраивании SDK. Ну, а если в SDK есть GUI (например Qt), то это уже другая не универсальная история...

Надеюсь, Вам было интересно и не очень скучно!