블로그 목록
Fundamentals25분 읽기

하나의 C++ 코어로 iOS와 Android를 — 네이티브 코드, 크로스 컴파일, .so/.a, JNI, Objective-C++

공통 C++ 코어를 Android와 iOS에 배포할 때 필요한 ABI별 빌드와 플랫폼 바인딩을 설명합니다. Android의 JNI와 shared library, iOS의 Objective-C++와 static library·XCFramework를 구분하고, 네이티브 코어 업데이트가 앱 재빌드와 배포를 요구하는 이유도 함께 다룹니다.

C++JNIObjective-C++크로스 컴파일ABIARM.so.aNDKXCFramework네이티브기초
목차(28개 항목)
  1. 0. libwebrtc 기반 SDK — 소스는 공유하고 산출물은 플랫폼별로 만든다
  2. 1. libwebrtc가 C++를 쓰는 이유 — 성능과 멀티플랫폼 자산
    1. C++를 고르는 세 가지 이유
    2. 언어 선택 비교
  3. 2. 네이티브 코드 · 컴파일 · 크로스 컴파일 · ARM ABI
    1. 네이티브 코드란?
    2. 크로스 컴파일이란?
    3. ARM ISA는 같은데 왜 하나의 바이너리로 안 되나 — ABI 문제
  4. 3. 정적·동적 링킹과 OS별 파일 형식
    1. Static vs Dynamic — 진짜 구분 기준
    2. 두 OS 모두 둘 다 지원한다 — 이게 핵심
    3. 그럼 WebRTC는 실제로 어떻게 배포되나
  5. 4. Android의 연결 — JNI로 Java/Kotlin ↔ C++ 잇기
    1. JNI란?
    2. JNI가 작동하는 3단계
    3. 단계별 코드
  6. 5. iOS의 연결 — Objective-C++(.mm)와 Swift-C++ Interop
    1. Objective-C++ — 한 파일에서 ObjC와 C++ 혼용
    2. Swift에서 C++ 부르기 — 두 갈래
    3. Android(JNI) vs iOS(ObjC++/Swift) 비교
  7. 6. 래퍼(Wrapper) — 앱 개발자가 실제로 만지는 것
  8. 7. 업데이트 = 앱 재빌드 + 스토어 재배포
    1. 영향 비교
  9. 8. 전체 그림 한 장 — 하나의 C++ 코어가 두 OS로
  10. 9. 암기 카드 — 한 줄 요약
  11. 10. 한 줄 결론
  12. 관련 글
  13. 참고 자료

"libwebrtc가 C++로 짜여 있다는데, iOS와 Android에서 그 코드를 어떻게 같이 쓰나요?" 같은 C++ 소스를 각 플랫폼용으로 빌드하고 플랫폼별 바인딩을 붙이는 것이 일반적인 구조입니다. 다만 모든 WebRTC 제품이 하나의 공통 코어를 쓰는 것은 아닙니다.

이 글은 그 구조를 처음부터 끝까지 정리합니다. 왜 코어가 C++인지, 네이티브 코드와 크로스 컴파일이 정확히 뭔지, .a와 .so의 진짜 구분 기준은 무엇인지(여기서 흔한 오해를 정정합니다), Android는 JNI로·iOS는 Objective-C++로 어떻게 C++를 부르는지를 차례로 짚습니다. 마지막으로 이 구조의 트레이드오프(업데이트하려면 앱을 다시 빌드해야 한다)까지 다룹니다.


0. libwebrtc 기반 SDK — 소스는 공유하고 산출물은 플랫폼별로 만든다

libwebrtc 기반 모바일 SDK는 공통 C++ 소스를 각 target OS·architecture·ABI에 맞춰 빌드한다. Android는 주로 JNI로 Kotlin/Java와 연결하고, Apple 플랫폼은 Objective-C++ 또는 Swift-C++ interoperability를 사용할 수 있다. 바인딩은 타입·수명·오류 경계를 관리해야 하므로 항상 얇거나 자동인 것은 아니다.

