네이티브 macOS 프로그램을 링크하려면 선택한 모든 라이브러리 슬라이스가 CPU 아키텍처와 Apple 플랫폼이 일치해야 하며, 언어 ABI도 호환되어야 합니다. 이 가이드에서는 C++ 데스크톱 명령줄 도구에서 사용하는 작은 C 라이브러리를 빌드하고 검사하는 방법을 보여 줍니다.
구체적인 작업은 로컬 소스에서 macOS 실행 파일을 만들고 호환되지 않는 네이티브 종속성을 진단하는 것입니다. 이 예제는 합성 정수 연산을 사용하고, 다운로드한 라이브러리가 필요하지 않으며, 아키텍처별 출력을 분리해 둡니다.
네이티브 라이브러리를 링크하기 전에 무엇이 일치해야 하나요?
아키텍처, 플랫폼, 언어 간 바이너리 인터페이스를 각각 확인하세요. Clang의 대상 지정에는 프로세서 외의 정보도 포함됩니다. 대상과 환경은 코드 생성 및 라이브러리 선택에 영향을 줍니다. 종속성도 의도한 대상으로 빌드해야 합니다. Clang을 사용한 크로스 컴파일.
| 확인 항목 | 이 macOS 작업에서 확인할 질문 | 불일치의 의미 |
|---|---|---|
| CPU 아키텍처 | 종속성에 링크할 arm64 또는 x86_64 슬라이스가 포함되어 있나요? |
누락된 아키텍처를 구하거나 빌드합니다. |
| Apple 플랫폼 | 해당 슬라이스가 네이티브 macOS용으로 빌드되었나요? | macOS 빌드를 구합니다. iOS 또는 Simulator 빌드는 별도의 변형입니다. |
| 언어 ABI | 선언, 호출 규약, 필수 런타임이 서로 일치하나요? | 인터페이스를 수정하거나 해당 언어에서 지원하는 상호 운용 경로를 사용합니다. |
아키텍처 목록만으로는 플랫폼을 확인할 수 없습니다. Apple의 Mach-O 형식은 CPU 정보를 플랫폼 빌드 정보와 별도로 기록하고, macOS, iOS, Mac Catalyst, iOS Simulator에 서로 다른 플랫폼 식별자를 정의합니다. 따라서 arm64로만 설명된 종속성은 추가 검사가 필요합니다. Apple의 Mach-O 형식 정의.
이 예제의 빌드 계약은 네이티브 macOS, 배포 대상 macOS 13.0, 아키텍처별 빌드 하나씩입니다. C 라이브러리는 C 인터페이스를 제공하고 실행 파일은 C++17을 사용합니다. 버전은 예시를 위한 선택이며, Mac 소프트웨어에 보편적으로 적용되는 최소 버전은 아닙니다.
명시적 대상으로 완전한 macOS 예제 빌드하기
명령줄 개발에 맞게 현재 Xcode 설치가 선택된 Mac을 사용하세요. Apple은 선택된 개발자 설치에서 도구를 찾거나 실행하는 방법으로 xcrun를 안내합니다. 소스 변경 사항을 조사하기 전에 선택 상태를 확인하세요. Xcode FAQ: 명령줄에서 빌드하기.
아래 명령은 macOS의 Bash용으로 작성했습니다. 조사 환경은 Linux이며 Apple 도구 체인이 없으므로 여기서는 명령을 검토만 하고 실행하지 않았습니다.
xcode-select --print-path
xcrun --sdk macosx --find clang
xcrun --sdk macosx --find clang++
xcrun --sdk macosx --show-sdk-path
이 확인에 실패하면 먼저 개발자 도구 선택이나 설치 문제를 해결하세요. 빌드에는 컴파일러뿐 아니라 macOS SDK도 필요합니다. SDK는 제품을 빌드할 때 사용하는 플랫폼 헤더와 라이브러리를 제공합니다. Xcode 빌드 설정 참조: 기본 SDK.
소스 파일 만들기
Bash에서 이 설정 블록을 실행하세요. 새 임시 디렉터리를 만들고 위치를 출력하므로 기존 프로젝트 파일은 그대로 유지됩니다. 이후 블록도 같은 셸에서 이어서 실행하세요.
set -euo pipefail
example_dir="$(mktemp -d "${TMPDIR:-/tmp}/mac-link-demo.XXXXXX")"
cd "$example_dir"
printf 'Example directory: %s\n' "$example_dir"
mkdir -p include src build/arm64 build/x86_64
cat > include/tile.h <<'EOF'
#ifndef TILE_H
#define TILE_H
#ifdef __cplusplus
extern "C" {
#endif
int tile_total(int rows, int columns);
#ifdef __cplusplus
}
#endif
#endif
EOF
cat > src/tile.c <<'EOF'
#include "tile.h"
int tile_total(int rows, int columns)
{
return rows * columns;
}
EOF
cat > src/main.cpp <<'EOF'
#include <cstdio>
#include "tile.h"
int main()
{
const int total = tile_total(3, 4);
std::printf("Tiles: %d\n", total);
return total == 12 ? 0 : 1;
}
EOF
헤더는 C++ 번역 단위에서 포함될 때 C 언어 연결을 선언합니다. 따라서 C++ 호출자가 이름 맹글링이 다른 C++ 함수 이름을 요청하지 않습니다. C++ FAQ에서도 필요한 라이브러리가 포함되도록 최종 링크를 C++ 컴파일러가 수행하게 할 것을 권장합니다. Standard C++ FAQ: C와 C++ 혼합 방법.
각 아키텍처 컴파일, 아카이브, 링크하기
다음 블록은 두 macOS 변형을 모두 빌드합니다. 각 아키텍처에 대해 라이브러리 컴파일과 실행 파일 링크에 동일한 대상 및 SDK를 전달합니다. Clang 문서에 따르면 -c는 오브젝트 파일을 생성하며, 단계를 선택하는 옵션이 없으면 드라이버는 링크까지 진행합니다. Clang 컴파일러 명령 가이드.
mac_sdk="$(xcrun --sdk macosx --show-sdk-path)"
for cpu in arm64 x86_64; do
mac_target="${cpu}-apple-macos13.0"
xcrun --sdk macosx clang \
-target "$mac_target" -isysroot "$mac_sdk" \
-std=c17 -Wall -Wextra -Werror \
-I include -c src/tile.c \
-o "build/$cpu/tile.o"
xcrun ar rcs "build/$cpu/libtile.a" "build/$cpu/tile.o"
xcrun --sdk macosx clang++ \
-target "$mac_target" -isysroot "$mac_sdk" \
-std=c++17 -stdlib=libc++ -Wall -Wextra -Werror \
-I include src/main.cpp "build/$cpu/libtile.a" \
-o "build/$cpu/tile-demo"
done
여기서 -target은 아키텍처와 macOS 배포 대상을 선택하고, -isysroot는 SDK 루트를 지정합니다. 서로 별개의 입력입니다. 명시적인 아카이브 경로를 지정하면 명령에서 선택한 종속성도 확인할 수 있습니다. Clang 크로스 컴파일 안내, Clang 명령줄 인수 참조.
ar rcs 명령은 컴파일된 오브젝트를 정적 아카이브로 묶고 심볼 인덱스를 제공합니다. 아카이브는 기존 오브젝트를 묶는 기능이며, 다른 대상으로 컴파일하지는 않습니다. LLVM 문서에서는 아카이브 작업과 다른 ar 구현과의 호환성을 설명합니다. LLVM 아카이버 문서.
예제 입력 3과 4은 Tiles: 12를 출력해야 합니다. 이 작은 함수는 링크 예제일 뿐이며, 인터페이스는 곱이 int의 범위를 초과하는 임의의 크기에 대해 동작을 정의하지 않습니다.
아키텍처, 플랫폼, 심볼은 어떻게 검사하나요?
실제 실행 파일, 라이브러리 또는 오브젝트 파일을 검사하세요. Apple은 아키텍처 목록을 확인하는 방법으로 lipo -archs를 안내하며, 앱 실행 파일은 번들의 Contents/MacOS/ 디렉터리에 있다고 설명합니다. 유니버설 macOS 바이너리 빌드하기.
xcrun lipo -archs build/arm64/tile-demo
xcrun lipo -archs build/x86_64/tile-demo
xcrun otool -l build/arm64/tile.o
xcrun otool -l build/arm64/tile-demo
xcrun nm -g build/arm64/libtile.a
처음 두 명령은 각각 해당 아키텍처를 표시해야 합니다. 플랫폼을 확인하려면 otool -l이 출력하는 로드 명령을 살펴보세요. LLVM에서 호환 도구로 제공하는 llvm-otool도 같은 옵션을 설명합니다. LLVM Mach-O 덤프 도구.
LC_BUILD_VERSION을 찾으세요. 해당 명령의 platform은 macOS를 식별해야 하고, minos는 최소 운영 체제 버전을 기록합니다. sdk 필드는 별개입니다. 일부 이전 바이너리는 대신 LC_VERSION_MIN_* 명령을 사용합니다. Apple의 형식 정의는 macOS 플랫폼 식별자 1을 포함해 두 표현을 모두 설명합니다. Mach-O 빌드 버전 구조.
정적 라이브러리는 아카이브 래퍼를 플랫폼 호환성의 증거로 간주하지 말고 멤버 오브젝트를 검사하세요. 이 예제에서는 그 목적으로 tile.o를 보존합니다. 익숙하지 않은 아카이브라면 xcrun ar t로 멤버를 나열하고 별도의 작업 디렉터리에서 검사하세요. LLVM 문서에는 아카이브 목록 조회 및 추출 작업이 설명되어 있습니다. LLVM 아카이버 문서.
마지막으로 심볼을 확인하세요. 예제 라이브러리는 Mach-O에서 _tile_total을 정의해야 합니다. 기존 nm 출력에서 T는 전역 코드 심볼을, U은 정의되지 않은 참조를 나타냅니다. 정의와 참조는 서로 다른 증거입니다. LLVM 심볼 테이블 문서.
다음 진단 순서에 따라 조치를 선택하세요.
- 필요한 CPU 슬라이스가 없음: 해당 아키텍처용 종속성을 다시 빌드하거나 일치하는 바이너리를 구합니다.
- CPU는 일치하지만 플랫폼이 다름: 네이티브 macOS 변형을 구합니다. 호출자의 아키텍처를 바꿔도 플랫폼 호환성이 확보되지는 않습니다.
- CPU와 플랫폼은 일치하지만 함수를 찾을 수 없음: 선언된 함수와 라이브러리의 내보낸 심볼을 비교하고, 의도한 아카이브가 링크 명령에 포함되었는지 확인합니다.
- C와 C++에서 이름이 다름: 공유 헤더의 언어 연결을 수정한 뒤 호출자를 다시 빌드합니다.
- 배포 버전이 다름: 종속성의 최소 버전을 확인하고, 지원되는 경우 의도한 배포 대상으로 다시 빌드하거나 애플리케이션의 요구 버전을 의도적으로 높입니다.
이 순서는 앞서 언급한 대상, Mach-O, 링크 문서를 바탕으로 한 의사 결정 도구입니다. 누락된 기계 코드와 호환되지 않는 플랫폼 메타데이터, 언어 인터페이스 문제를 구분하므로 각 재빌드가 구체적으로 확인된 문제를 해결합니다.
Swift에서 네이티브 라이브러리를 호출하면 무엇이 달라지나요?
CPU와 플랫폼이 일치한다고 해서 모든 언어 인터페이스를 호출할 수 있는 것은 아닙니다. Swift는 기본적으로 C 상호 운용을 지원합니다. C++ 상호 운용은 별도로 활성화해야 하며, 문서화된 C++ 기능의 일부만 지원합니다. 이 예제의 C용 헤더는 Swift 빌드에서 구성된 헤더 또는 모듈 방식을 통해 가져오면 지원되는 인터페이스 선택지가 됩니다. Swift와 C++ 혼합.
직접 C++ 인터페이스를 사용하려면 Swift는 명령줄 빌드용 -cxx-interoperability-mode=default, 헤더 가져오기를 위한 Clang 모듈, C++ 임포터 설정 전달용 -Xcc를 설명합니다. 가져오기 구성과 컴파일된 구현의 링크는 별도의 빌드 요구 사항입니다. Swift 및 C++ 혼합 언어 프로젝트 설정하기.
문서에 명시된 제한 사항을 지키세요. C++ 상호 운용에는 Swift 5.9 이상과 C++14 이상이 필요합니다. Swift와 C++ 코드는 동일한 C++ 표준 라이브러리를 사용해야 하며, Apple 플랫폼에서는 libc++를 사용합니다. C++에서 Swift를 호출하려면 생성된 인터페이스와 지원되는 Clang 컴파일러가 필요합니다. 가져온 C++ 참조 타입에는 macOS 13.3 배포 요구 사항도 추가로 적용됩니다. 이 경로를 선택하기 전에 현재 호환성 문서에서 해당 API를 확인하세요. C++ 상호 운용의 지원 기능 및 제약 사항.
유니버설 바이너리나 XCFramework는 언제 사용해야 하나요?
이 macOS 전용 실행 파일에는 완성된 macOS 빌드 두 개를 유니버설 바이너리로 병합하세요. Apple은 아키텍처별로 컴파일하고 링크한 다음 lipo으로 결과 실행 파일을 결합하는 방법을 설명합니다. 유니버설 macOS 바이너리 빌드하기.
xcrun lipo -create \
build/arm64/tile-demo \
build/x86_64/tile-demo \
-output build/tile-demo
xcrun lipo -archs build/tile-demo
./build/tile-demo
지원되는 Mac에서 macOS 13.0 이상을 실행 중이라면 마지막 명령은 Tiles: 12를 출력하고 성공을 반환해야 합니다. 두 슬라이스를 빌드하는 것과 두 슬라이스를 모두 테스트하는 것은 다릅니다. Intel Mac에서는 arm64 슬라이스를 실행할 수 없습니다. Apple의 유니버설 바이너리 안내.
여러 Apple 플랫폼 또는 플랫폼 변형을 지원하는 라이브러리에는 별도로 빌드한 변형을 XCFramework로 패키징하세요. Apple은 iOS 및 iOS Simulator 정적 라이브러리를 분리해 두도록 명시하고, 네이티브 macOS 및 Mac Catalyst 변형을 동일한 XCFramework에 포함하는 방법도 지원합니다. Xcode 아카이브 작업 흐름에서는 대상을 지정해 플랫폼과 SDK를 선택하는 방법을 문서화하고 있습니다. 다중 플랫폼 바이너리 프레임워크 번들 만들기.
다음 macOS 링크 오류가 발생하면 의도한 대상을 기록하고, 선택한 종속성의 아키텍처와 플랫폼을 검사한 다음 언어 인터페이스를 확인하세요. 그 결과를 정확한 링크 명령과 함께 보관하세요. 그러면 올바른 바이너리를 선택하거나 소스에서 다시 빌드할 근거가 마련됩니다.
출처
Clang을 사용한 크로스 컴파일 — LLVM 프로젝트.
Xcode FAQ: 명령줄에서 빌드하기 — Apple.
빌드 설정 참조 — Apple.
clang — Clang C, C++, Objective-C 컴파일러 — LLVM 프로젝트.
Clang 명령줄 인수 참조 — LLVM 프로젝트.
Standard C++: C와 C++ 혼합 방법 — Standard C++ Foundation.
llvm-ar — LLVM 아카이버 — LLVM 프로젝트.
llvm-otool — Mach-O 덤프 도구 — LLVM 프로젝트.
llvm-nm — LLVM 비트코드 및 오브젝트 파일의 심볼 테이블 목록 — LLVM 프로젝트.
Mach-O 형식 정의: loader.h — Apple.
Swift와 C++ 혼합 — Swift.org.
Swift 및 C++ 혼합 언어 프로젝트 설정하기 — Swift.org.
C++ 상호 운용의 지원 기능 및 제약 사항 — Swift.org.
유니버설 macOS 바이너리 빌드하기 — Apple.
다중 플랫폼 바이너리 프레임워크 번들 만들기 — Apple.