하나의 C++ 코어로 iOS와 Android를 — 네이티브 코드, 크로스 컴파일, .so/.a, JNI, Objective-C++
"WebRTC 엔진이 C++라는데, iOS랑 Android에서 그 코드를 같이 쓰는 건가요? 플랫폼마다 다시 짜는 거 아닌가요?" 모바일 RTC 도입 자리에서 자주 나오는 질문입니다. 답은 코어 로직을 C++ 하나로 짜고 플랫폼마다 얇은 연결 계층만 따로 붙인다는 것입니다. 왜 코어가 C++인지, ABI가 다르면 왜 .so 하나로 안 되는지, 정적과 동적 라이브러리의 진짜 구분 기준은 무엇인지, Android는 JNI로 iOS는 Objective-C++로 어떻게 잇는지, 그리고 코어가 앱에 박혀 있어 업데이트하려면 앱을 다시 빌드해야 한다는 대가까지 차근히 짚어 드립니다.
목차(28개 항목)
- 0. 핵심 명제 — 코어는 하나, 연결 계층은 플랫폼마다
1. 왜 코어가 C++인가 — 성능, GC 없음, 멀티플랫폼
2. 네이티브 코드 · 컴파일 · 크로스 컴파일 · ARM ABI
3. ★중요 정정 — `.a`(static) vs `.so`(shared)는 OS 구분이 아니다
4. Android의 연결 — JNI로 Java/Kotlin ↔ C++ 잇기
5. iOS의 연결 — Objective-C++(.mm)와 Swift-C++ Interop
- 6. 래퍼(Wrapper) — 앱 개발자가 실제로 만지는 것
7. 업데이트 = 앱 재빌드 + 스토어 재배포
- 8. 전체 그림 한 장 — 하나의 C++ 코어가 두 OS로
- 9. 암기 카드 — 한 줄 요약
- 10. 한 줄 결론
- 관련 글
- 참고 자료
"WebRTC 엔진이 C++로 짜여 있다는데, 그럼 iOS랑 Android에서 그 코드를 어떻게 같이 쓰나요? 플랫폼마다 다시 짜는 거 아닌가요?". 모바일 RTC 도입을 검토하는 자리에서 자주 받는 질문입니다. 답부터 말하면 — 코어 로직은 C++ 하나로 짜고, 플랫폼마다 얇은 연결 계층(바인딩)만 따로 붙인다. WebRTC가 정확히 이 구조로 동작합니다.
이 글은 그 구조를 처음부터 끝까지 정리합니다. 왜 코어가 C++인지, 네이티브 코드와 크로스 컴파일이 정확히 뭔지, .a와 .so의 진짜 구분 기준은 무엇인지(여기서 흔한 오해를 정정합니다), Android는 JNI로·iOS는 Objective-C++로 어떻게 C++를 부르는지를 차례로 짚습니다. 마지막으로 이 구조의 트레이드오프(업데이트하려면 앱을 다시 빌드해야 한다)까지 다룹니다.
0. 핵심 명제 — 코어는 하나, 연결 계층은 플랫폼마다
WebRTC 같은 실시간 엔진은 C++로 작성된 단일 코어를 iOS와 Android가 공유한다. 두 OS가 코드를 따로 갖는 게 아니라, 같은 C++ 소스를 각 플랫폼의 CPU 명령어 집합(ABI)에 맞춰 크로스 컴파일하고, 그 위에 OS별 언어(Android는 Java/Kotlin, iOS는 Swift/Objective-C)가 C++를 호출할 수 있도록 얇은 바인딩 계층만 따로 붙인다. Android의 바인딩은 JNI라는 표준 규격을 거치고, iOS는 Objective-C++(.mm) 또는 Swift-C++ interop으로 언어 차원에서 직접 연결한다.
흔한 오해와 정정부터 박아두겠습니다. 이 표 하나가 이 글 전체의 요약입니다.
| 오해 | 정확한 이해 |
|---|---|
| iOS와 Android는 RTC 코드를 각자 따로 갖고 있다 | ❌ 같은 C++ 코어를 공유. 다른 건 컴파일 타깃과 바인딩 계층뿐 |
.so는 Android(리눅스)용, .a는 iOS(유닉스)용 기계어다 | ❌ OS 구분이 아니다. .a=정적/.so=동적 — 링킹 방식의 차이. 두 OS 모두 둘 다 가능 |
ARM 칩이면 다 같으니 .so 하나로 모든 안드로이드폰에서 돈다 | ❌ ABI(armeabi-v7a / arm64-v8a / x86_64…)가 다르면 호환 안 됨. APK에 여러 .so를 담음 |
| C++ 코어는 OS가 알아서 자동으로 업데이트해준다 | ❌ 코어는 앱 바이너리에 박혀 배포됨. 업데이트 = 앱 재빌드 + 스토어 재배포 |
| iOS도 JNI 같은 규격이 필요하다 | ❌ iOS는 JVM이 없어 JNI 불필요. ObjC++/Swift가 C++를 언어 차원에서 직접 호출 |
1. 왜 코어가 C++인가 — 성능, GC 없음, 멀티플랫폼
실시간 미디어 엔진(WebRTC, 코덱, 에코 캔슬러, 지터 버퍼)은 밀리초 단위로 예측 가능한 성능이 생명입니다. 오디오는 보통 10ms 단위 프레임을, 비디오는 16ms(60fps)~33ms(30fps) 단위로 끊김 없이 처리해야 합니다. 이 요구가 언어 선택을 거의 강제합니다.
C++를 고르는 세 가지 이유
🟢 1. 네이티브 성능 — 하드웨어에 직접 닿는다 C++는 가상 머신이나 인터프리터를 거치지 않고 CPU 기계어로 직접 컴파일됩니다. 코덱의 DSP 루프, SIMD 벡터 연산(NEON/SSE), 메모리 정렬 같은 저수준 최적화를 그대로 표현할 수 있습니다.
🟢 2. GC(Garbage Collector) 없음 — 예측 가능한 지연 이게 핵심입니다. Java/Kotlin(JVM)이나 Swift가 아닌 이유.
정정: Swift는 GC가 아니라 ARC(Automatic Reference Counting)를 씁니다. ARC는 "stop-the-world" 일시정지가 없다는 점에서 JVM의 GC보다 실시간에 유리하지만, 참조 카운트 갱신 비용·순환 참조 관리·iOS 전용이라는 제약 때문에 멀티플랫폼 공용 코어로는 여전히 C++가 표준입니다. 즉 "GC 없음"이 C++만의 독점 특성은 아니지만, GC 없음 + 멀티플랫폼 + 저수준 제어를 동시에 만족하는 건 C/C++가 사실상 유일합니다.
🟢 3. 멀티플랫폼 단일 코어 — Write Once, Compile Everywhere C++ 소스는 ISO 표준이라 컴파일러만 있으면 어느 플랫폼이든 같은 코드를 빌드할 수 있습니다. 코어 알고리즘(코덱, 네트워크 스택, 에코 캔슬러)을 한 번 짜고 iOS·Android·Windows·macOS·Linux 모두에서 재사용합니다. WebRTC 코드베이스가 약 90% 이상 C++인 이유가 여기에 있습니다. [NEEDS VERIFICATION: GitHub 미러 기준 언어 비중은 시점에 따라 변동]
⚠️ 트레이드오프: C++는 메모리를 수동으로 관리하므로 댕글링 포인터·버퍼 오버플로우·메모리 누수 위험이 큽니다. "예측 가능한 성능"을 얻는 대가로 "메모리 안전성을 개발자가 책임진다". 그래서 최근에는 Rust 같은 대안도 부상하지만, 기존 C++ 코어의 거대한 자산과 생태계 때문에 RTC 엔진의 주류는 여전히 C++입니다.
언어 선택 비교
| 언어 | 성능 | 일시정지 위험 | 멀티플랫폼 코어 | RTC 코어로 |
|---|---|---|---|---|
| C++ | 🟢 네이티브 | 🟢 GC 없음 | 🟢 전 플랫폼 | ✅ 주류 |
| Java/Kotlin (JVM) | ⚠️ JIT | 🔴 GC stop-the-world | ❌ JVM 종속 | ❌ |
| Swift | 🟢 네이티브 | 🟢 ARC (GC 아님) | ❌ 사실상 Apple 전용 | ❌ 코어로는 부적합 |
| Rust | 🟢 네이티브 | 🟢 GC 없음 | 🟢 전 플랫폼 | ⚠️ 부상 중, 자산 부족 |
2. 네이티브 코드 · 컴파일 · 크로스 컴파일 · ARM ABI
코어가 C++라는 건 알았습니다. 그런데 "C++ 소스"가 곧바로 폰에서 도는 건 아닙니다. 사람이 읽는 텍스트일 뿐이고, CPU가 실행하려면 기계어(machine code)로 번역해야 합니다. 이 번역이 컴파일입니다.
네이티브 코드란?
네이티브 코드 = 특정 CPU가 직접 실행할 수 있는 기계어 명령어. 중간 가상 머신(JVM 등)을 거치지 않고 하드웨어에서 바로 도는 코드.
크로스 컴파일이란?
여기서 모바일 개발 특유의 문제가 생깁니다. 개발자는 Mac이나 PC에서 코드를 짜지만, 그 코드는 폰의 ARM CPU에서 돌아야 합니다. Mac의 CPU와 폰의 CPU는 명령어 집합이 다릅니다. 그래서 "내 컴퓨터와 다른 종류의 CPU용 기계어를 만드는" 컴파일이 필요합니다.
크로스 컴파일(Cross-compilation): 컴파일러가 실행되는 플랫폼(Host)과 다른 플랫폼(Target)의 실행 코드를 생성하는 것.
- Host = 컴파일러가 도는 곳 (보통 개발자의 Mac/PC)
- Target = 생성된 코드가 실행될 곳 (폰의 ARM 기기)
비유로 풀면: 한국에서 일본어 책을 인쇄하는 것과 같습니다. 인쇄기(Host)는 한국에 있지만 결과물(일본어 책)은 일본 독자(Target)를 위한 것입니다. 기술적 실체로 돌아오면 — Mac의 clang이 ARM64 명령어를 출력하면, 그 산출물은 Mac에서는 못 돌고 폰에서만 돈다. Android는 NDK(Native Development Kit), iOS는 Xcode 툴체인이 이 크로스 컴파일러를 내장하고 있습니다.
ARM ISA는 같은데 왜 하나의 바이너리로 안 되나 — ABI 문제
흔한 오해를 정정합니다.
정정: "스냅드래곤·미디어텍·Apple Silicon이 다 ARM이니까 ARM 기계어
.so하나면 모든 안드로이드폰에서 돈다" → FALSE.
ARM ISA(명령어 집합 구조)를 공유하더라도 ABI(Application Binary Interface)가 다르면 호환되지 않습니다. ABI는 명령어뿐 아니라 함수 호출 규약, 레지스터 사용법, 데이터 정렬, 32비트/64비트 여부까지 포함한 "바이너리 수준의 약속"입니다.
Android가 지원하는 주요 ABI:
| ABI | 설명 | 비트 |
|---|---|---|
armeabi-v7a | 32비트 ARM (Thumb-2) | 32-bit |
arm64-v8a | 64비트 ARM (AArch64) — 현재 권장 | 64-bit |
x86 | 32비트 인텔 (에뮬레이터 등) | 32-bit |
x86_64 | 64비트 인텔 (에뮬레이터, 일부 기기) | 64-bit |
하나의 .so 바이너리는 하나의 ABI에만 호환됩니다. ARMv8 칩(스냅드래곤 등)은 AArch32 호환 모드로 armeabi-v7a 코드를 실행할 수는 있지만, 이는 64비트 칩에서 32비트 코드를 억지로 돌리는 셈이라 비효율적입니다. 그래서 실무에서는 multi-ABI로 빌드하여 APK 안에 여러 .so를 담고, 설치 시 기기에 맞는 것만 골라 씁니다.
⚠️ 따라서 "ARM이면 다 같다"가 아니라, 같은 C++ 소스 → ABI 개수만큼 크로스 컴파일 → 각각 별도 바이너리가 정확한 그림입니다.
3. ★중요 정정 — .a(static) vs .so(shared)는 OS 구분이 아니다
이 글에서 가장 자주 틀리는 지점입니다. 명확히 정정하겠습니다.
정정: "
.so는 Android(리눅스)용,.a는 iOS(유닉스)용 기계어 파일이다" → FALSE.
.a와 .so의 구분 기준은 운영체제가 아니라 링킹(linking) 방식입니다. 둘 다 "컴파일된 기계어 묶음"이라는 점은 같고, 그것을 앱에 언제 결합하느냐가 다릅니다.
Static vs Dynamic — 진짜 구분 기준
| 항목 | .a (Static Archive) | .so (Shared Object) |
|---|---|---|
| 결합 시점 | 컴파일/링크 시점 | 런타임(실행 중) 로드 |
| 코드 위치 | 앱 바이너리 안에 복사됨 | 별도 파일로 존재, 실행 시 로드 |
| 앱 크기 | 커짐 (코드 포함) | 작아짐 (외부 참조) |
| 공유 | 불가 (각 앱이 복사본) | 여러 프로세스가 공유 가능 |
| 개념의 소속 | OS 무관 — 링킹 방식 | OS 무관 — 링킹 방식 |
비유: .a는 책 내용을 통째로 복사해서 내 보고서에 붙여넣는 것(보고서가 두꺼워지지만 독립적), .so는 참고문헌으로 링크만 걸어두는 것(보고서는 얇지만 그 문헌이 옆에 있어야 함). 기술적 실체 — Static은 링커가 .a 안의 필요한 오브젝트를 앱 실행 파일에 직접 병합하고, Dynamic은 실행 시 동적 링커가 .so를 메모리에 매핑합니다.
두 OS 모두 둘 다 지원한다 — 이게 핵심
Static (.a) | Dynamic | 비고 | |
|---|---|---|---|
| Android | ✅ 지원 (다른 라이브러리에 링크) | ✅ .so (NDK 기본 산출물) | NDK가 C/C++ 소스로 .so를 빌드 |
| iOS | ✅ 지원 (과거 표준) | ✅ .dylib / .framework | 현대는 .framework/XCFramework 권장. .framework는 패키징 형식일 뿐, 정적/동적은 빌드 설정으로 결정됨 |
Android NDK 공식 설명을 그대로 옮기면:
- Native Libraries (.so files) — NDK가 C/C++ 소스에서 빌드하는 동적 라이브러리
- Static libraries (.a files) — 다른 라이브러리에 링크해 넣을 수 있는 정적 라이브러리
즉 Android도 .a를 쓰고, iOS도 과거엔 .a 정적 라이브러리를 요구했습니다. 현대 iOS 워크플로우는 동적 .framework(또는 여러 플랫폼/아키텍처를 묶은 XCFramework)를 권장하지만, .a와 정적 프레임워크도 여전히 지원합니다.
✅ 외워둘 한 줄:
.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 래퍼 |
🔧 정정: Google은 WebRTC M80(약 2020년) 이후 모바일 prebuilt 바이너리(
GoogleWebRTCCocoaPod 등) 공식 배포를 중단했고, 현재 공식 경로는 소스 빌드입니다. XCFramework/AAR prebuilt는 대개 커뮤니티(stasel/WebRTC등)나 벤더(Agora 등) 빌드로 유통됩니다. (출처: WebRTC Native Code — iOS)
⚠️ WebRTC는 오픈소스라 배포 형태가 다양합니다. Google 소스를 빌드하면 iOS는 XCFramework, Android는 AAR+
.so형태로 산출되며, 이를 Agora·Firebase 등이 자체 래퍼와 함께 SDK로 재배포합니다. 그래서 "정확히 어떤 파일 확장자냐"는 누가 배포했느냐에 따라 달라집니다.
4. Android의 연결 — JNI로 Java/Kotlin ↔ C++ 잇기
Android 앱은 Java/Kotlin으로 작성되고 JVM(정확히는 ART, Android Runtime) 위에서 돕니다. 그런데 WebRTC 코어는 C++ 네이티브 코드입니다. JVM 세계의 Kotlin이 JVM 밖의 C++ 함수를 어떻게 부를까요? 이 다리가 JNI입니다.
JNI란?
JNI(Java Native Interface): Java/Kotlin 코드와 C/C++ 네이티브 코드가 서로 호출할 수 있게 해주는 표준 규격.
판정: TRUE — JNI는 Sun Microsystems가 설계하고 현재 Oracle(Sun 인수)이 관리하는 Java 표준입니다. 공식 사양에 따르면, JDK 1.0의 native 인터페이스, Netscape JRI, Microsoft RNI 등 서로 호환되지 않던 여러 native 인터페이스를 하나로 통일하기 위해 만들어졌습니다. 모든 JVM 구현(Oracle, OpenJDK, Android ART)에서 지원됩니다. Android NDK 문서 표현으로는 "Java와 C/C++ 컴포넌트가 서로 통신할 수 있게 하는 다리(bridge)".
JNI가 작동하는 3단계
단계별 코드
①② Kotlin: external 선언 + .so 로드
①(external 선언)과 ②(loadLibrary)는 같은 Kotlin 블록 안에서 함께 이뤄집니다.
Java로 쓰면 public native int startCall(String channelId); — 키워드만 native로 다를 뿐 같은 개념입니다.
③ C++: 이름 규칙으로 함수 구현
JNI는 함수 이름을 Java_<패키지>_<클래스>_<메서드> 규칙으로 매핑합니다. 이름이 정확히 맞아야 런타임이 연결합니다.
핵심 포인트:
extern "C"— C++ 컴파일러는 함수 이름을 변형(name mangling)하는데, JNI는 변형 안 된 C 심볼을 기대하므로 이걸로 막습니다.JNIEXPORT/JNICALL— JNI가 인식하는 함수임을 표시하는 매크로.- 타입 변환 — Kotlin의
String은 JVM 객체라 C++의char*로 쓰려면GetStringUTFChars로 변환해야 합니다. JVM 세계와 네이티브 세계는 데이터 표현이 다르기 때문입니다.
⚠️ JNI의 대가: 모든 데이터가 JVM↔네이티브 경계를 넘을 때 마샬링(변환) 비용이 듭니다. 객체 변환, 참조 관리(Local/Global Reference), 예외 전파를 직접 신경 써야 합니다. 그래서 JNI 호출은 "자주 넘나들수록 비싸지므로" 경계를 굵게(coarse-grained) 설계하는 게 정석입니다.
5. iOS의 연결 — Objective-C++(.mm)와 Swift-C++ Interop
iOS에는 JVM이 없습니다. 앱은 Swift나 Objective-C로 작성됩니다. 그래서 Android식 JNI 같은 별도 바인딩 규격이 필요 없습니다. C++와의 연결이 언어 차원에서 더 매끄럽습니다.
판정: TRUE — iOS는 JVM이 없어 Swift/Objective-C를 쓰고, C++ 연결은 Objective-C++(.mm 파일) 또는 Swift-C++ 직접 interop으로 이뤄집니다. Java-C++ 매핑 계층이 필요한 JNI보다 seamless합니다.
Objective-C++ — 한 파일에서 ObjC와 C++ 혼용
.mm 확장자 파일은 Objective-C++ 컴파일 단위입니다. 같은 파일 안에서 Objective-C 객체와 C++ 객체를 자유롭게 섞을 수 있습니다. JNI 같은 별도 규약·이름 규칙·타입 변환 함수가 필요 없습니다 — 컴파일러가 두 언어를 함께 이해하기 때문입니다.
JNI 코드(섹션 4)와 비교해보면 차이가 분명합니다: extern "C", JNIEXPORT, JNIEnv*, GetStringUTFChars/ReleaseStringUTFChars 같은 의식(ceremony)이 전혀 없습니다. C++ 헤더를 include하고 함수를 바로 호출하면 끝입니다.
Swift에서 C++ 부르기 — 두 갈래
전통적으로 Swift는 C++를 직접 보지 못해 Objective-C++ 래퍼를 한 겹 두고, Swift는 그 ObjC 인터페이스만 봤습니다. 하지만 Apple은 WWDC 2023 무렵부터 direct Swift-C++ interoperability를 도입해, 설정을 켜면 Swift가 C++ 타입·함수를 직접 다룰 수 있게 됐습니다. [NEEDS VERIFICATION: 직접 interop은 C++ 기능별로 지원 범위·성숙도가 다르므로 프로젝트에서 실제 검증 필요]
Android(JNI) vs iOS(ObjC++/Swift) 비교
| 항목 | Android | iOS |
|---|---|---|
| 앱 언어 | Java / Kotlin | Swift / Objective-C |
| 런타임 | ART(JVM류) — VM 있음 | 네이티브 — VM 없음 |
| C++ 연결 방식 | JNI (표준 규격 계층) | ObjC++(.mm) / Swift-C++ interop |
| 별도 규약 필요? | ✅ 필요 (JNIEXPORT, 이름 규칙) | ❌ 언어 차원 지원 |
| 데이터 변환 | 수동 마샬링(GetStringUTFChars 등) | 한 줄 변환 또는 자동 |
| 표현 | "다리를 놓는다(bridge)" | "같이 컴파일한다" |
✅ 한 줄: Android는 JVM↔네이티브 경계가 있어 JNI라는 다리가 필요하고, iOS는 경계가 없어 컴파일러가 ObjC++/Swift와 C++를 한 번에 처리한다. iOS 쪽이 더 매끄러운 건 "규격이 좋아서"가 아니라 "넘을 경계 자체가 없어서"다.
6. 래퍼(Wrapper) — 앱 개발자가 실제로 만지는 것
여기까지 보면 의문이 생깁니다. "그럼 RTC 앱 만들 때 매번 JNI나 .mm을 직접 짜야 하나?" 아닙니다. 그 위에 래퍼(Wrapper) 계층이 있습니다.
래퍼: 저수준 C++ 코어를 각 플랫폼의 관용적(idiomatic) API로 감싼 얇은 계층. 앱 개발자는 Kotlin/Swift 객체만 다루고, 내부의 C++ 호출은 래퍼가 숨긴다.
각 계층의 역할:
| 계층 | 역할 | 누가 작성 |
|---|---|---|
| 앱 코드 | 비즈니스 로직, 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. 업데이트 = 앱 재빌드 + 스토어 재배포
마지막으로 이 구조의 가장 중요한 운영상 트레이드오프입니다.
정정: "C++ 코어는 OS나 서버가 알아서 업데이트해준다" → FALSE.
C++ 코어(.so/.framework)는 앱 바이너리 안에 포함되어 배포됩니다. 서버에서 동적으로 받아오는 웹 자바스크립트와 근본적으로 다릅니다.
영향 비교
| 항목 | 웹(브라우저 내장) | 네이티브(C++ 코어 내장) |
|---|---|---|
| 코어 업데이트 경로 | 브라우저 자동 / JS 즉시 | 앱 재빌드 + 스토어 재배포 |
| 반영 속도 | 빠름 | 느림 (심사 + 사용자 업데이트 대기) |
| 버전 파편화 | 적음 | 🔴 큼 (구버전 앱 사용자 잔존) |
| 긴급 패치 | 즉시 가능 | 스토어 심사 주기에 묶임 |
⚠️ 실무 함의: 네이티브 앱은 사용자마다 다른 SDK 버전을 들고 있을 수 있습니다. 보안 취약점이나 코덱 버그를 고쳐도, 사용자가 앱을 업데이트하기 전엔 구버전 코어가 그대로 돕니다. 그래서 RTC SDK 도입 시 버전 강제 업데이트 정책(예: 특정 버전 미만은 접속 차단)이나 서버 측 호환성 유지를 미리 설계해야 합니다.
이것이 #43에서 다룬 "네이티브는 자체 코덱을 쓰지만 웹은 브라우저 표준에 묶인다"의 또 다른 측면입니다. 네이티브는 제어권을 얻는 대신 배포 민첩성을 잃습니다.
8. 전체 그림 한 장 — 하나의 C++ 코어가 두 OS로
9. 암기 카드 — 한 줄 요약
| 개념 | 한 줄 요약 |
|---|---|
| 왜 C++ | 네이티브 성능 + GC 없음(예측 가능) + 멀티플랫폼 단일 코어 |
| 네이티브 코드 | CPU가 직접 실행하는 기계어, VM 안 거침 |
| 크로스 컴파일 | Host(Mac/PC)에서 Target(폰 ARM)용 기계어 생성 |
| ABI | ARM ISA 같아도 ABI 다르면 비호환 → APK에 여러 .so |
.a vs .so | ⭐ OS 구분 ❌ / 정적(컴파일 시 포함) vs 동적(런타임 로드) |
| WebRTC 배포 | iOS=XCFramework/.framework, Android=.aar+.so 형태가 전형적 (실제 확장자는 배포 주체에 따라 다름, 코어는 같은 C++) |
| JNI | Java/Kotlin↔C++ 다리. Sun/Oracle 표준. native 키워드 + loadLibrary + JNIEXPORT |
| iOS 연결 | JVM 없음 → ObjC++(.mm)/Swift interop, JNI 불필요 (더 매끄러움) |
| 래퍼 | 앱 개발자는 Kotlin/Swift만 만짐, 바인딩·코어는 SDK가 숨김 |
| 업데이트 | 코어가 앱에 박힘 → 재빌드 + 스토어 재배포 필요 |
10. 한 줄 결론
iOS와 Android는 RTC 코드를 따로 갖지 않는다. 같은 C++ 코어를 ABI마다 크로스 컴파일하고, 그 위에 OS별 연결 계층만 얇게 붙인다 — Android는 JVM 경계가 있어 JNI라는 표준 다리를, iOS는 경계가 없어 ObjC++/Swift interop을 쓴다. 핵심 정정 둘: (1)
.a/.so는 iOS/Android 구분이 아니라 정적/동적 링킹의 구분이며 두 OS 모두 둘 다 쓴다. (2) ARM이라고 바이너리 하나로 다 도는 게 아니라 ABI마다 별도.so가 필요하다. 이 구조의 대가는 배포 민첩성 — 코어가 앱에 박혀 있어 업데이트하려면 앱을 재빌드해 스토어에 다시 올려야 한다.
관련 글
- #43 Agora 자체 코덱 vs Web SDK — SD-RTN, FEC, 레이어 분리 — 네이티브 vs 웹의 코덱/네트워크 레이어 분업
- #0 WebRTC란? ICE? STUN? NAT? TURN? — 이 C++ 코어가 실제로 푸는 문제의 기본기
- #23 오디오 파이프라인 — Opus·RTP·Jitter Buffer — C++ 코어 안에서 도는 실시간 처리 루프
- #28 H.264 Profile·인코더 옵션·비트레이트의 현실 — 코어가 다루는 코덱 레이어 깊게 보기
참고 자료
- Android NDK Concepts —
.so/.a, 네이티브 라이브러리 개념 - Android NDK ABI Management — armeabi-v7a / arm64-v8a / x86_64 등 ABI
- Oracle JNI Specification — Introduction — JNI 설계 배경과 표준화
- Apple — Objective-C Runtime / Documentation — Objective-C 및 ObjC++ 기반
- Apple — Swift Documentation — Swift-C++ interop 포함
- Apple — Dynamic Library Programming Topics — 정적/동적 라이브러리
- GCC Link Options — static/shared 링킹 옵션
- WebRTC Source (Google) — WebRTC C++ 코어 공식 소스
- WebRTC Mirror (GitHub) — 코드베이스 언어 구성 참고
- Cross compiler (Wikipedia) — 크로스 컴파일 정의