흔한 오해와 정정부터 박아두겠습니다. 이 표 하나가 이 글 전체의 요약입니다.

오해정확한 이해
iOS와 Android는 RTC 코드를 항상 각자 다시 작성한다libwebrtc 기반 SDK는 공통 C++ 소스를 재사용할 수 있지만 플랫폼 통합 코드는 별도
.so는 동적, .a는 정적이라는 개념만 알면 된다정적/동적 링킹 개념은 공통이지만 실제 파일 형식은 OS별로 다름. Android는 ELF .so, Apple 플랫폼은 Mach-O dylib/framework를 사용
ARM 칩이면 다 같으니 .so 하나로 모든 안드로이드폰에서 돈다❌ ABI(armeabi-v7a / arm64-v8a / x86_64…)가 다르면 호환 안 됨. APK에 여러 .so를 담음
C++ 코어는 OS가 알아서 자동으로 업데이트해준다❌ 코어는 앱 바이너리에 박혀 배포됨. 업데이트 = 앱 재빌드 + 스토어 재배포
iOS도 JNI가 필요하다JNI는 필요 없지만 ObjC++/Swift-C++에도 타입·소유권·ABI 경계가 존재

1. libwebrtc가 C++를 쓰는 이유 — 성능과 멀티플랫폼 자산

실시간 미디어 엔진은 짧은 처리 deadline과 일정한 지연이 중요합니다. C++는 네이티브 코드, SIMD, 메모리 배치와 기존 미디어 생태계를 함께 활용할 수 있어 libwebrtc의 핵심 구현 언어로 사용됩니다. 이 요구가 C++만을 강제하는 것은 아니며 Rust 같은 대안도 가능합니다.

C++를 고르는 세 가지 이유

1. 네이티브 성능과 저수준 제어 C++는 가상 머신이나 인터프리터를 거치지 않고 CPU 기계어로 직접 컴파일됩니다. 코덱의 DSP 루프, SIMD 벡터 연산(NEON/SSE), 메모리 정렬 같은 저수준 최적화를 그대로 표현할 수 있습니다.

2. 런타임 일시정지를 제어하기 쉬움

🔴 GC 있는 언어 (예: JVM)
   오디오 프레임 처리 중...
   → GC가 갑자기 끼어들어 "Stop-the-world" 일시정지
   → 수 ms ~ 수십 ms 멈춤 (Major GC)
   → 그 사이 오디오 프레임 마감 시간(10ms) 놓침
   → "뚝뚝" 끊김 (audio glitch)

GC 없는 C++ (수동 메모리 관리)
   오디오 프레임 처리 중...
   → 개발자가 메모리 수명을 직접 통제
   → 예측 못한 일시정지 없음
   → 프레임 마감 시간을 안정적으로 지킴

Swift는 GC가 아니라 ARC를 사용합니다. GC·ARC·수동 메모리 관리는 각각 비용과 안전성 특성이 다르며, GC 없음만으로 언어 선택을 설명할 수 없습니다. libwebrtc에서는 기존 C++ 코드와 플랫폼 이식성, 저수준 제어가 함께 작용합니다.

3. 멀티플랫폼 코드 재사용 C++ 코어 알고리즘을 여러 target에서 재사용할 수 있습니다. 플랫폼 API, 툴체인, 조건부 컴파일과 바인딩은 별도로 관리해야 하며, 언어 비중을 90% 이상 같은 고정 수치로 일반화하지 않습니다.

트레이드오프: C++는 메모리 안전과 객체 수명 관리 부담이 큽니다. 기존 코드·도구·플랫폼 지원을 재사용하는 이점과 함께 평가해야 합니다.

언어 선택 비교

언어성능일시정지 위험멀티플랫폼 코어RTC 코어로
C++🟢 네이티브🟢 GC 없음🟢 전 플랫폼✅ 주류
Java/Kotlin (ART)JIT/AOT 혼합GC 영향은 할당 패턴·런타임에 따름Android 중심플랫폼 계층에 사용
Swift네이티브ARCApple 중심Apple 전용 코어에 사용 가능
Rust네이티브GC 없음여러 target신규 네이티브 코어 선택지

