Розробка біндінгів до C/C++ бібліотек для мобільних додатків
При інтеграції нативної C++ бібліотеки в мобільний додаток ключове завдання — написати коректні та продуктивні біндінги. Типова помилка — забути звільнити пам'ять після JNI виклику, що призводить до витоків і крашів. Ми розберемо, як цього уникнути на Android та iOS, і покажемо на прикладі OpenCV і FFmpeg.
C та C++ бібліотеки — стандарт в областях, де продуктивність критична: обробка відео (FFmpeg, x264), криптографія (OpenSSL, libsodium), комп'ютерний зір (OpenCV), аудіо (Opus, WebRTC), фізичні движки (Bullet, Box2D). Мобільні платформи дають прямий доступ до нативного коду — питання в тому, як правильно написати біндінг. Наш досвід показує, що грамотний біндінг економить до 30% часу на інтеграцію та тестування.
Як правильно передати дані через JNI без копіювання?
На Android нативний код викликається через JNI (Java Native Interface). Біндінг — це шар C/C++ функцій з іменами виду Java_com_example_MyClass_nativeMethod, які Dalvik/ART автоматично зв'язує з Java/Kotlin-методами, позначеними external.
// Kotlin class ImageProcessor { external fun processFrame(pixels: ByteArray, width: Int, height: Int): ByteArray companion object { init { System.loadLibrary("imageprocessor") } } } // C++ extern "C" JNIEXPORT jbyteArray JNICALL Java_com_example_ImageProcessor_processFrame( JNIEnv* env, jobject thiz, jbyteArray pixels, jint width, jint height) { auto* input = env->GetByteArrayElements(pixels, nullptr); // виклик OpenCV або своєї логіки env->ReleaseByteArrayElements(pixels, input, JNI_ABORT); // ... } Критичний момент — управління пам'яттю на межі JNI. GetByteArrayElements з прапорцем 0 копіює масив (безпечно, але повільно). GetByteArrayElements з JNI_ABORT — не копіює зміни назад. Для продуктивної обробки зображень використовуємо GetDirectBufferAddress з ByteBuffer.allocateDirect() — це shared memory без копіювання. Це в 2–3 рази швидше для потокових даних.
CMakeLists.txt через Android NDK: вказуємо target_link_libraries для підключення попередньо скомпільованої .a або .so бібліотеки. Для OpenCV — find_package(OpenCV REQUIRED) якщо збираємо з вихідників, або ручне підключення libopencv_core.a + libopencv_imgproc.a через add_library(opencv STATIC IMPORTED). Розмір бінарника важливий: OpenCV static linking додає 8–15 МБ на ABI. Використовуємо abiFilters "arm64-v8a", "x86_64" — прибираємо зайві ABI.
Exception Handling в JNI. C++ винятки не проходять через JNI автоматично. Обгортаємо в try/catch на C++ стороні, кидаємо Java-виняток через env->ThrowNew(env->FindClass("java/lang/RuntimeException"), message).
Що робити, якщо C++ бібліотека не підтримує iOS?
На iOS C-код підключається напряму — Swift та Objective-C працюють в одному рантаймі з C. Для C++ потрібен Objective-C++ (.mm файли).
// ImageProcessorBridge.mm — Obj-C++ обгортка #include "opencv2/opencv.hpp" #import "ImageProcessorBridge.h" @implementation ImageProcessorBridge - (NSData *)processFrame:(NSData *)pixelData width:(int)w height:(int)h { cv::Mat mat(h, w, CV_8UC4, (void*)pixelData.bytes); // обробка NSData *result = ...; return result; } @end Swift викликає Objective-C через bridging header (-Bridging-Header.h). Напряму C++ з Swift не викликати до Swift 5.9, де з'явився експериментальний Swift/C++ Interop — дозволяє імпортувати C++ типи напряму через import CxxModule. У продакшені з Xcode 15 це вже робочий варіант для простих C++ API без шаблонів і віртуального наслідування.
XCFramework з нативною бібліотекою. Якщо підключаємо попередньо скомпільовану C++ бібліотеку — збираємо .xcframework з lipo create для Device (arm64) і Simulator (arm64 + x86_64). Apple Silicon Simulator вимагає arm64, Intel Mac — x86_64; fat binary через lipo об'єднує обидва.
OpenCV на iOS. Офіційний opencv2.framework (або xcframework) підключається через Cocoapods (pod 'OpenCV') або вручну. Розмір: ~160 МБ у debug, з бітового коду linker вибирає тільки потрібні модулі. Для App Store важливий strip у Release-збірці.
Приклад з практики: real-time обробка відео
Додаток для обробки відеопотоку з камери (фільтри, розпізнавання облич): iOS — AVFoundation дає CMSampleBuffer → конвертація в cv::Mat через CVPixelBufferGetBaseAddress → OpenCV фільтр → відображення через MTLTexture (Metal). Android — Camera2 API → ImageReader з форматом YUV_420_888 → конвертація через libyuv в RGBA → OpenCV → SurfaceView. Біндінги написані на Objective-C++ (iOS) та JNI (Android). Продуктивність: обробка кадру 1920×1080 — 8–12 мс на iPhone 13, 15–22 мс на mid-range Android.
Збірка та інтеграція
CMake — крос-платформена система збірки і для Android NDK і для iOS (через CMake toolchain для iOS). Один CMakeLists.txt для нативної логіки, різні toolchain файли.
Для складних C++ бібліотек з autoconf/Makefile — configure && make запускається через cross-compilation toolchain NDK або iOS. Це більш трудомістко, але стандартна практика для OpenSSL, libsodium, FFmpeg.
Що входить в роботу
- Аналіз API цільової бібліотеки: виявлення експортованих функцій, типів даних, залежностей.
- Проектування біндінгу: вибір підходу (JNI, Obj-C++, Swift/C++ Interop), узгодження інтерфейсу.
- Реалізація: написання коду на Swift/Obj-C/Kotlin та C/C++, CMakeLists, Xcode project integration.
- Тестування: unit-тести, інтеграційні тести, тести продуктивності.
- Документація: опис біндінгу, приклади використання, інструкція зі збірки.
- Підтримка: допомога в App Store Review, оновлення під нові версії ОС.
Цей обсяг роботи ми виконуємо під ключ. Оцінимо ваш проект за 1–2 робочих дні. Зв'яжіться з нами для консультації.
Строки орієнтовно
| Тип біндінгу | Орієнтовні строки |
|---|---|
| Проста C бібліотека (crypto, compression) | 2–4 тижні |
| C++ бібліотека з нетривіальним API (OpenCV, FFmpeg) | 4–8 тижнів |
| Повноцінна інтеграція з UI pipeline | 2–4 місяці |
Вартість розраховується індивідуально — залежить від складності API цільової бібліотеки та наявності існуючої документації. Замовте розробку біндінгу — отримаєте готове рішення з гарантією сумісності.