2. 네이티브 코드 · 컴파일 · 크로스 컴파일 · ARM ABI

코어가 C++라는 건 알았습니다. 그런데 "C++ 소스"가 곧바로 폰에서 도는 건 아닙니다. 사람이 읽는 텍스트일 뿐이고, CPU가 실행하려면 기계어(machine code)로 번역해야 합니다. 이 번역이 컴파일입니다.

네이티브 코드란?

네이티브 코드 = 특정 CPU가 직접 실행할 수 있는 기계어 명령어. 중간 가상 머신(JVM 등)을 거치지 않고 하드웨어에서 바로 도는 코드.

C++ 소스 (.cpp, 사람이 읽는 텍스트)
      │  컴파일 (clang/gcc)
      ▼
기계어 (특정 CPU 전용 0/1 명령어)
      │
      ▼
CPU가 직접 실행 ← "네이티브"

크로스 컴파일이란?

여기서 모바일 개발 특유의 문제가 생깁니다. 개발자는 Mac이나 PC에서 코드를 짜지만, 그 코드는 폰의 ARM CPU에서 돌아야 합니다. Mac의 CPU와 폰의 CPU는 명령어 집합이 다릅니다. 그래서 "내 컴퓨터와 다른 종류의 CPU용 기계어를 만드는" 컴파일이 필요합니다.

크로스 컴파일(Cross-compilation): 컴파일러가 실행되는 플랫폼(Host)과 다른 플랫폼(Target)의 실행 코드를 생성하는 것.

  • Host = 컴파일러가 도는 곳 (보통 개발자의 Mac/PC)
  • Target = 생성된 코드가 실행될 곳 (폰의 ARM 기기)
[Host: 개발자 Mac (Apple Silicon 또는 Intel)]
        │
        │  Android NDK / Xcode 의 크로스 컴파일러
        │  "내 CPU 말고 ARM 폰용 기계어를 만들어줘"
        ▼
[Target용 기계어 산출물]
        │
        ▼
[폰의 ARM CPU에서 실행]

Host와 target이 다를 때 cross-compilation이라고 합니다. 같은 ARM64라도 target OS의 ABI와 파일 형식이 다르면 산출물을 서로 바꿔 쓸 수 없습니다. Android는 NDK, iOS는 Xcode 툴체인이 해당 target 구성을 제공합니다.

ARM ISA는 같은데 왜 하나의 바이너리로 안 되나 — ABI 문제

흔한 오해를 정정합니다.

정정: "스냅드래곤·미디어텍·Apple Silicon이 다 ARM이니까 ARM 기계어 .so 하나면 모든 안드로이드폰에서 돈다" → FALSE.

ARM ISA(명령어 집합 구조)를 공유하더라도 ABI(Application Binary Interface)가 다르면 호환되지 않습니다. ABI는 명령어뿐 아니라 함수 호출 규약, 레지스터 사용법, 데이터 정렬, 32비트/64비트 여부까지 포함한 "바이너리 수준의 약속"입니다.

Android가 지원하는 주요 ABI:

ABI설명비트
armeabi-v7a32비트 ARM (Thumb-2)32-bit
arm64-v8a64비트 ARM (AArch64) — 현재 권장64-bit
x8632비트 인텔 (에뮬레이터 등)32-bit
x86_6464비트 인텔 (에뮬레이터, 일부 기기)64-bit

하나의 .so 바이너리는 빌드 대상 ABI와 OS 계약에 맞아야 합니다. 64비트 ARM 하드웨어라도 OS가 32비트 userspace를 지원하지 않을 수 있으므로 AArch32 실행을 보장할 수 없습니다. Android App Bundle/APK는 지원할 ABI별 라이브러리를 포함하도록 구성합니다.

my-app.apk
└── lib/
    ├── arm64-v8a/libwebrtc.so      ← 최신 64비트 폰
    ├── armeabi-v7a/libwebrtc.so    ← 구형 32비트 폰
    └── x86_64/libwebrtc.so         ← 에뮬레이터
        ↑ 같은 C++ 소스를 ABI별로 각각 크로스 컴파일한 결과물
# Android NDK 설정 예 (개념)
APP_ABI := arm64-v8a armeabi-v7a x86_64
# → 같은 C++ 소스를 세 ABI로 각각 빌드 → APK에 세 개의 .so 포함

따라서 "ARM이면 다 같다"가 아니라, 같은 C++ 소스 → ABI 개수만큼 크로스 컴파일 → 각각 별도 바이너리가 정확한 그림입니다.


3. 정적·동적 링킹과 OS별 파일 형식

이 글에서 가장 자주 틀리는 지점입니다. 명확히 정정하겠습니다.

정정: ".so는 Android(리눅스)용, .a는 iOS(유닉스)용 기계어 파일이다" → FALSE.

정적·동적 링킹이라는 개념은 OS에 공통이지만 파일 형식과 확장자는 OS별입니다. .a는 정적 archive에 널리 쓰이고 Android/Linux는 ELF .so, Apple 플랫폼은 Mach-O dylib 또는 framework를 사용합니다.

Static vs Dynamic — 진짜 구분 기준

항목.a (Static Archive).so (Shared Object)
결합 시점컴파일/링크 시점런타임(실행 중) 로드
코드 위치앱 바이너리 안에 복사됨별도 파일로 존재, 실행 시 로드
앱 크기커짐 (코드 포함)작아짐 (외부 참조)
공유불가 (각 앱이 복사본)여러 프로세스가 공유 가능
개념의 소속OS 무관 — 링킹 방식OS 무관 — 링킹 방식
🔗 Static (.a) — 컴파일할 때 코드를 앱 안으로 복사
  [libcore.a] ──복사──▶ [내 앱 바이너리(코드 포함)]
                          → 실행 시 외부 파일 불필요

🔗 Dynamic (.so) — 실행할 때 외부 파일을 로드
  [내 앱 바이너리] ──런타임 로드──▶ [libcore.so]
                          → 실행 시 .so 파일이 옆에 있어야 함

비유: .a는 책 내용을 통째로 복사해서 내 보고서에 붙여넣는 것(보고서가 두꺼워지지만 독립적), .so는 참고문헌으로 링크만 걸어두는 것(보고서는 얇지만 그 문헌이 옆에 있어야 함). 기술적 실체 — Static은 링커가 .a 안의 필요한 오브젝트를 앱 실행 파일에 직접 병합하고, Dynamic은 실행 시 동적 링커가 .so를 메모리에 매핑합니다.

두 OS 모두 둘 다 지원한다 — 이게 핵심

Static (.a)Dynamic비고
Android✅ 지원 (다른 라이브러리에 링크)✅ .so (NDK 기본 산출물)NDK가 C/C++ 소스로 .so를 빌드
iOS✅ .a / 정적 framework✅ Mach-O dylib / 동적 frameworkXCFramework는 여러 플랫폼·architecture 변형을 묶는 배포 컨테이너

Android NDK 공식 설명을 그대로 옮기면:

  • Native Libraries (.so files) — NDK가 C/C++ 소스에서 빌드하는 동적 라이브러리
  • Static libraries (.a files) — 다른 라이브러리에 링크해 넣을 수 있는 정적 라이브러리

Android도 정적 archive를 다른 native library에 링크할 수 있고, Apple 플랫폼도 정적·동적 framework를 지원합니다. XCFramework가 정적 또는 동적을 자동으로 결정하지는 않습니다.

.a/.so는 "정적이냐 동적이냐"이지 "iOS냐 Android냐"가 아닙니다. Static vs Dynamic은 OS 독립적 개념입니다.

그럼 WebRTC는 실제로 어떻게 배포되나

판정: PARTLY_TRUE — "iOS는 WebRTC.framework, Android는 .aar/.so" 는 대체로 맞지만 정확히 정리하면:

WebRTC 코어는 C++로 작성되어 있고 iOS·Android가 같은 C++ 코드베이스를 공유합니다. 다만 배포 형태는 플랫폼별로 다릅니다.

플랫폼WebRTC 코어를 모바일에 싣는 전형적 형태내용물
iOS.framework 또는 XCFramework컴파일된 C++ 코어 + Objective-C 래퍼
Android.aar (Android Archive) 또는 직접 .so컴파일된 C++ 코어(.so) + Java/Kotlin 래퍼

공식 source tree는 iOS framework와 Android library를 빌드하는 도구를 제공합니다. 사전 빌드 배포 여부와 패키지 형식은 공급자·버전별 릴리스 문서에서 확인합니다.

        같은 C++ 코어 소스 (libwebrtc)
       /                            \
   크로스 컴파일 (iOS 타깃)       크로스 컴파일 (Android 타깃)
       │                            │
   WebRTC.framework /            libjingle_peerconnection.so
   XCFramework                   + .aar(Java/Kotlin 래퍼 포함)
       │                            │
   [iOS 앱에 링크]               [Android 앱에 링크]

WebRTC는 오픈소스라 배포 형태가 다양합니다. Google 소스를 빌드하면 iOS는 XCFramework, Android는 AAR+.so 형태로 산출되며, 이를 각 SDK 제공자가 자체 래퍼와 함께 재배포합니다. 그래서 "정확히 어떤 파일 확장자냐"는 누가 배포했느냐에 따라 달라집니다.


4. Android의 연결 — JNI로 Java/Kotlin ↔ C++ 잇기

Android 앱의 Kotlin/Java 코드는 DEX bytecode로 컴파일되어 ART(Android Runtime) 에서 실행됩니다. 네이티브 C++ 라이브러리와의 호출 경계에 JNI를 사용할 수 있습니다.

JNI란?

JNI(Java Native Interface): Java/Kotlin 코드와 C/C++ 네이티브 코드가 서로 호출할 수 있게 해주는 표준 규격.

JNI는 Java 코드와 native code 사이의 표준 인터페이스입니다. Android에서는 ART와 C/C++ 컴포넌트를 연결할 때 사용합니다.

JNI가 작동하는 3단계

[Kotlin/Java 코드]
   │  ① native 키워드로 "이 함수는 C++에 있다" 선언
   ▼
[JNI 경계]  ← Sun/Oracle이 정한 표준 규약
   │  ② System.loadLibrary()로 .so 로드
   │  ③ `RegisterNatives()` 또는 이름 기반 discovery로 함수 매핑
   ▼
[C++ 네이티브 코드 (.so 안)]

단계별 코드

①② Kotlin: external 선언 + .so 로드

①(external 선언)과 ②(loadLibrary)는 같은 Kotlin 블록 안에서 함께 이뤄집니다.

class WebRtcEngine {
    // ① "이 함수의 실제 구현은 C++ .so 안에 있다"는 선언
    external fun startCall(channelId: String): Int

    companion object {
        init {
            // ② 앱 실행 시 .so를 메모리에 로드
            System.loadLibrary("webrtc_core")   // → libwebrtc_core.so
        }
    }
}

Java로 쓰면 public native int startCall(String channelId); — 키워드만 native로 다를 뿐 같은 개념입니다.

③ C++: 이름 기반 함수 구현 예

아래는 이름 기반 discovery 예입니다. Android 공식 가이드는 보통 JNI_OnLoad()에서 RegisterNatives()로 명시적으로 등록하는 방식을 권장합니다.

#include <jni.h>

extern "C"   // C++ 이름 맹글링 방지 (JNI는 C 심볼 규약을 기대)
JNIEXPORT jint JNICALL
Java_com_example_WebRtcEngine_startCall(
    JNIEnv* env,        // JNI 환경 포인터 (JVM과 통신하는 핸들)
    jobject thiz,       // 호출한 Kotlin 객체 (this)
    jstring channelId)  // Kotlin String → JNI jstring
{
    // jstring을 C 문자열로 변환
    const char* id = env->GetStringUTFChars(channelId, nullptr);

    int result = WebRtcCore::StartCall(id);   // 실제 C++ 코어 호출

    env->ReleaseStringUTFChars(channelId, id);  // 수동 해제 (메모리 관리)
    return result;   // int → jint 로 JVM에 반환
}

핵심 포인트:

  • extern "C" — 이름 기반 discovery 예에서 C++ name mangling을 막습니다. RegisterNatives()를 쓰면 이 이름 규칙에 덜 의존합니다.
  • JNIEXPORT / JNICALL — JNI가 인식하는 함수임을 표시하는 매크로.
  • 타입 변환 — Kotlin의 String은 JVM 객체라 C++의 char*로 쓰려면 GetStringUTFChars로 변환해야 합니다. JVM 세계와 네이티브 세계는 데이터 표현이 다르기 때문입니다.

JNI 경계에서는 타입 변환, 참조 관리와 예외 처리가 필요할 수 있습니다. primitive와 direct buffer처럼 복사를 피할 수 있는 경로도 있으므로 모든 호출이 데이터를 복사한다고 단정하지 않습니다. 실제 호출 빈도와 payload로 측정합니다.


5. iOS의 연결 — Objective-C++(.mm)와 Swift-C++ Interop

iOS에는 ART/JNI 계층이 없습니다. Swift·Objective-C와 C++는 Objective-C++ 또는 Swift-C++ interoperability로 연결할 수 있지만 타입·소유권·오류 처리 경계는 여전히 존재합니다.

어느 경로가 더 단순한지는 노출할 C++ API와 지원 기능에 따라 달라집니다.

Objective-C++ — 한 파일에서 ObjC와 C++ 혼용

.mm 확장자 파일은 Objective-C++ 컴파일 단위입니다. 같은 파일에서 Objective-C와 C++를 사용할 수 있지만 NSString과 std::string처럼 표현이 다른 타입은 명시적으로 변환하고 수명을 관리해야 합니다.

// WebRtcEngine.mm   ← .mm = Objective-C++ (ObjC + C++ 동시 가능)
#import "WebRtcEngine.h"
#include "webrtc_core.h"     // C++ 헤더를 그대로 include

@implementation WebRtcEngine

- (int)startCall:(NSString *)channelId {
    // NSString(ObjC) → std::string(C++) 변환은 한 줄
    std::string id = std::string([channelId UTF8String]);

    // C++ 코어를 직접 호출 — 별도 바인딩 규약 불필요
    return WebRtcCore::StartCall(id);
}

@end

JNI 코드(섹션 4)와 비교해보면 차이가 분명합니다: extern "C", JNIEXPORT, JNIEnv*, GetStringUTFChars/ReleaseStringUTFChars 같은 의식(ceremony)이 전혀 없습니다. C++ 헤더를 include하고 함수를 바로 호출하면 끝입니다.

Swift에서 C++ 부르기 — 두 갈래

[Swift 앱 코드]
   │
   ├─ (전통) Swift → ObjC++ 래퍼(.mm) → C++
   │         Swift는 C++를 직접 못 보므로 ObjC 래퍼 경유
   │
   └─ (2023+) Swift → C++ 직접 interop
             Swift-C++ interoperability 활성화 시 C++ 직접 호출

Swift 5.9부터 C++ interoperability를 지원합니다. 다만 C++의 모든 기능을 지원하는 것은 아니며, 지원 범위와 제약은 Swift 공식 status 문서에서 계속 갱신됩니다. 프로젝트가 사용하는 템플릿·소유권·예외 패턴을 실제 툴체인으로 검증해야 합니다.

Android(JNI) vs iOS(ObjC++/Swift) 비교

항목AndroidiOS
앱 언어Java / KotlinSwift / Objective-C
런타임ARTApple native runtime
C++ 연결 방식JNI (표준 규격 계층)ObjC++(.mm) / Swift-C++ interop
연결 규약JNI, 보통 RegisterNatives()ObjC++ 또는 Swift-C++ interop 규칙
데이터 변환타입에 따라 변환·참조 관리타입에 따라 변환·소유권 관리
표현"다리를 놓는다(bridge)""같이 컴파일한다"

Android는 ART와 native code를 JNI로 연결하고, Apple 플랫폼은 ObjC++ 또는 Swift-C++ interop을 사용한다. 두 경로 모두 언어·타입·수명 경계를 설계해야 한다.


6. 래퍼(Wrapper) — 앱 개발자가 실제로 만지는 것

여기까지 보면 의문이 생깁니다. "그럼 RTC 앱 만들 때 매번 JNI나 .mm을 직접 짜야 하나?" 아닙니다. 그 위에 래퍼(Wrapper) 계층이 있습니다.

래퍼: 저수준 C++ 코어를 각 플랫폼의 관용적(idiomatic) API로 감싼 얇은 계층. 앱 개발자는 Kotlin/Swift 객체만 다루고, 내부의 C++ 호출은 래퍼가 숨긴다.

┌──────────────────────────────────────────────┐
│ 앱 개발자 코드 (Kotlin / Swift)               │
│   engine.startCall("room-123")  ← 이것만 만짐 │
├──────────────────────────────────────────────┤
│ 플랫폼 래퍼 (Kotlin 클래스 / Swift 클래스)    │
│   - Android: JNI 호출을 감싼 Kotlin API       │
│   - iOS:     ObjC++/Swift로 감싼 Swift API    │
├──────────────────────────────────────────────┤
│ 바인딩 계층                                    │
│   - Android: JNI (Java_..._startCall)         │
│   - iOS:     .mm / Swift-C++ interop          │
├──────────────────────────────────────────────┤
│ C++ 코어 (libwebrtc) ← 양 플랫폼 공유           │
│   코덱 · 네트워크 · 에코캔슬러 · 지터버퍼       │
└──────────────────────────────────────────────┘

각 계층의 역할:

계층역할누가 작성
앱 코드비즈니스 로직, UI앱 개발자(우리)
래퍼플랫폼 친화 API 제공SDK 제공자(Google/Agora 등)
바인딩언어 경계 연결 (JNI/.mm)SDK 제공자
C++ 코어실제 미디어 처리SDK 제공자

핵심: 아래 세 계층(래퍼·바인딩·코어)은 SDK가 이미 만들어 배포합니다. 그래서 앱 개발자는 보통 WebRTC.framework를 import하고 Swift로, 또는 .aar를 의존성에 넣고 Kotlin으로 코어를 호출만 하면 됩니다. JNI·.mm을 직접 짜는 건 코어를 만드는 쪽(SDK 벤더)의 일입니다.

비유: 자동차 엔진(C++ 코어)은 엔지니어가 만들고, 운전자(앱 개발자)는 핸들과 페달(래퍼)만 만집니다. 기술적 실체 — 래퍼는 "Kotlin/Swift로 노출된 공개 API 표면"이고, 그 아래 JNI/.mm이 실제 C++ 함수로 호출을 전달합니다.


7. 업데이트 = 앱 재빌드 + 스토어 재배포

마지막으로 이 구조의 가장 중요한 운영상 트레이드오프입니다.

앱에 포함한 native SDK는 앱 릴리스 경로로 업데이트한다.

C++ 코어(.so/.framework)는 앱 바이너리 안에 포함되어 배포됩니다. 서버에서 동적으로 받아오는 웹 자바스크립트와 근본적으로 다릅니다.

웹 (브라우저 WebRTC)
   브라우저 공급자의 배포 정책으로 엔진 업데이트
   → 사용자의 브라우저 버전과 rollout 시점에 따라 반영

네이티브 (C++ 코어 내장)
   C++ 코어가 앱 바이너리에 박혀 있음
   → 코어 버전 올리려면:
     ① 새 SDK 버전 의존성 교체
     ② 앱 재빌드 (크로스 컴파일 다시)
     ③ 앱스토어/플레이스토어 재심사·재배포
     ④ 사용자가 앱 업데이트해야 적용

영향 비교

항목웹(브라우저 내장)네이티브(C++ 코어 내장)
코어 업데이트 경로브라우저 공급자와 사용자 업데이트 정책앱 재빌드 + 스토어 재배포
반영 속도빠름느림 (심사 + 사용자 업데이트 대기)
버전 파편화적음🔴 큼 (구버전 앱 사용자 잔존)
긴급 패치브라우저 rollout과 사용자 버전에 따름스토어 심사·사용자 업데이트에 따름

실무 함의: 네이티브 앱은 사용자마다 다른 SDK 버전을 사용할 수 있습니다. 최소 지원 버전, 강제 업데이트 조건과 서버 호환 범위를 미리 설계해야 합니다.

네이티브 SDK는 브라우저보다 넓은 기기 제어권을 얻는 대신 앱 릴리스 주기에 묶입니다.


8. 전체 그림 한 장 — 하나의 C++ 코어가 두 OS로

                    ┌─────────────────────────┐
                    │   C++ 코어 소스 (단일)   │
                    │  코덱·네트워크·AEC·버퍼  │
                    └────────────┬────────────┘
                                 │
              ┌──────────────────┴──────────────────┐
   크로스 컴파일 (NDK)                    크로스 컴파일 (Xcode)
              │                                      │
   ABI별 .so (arm64-v8a 등)            XCFramework/.framework
              │                                      │
        JNI 바인딩                         ObjC++(.mm)/Swift interop
     (Java_..._fn, JNIEXPORT)            (헤더 include 후 직접 호출)
              │                                      │
      Kotlin/Java 래퍼                        Swift 래퍼
              │                                      │
        [Android 앱]                            [iOS 앱]
              │                                      │
        스토어 배포 ──── 업데이트하려면 재빌드 ──── 스토어 배포

9. 암기 카드 — 한 줄 요약

개념한 줄 요약
왜 C++네이티브 제어 + 기존 미디어 생태계 + 멀티플랫폼 코드 재사용
네이티브 코드CPU가 직접 실행하는 기계어, VM 안 거침
크로스 컴파일Host(Mac/PC)에서 Target(폰 ARM)용 기계어 생성
ABIARM ISA 같아도 ABI 다르면 비호환 → APK에 여러 .so
정적 vs 동적개념은 공통이지만 파일 형식은 OS별로 다름
WebRTC 배포iOS=XCFramework/.framework, Android=.aar+.so 형태가 전형적 (실제 확장자는 배포 주체에 따라 다름, 코어는 같은 C++)
JNIART의 Java/Kotlin↔C++ 경계. RegisterNatives() 또는 이름 기반 discovery 사용
iOS 연결ObjC++(.mm)/Swift-C++ interop. 타입·수명 경계는 여전히 관리
래퍼앱 개발자는 Kotlin/Swift만 만짐, 바인딩·코어는 SDK가 숨김
업데이트코어가 앱에 박힘 → 재빌드 + 스토어 재배포 필요
하나의 C++ 코어
   → ABI별 크로스 컴파일
   → Android: .so + JNI + Kotlin 래퍼
   → iOS:     .framework + ObjC++/Swift + Swift 래퍼
   → 업데이트는 앱 재빌드로만

10. 한 줄 결론

libwebrtc 기반 SDK는 공통 C++ 소스를 target OS·architecture·ABI마다 빌드한다. Android는 JNI, Apple 플랫폼은 ObjC++ 또는 Swift-C++ interop으로 연결한다. 같은 ARM64라도 ABI·파일 형식이 다르면 바이너리를 공유할 수 없고, 앱에 포함한 코어를 올리려면 앱 릴리스가 필요하다.


관련 글

참고 자료

© 2026 Frank Kim. All rights reserved.