Swift 6.3 이상에서는 전역 함수에 @c를 표시하고, 호환성 헤더를 생성한 다음, 호출 코드를 C로 컴파일하고 swiftc로 링크합니다. 이 안내에서는 각 경계를 보여 주는 작은 네이티브 Mac 명령줄 프로그램을 빌드합니다.

이 안내의 목적은 명확합니다. 일반 .c 파일을 Objective-C나 C++로 컴파일하지 않고 Swift 계산 함수를 호출하는 것입니다. Swift 공식 릴리스 문서에 따르면 @c는 Swift 6.3에서 도입된 지원되는 C 상호 운용 기능입니다. 실험적 특성에 관한 오래된 조언을 따르기 전에 설치된 컴파일러를 확인하세요. Swift 6.3 출시

Swift 함수를 C에서 호출할 수 있게 만드는 요소는 무엇인가요?

세 가지가 서로 맞아야 합니다. C 컴파일러가 읽는 선언, 구현에서 사용하는 호출 규약, 그리고 링커에 제공하는 오브젝트 파일입니다. 헤더는 첫 번째 요소를 해결하지만, 빠진 구현을 제공하거나 기존 함수의 ABI를 바꾸지는 않습니다.

@c 특성은 전역 Swift 함수에 C 호출 규약을 적용하고, 서명을 C로 표현 가능한 형식으로 제한합니다. 선택적 특성 인수로 C 이름을 지정할 수 있습니다. Swift는 생성된 호환성 헤더의 C 호환 부분에 해당 선언을 내보냅니다. 이러한 동작은 구현된 Swift Evolution 제안에 명시되어 있습니다. C 호환 함수 및 열거형

이 예제에서는 C 정수를 받아 반환하는 인터페이스를 사용합니다. 예시 계산은 화면에 표시할 백분율을 0부터 100까지의 양 끝값을 포함하는 범위로 제한합니다. 조율해야 할 버퍼, 할당된 결과, 객체 수명은 없습니다. 따라서 첫 빌드에서는 컴파일과 링크에만 집중할 수 있습니다.

먼저 Mac 도구 체인을 확인하세요

Clang, macOS SDK, Swift 6.3 이상을 갖춘 macOS 개발 환경이 필요합니다. Apple은 Xcode와 함께 명령줄 도구를 제공하며, 별도의 Command Line Tools 패키지도 제공합니다. 이미 Xcode가 있다면 별도의 도구 설치는 필요하지 않다고 Apple은 안내합니다. 명령줄 도구 설치하기

터미널에서 다음을 확인하세요.

xcode-select --print-path
xcrun --sdk macosx swiftc --version
xcrun --sdk macosx clang --version

Swift 컴파일러 버전을 직접 확인하세요. 이전 버전 컴파일러는 최신 SDK를 설치해도 @c 기능을 지원하게 되지 않습니다. Xcode 버전이 여러 개 설치되어 있다면 Apple 문서에 따라 Xcode > Settings > Locations > Command Line Tools에서 활성 버전을 선택하거나, 셸 세션에서 DEVELOPER_DIR로 재정의할 수 있습니다. 명령줄 도구 설정 구성하기

아래 명령은 선택된 개발 도구를 실행하기 위해 xcrun를 사용합니다. Apple은 명령줄 빌드 참조 문서에서 이 도구 검색 기능을 설명합니다. 명령줄에서 Xcode로 빌드하기 FAQ

빈 작업 디렉터리를 만들고 아래 두 소스 파일을 모두 그 안에 저장하세요. 해당 디렉터리에서 빌드 명령을 실행합니다. 이 빌드는 현재 Mac용 네이티브 빌드입니다. 유니버설 바이너리를 만들거나 다른 플랫폼을 구성하지 않습니다.

Swift 구현과 C 호출 코드 작성하기

내보낼 Swift 함수 정의하기

다음 Swift 소스 전체를 Percent.swift라는 이름으로 저장하세요.

@c(demo_clamp_percent)
public func clampPercent(_ value: CInt) -> CInt {
    if value < 0 {
        return 0
    }
    if value > 100 {
        return 100
    }
    return value
}

Swift 이름은 clampPercent이고, C에서 사용하는 이름은 demo_clamp_percent입니다. CInt는 C의 int에 대응합니다. 이처럼 명시적으로 대응하면 Swift Int가 C의 int을 뜻한다고 가정하지 않아도 됩니다. 문서에 따르면 Swift Int의 대응 형식은 ptrdiff_t입니다. C 호환 함수 및 열거형

이 함수는 값을 비교하고 입력값이나 상수 중 하나를 반환합니다. 계산 과정에서 백분율 범위를 벗어난 입력을 포함해 산술 오버플로가 발생하지 않습니다. 내보내는 이름의 접두사는 규모가 더 큰 프로그램에서 이 인터페이스를 다른 함수와 구분하는 규칙의 예입니다.

완전한 C 프로그램에서 함수 호출하기

이 파일을 main.c이라는 이름으로 저장하세요. 빌드 과정에서 포함되는 헤더가 생성됩니다.

#include <stddef.h>
#include <stdio.h>
#include "Percent-Swift.h"

int main(void) {
    const int inputs[] = {-12, 42, 140};
    const int expected[] = {0, 42, 100};
    const size_t count = sizeof inputs / sizeof inputs[0];

    for (size_t i = 0; i < count; ++i) {
        const int actual = demo_clamp_percent(inputs[i]);
        printf("%d -> %d\n", inputs[i], actual);
        if (actual != expected[i]) {
            fprintf(stderr, "Unexpected result at index %zu\n", i);
            return 1;
        }
    }
    return 0;
}

세 입력값은 하한, 그대로 유지되는 값, 상한을 각각 시험합니다. 프로그램은 결과를 명시적으로 확인하고 값이 다르면 0이 아닌 상태 코드를 반환하므로, 숫자만 출력하는 예제보다 유용한 결과를 제공합니다.

헤더 생성, C 컴파일, 프로그램 링크하기

두 파일이 있는 디렉터리에서 셸을 열고 다음 순서를 실행하세요. 각 빌드 단계가 실패하면 다음 단계로 진행하지 않습니다.

(
    set -eu
    SDK_PATH="$(xcrun --sdk macosx --show-sdk-path)"

    xcrun --sdk macosx swiftc \
        -sdk "$SDK_PATH" \
        -parse-as-library \
        -module-name Percent \
        -emit-object Percent.swift \
        -emit-objc-header-path Percent-Swift.h \
        -o Percent.o

    xcrun --sdk macosx clang \
        -isysroot "$SDK_PATH" \
        -x c -std=c11 \
        -Wall -Wextra -Werror \
        -c main.c -o main.o

    xcrun --sdk macosx swiftc \
        -sdk "$SDK_PATH" \
        main.o Percent.o \
        -o percent-demo

    ./percent-demo
)

첫 번째 명령은 Swift 오브젝트와 호환성 헤더를 생성합니다. Swift 드라이버에서 -emit-object는 오브젝트 파일 출력 옵션이며, -parse-as-library는 소스를 스크립트가 아닌 라이브러리로 파싱하도록 지정합니다. 이렇게 하면 실행 진입점은 C 파일이 담당합니다. Swift 컴파일러 드라이버 옵션

이름은 역사적으로 남아 있지만, -emit-objc-header-path는 @c 선언을 위한 헤더의 C 부분도 요청합니다. 구현된 제안은 이 목적으로 해당 플래그를 계속 사용하도록 명시합니다. 생성된 헤더를 직접 작성한 예상 프로토타입으로 대체하지 마세요. C 호환 함수 및 열거형

두 번째 명령은 소스 언어를 확인하는 핵심 단계입니다. Clang 문서에서 -x는 입력 언어를 선택하고 -c는 링크 없이 오브젝트 파일을 생성하는 옵션입니다. 여기서는 -x c -std=c11을 사용해 실제 생성된 헤더를 읽으면서 호출 코드를 C11로 컴파일합니다. Clang 명령 가이드

마지막 컴파일러 호출은 Swift 드라이버를 사용해 두 오브젝트 파일을 링크합니다. Swift 컴파일러 가이드에서는 하위 수준 링크 명령을 직접 조합하는 대신 파일 링크를 swiftc에 요청하라고 권장합니다. 컴파일러 디버깅

예제의 분기를 바탕으로 예상되는 출력은 다음과 같습니다.

-12 -> 0
42 -> 42
140 -> 100

이 명령은 검색한 문서를 기준으로 검토했지만 여기서 실행하지는 않았습니다. 조사 환경은 Linux이며 swiftc도 Clang도 설치되어 있지 않습니다. 위 출력은 예상 결과이며, Mac에서 실제로 시험한 결과가 아닙니다.

기존 코드에 맞는 인터페이스 선택하기

Swift 6.3 이상에서 작고 새로운 인터페이스를 만든다면 위의 생성 헤더 방식으로 선언을 구현과 직접 연결할 수 있습니다. 릴리스 문서는 다른 방향도 지원합니다. @c @implementation은 가져온 C 헤더에 이미 선언된 함수를 구현하며, Swift가 서명이 일치하는지 확인합니다. 기존 C API를 인터페이스 계약으로 유지해야 할 때 유용합니다. Swift 6.3 출시

주변 프로젝트가 이미 C++를 통해 Swift를 호출한다면 Swift에서 생성한 C++ 바인딩을 이용할 수도 있습니다. 공개 Swift 함수는 모듈 네임스페이스 안의 인라인 C++ 함수로 표현됩니다. C 호출 코드를 사용하려면 C++로 컴파일되는 별도의 어댑터를 만들어 C용 선언을 노출해야 합니다. C 번역 단위는 해당 어댑터의 C 헤더를 포함합니다. Swift와 C++ 혼합하기

이러한 어댑터에서 extern "C"는 C++ 선언이나 정의에 C 언어 링크를 지정합니다. C++ 헤더의 내용을 C 문법으로 바꾸지는 않습니다. C++ 초안은 링크 지정자를 정의하고 언어 링크와 일반 선언을 구분합니다. C++ 초안: 링크 지정자

이 안내에서는 이전 컴파일러를 @c를 지원하는 버전으로 업그레이드하는 것이 직접적이고 지원되는 방법입니다. 제안 문서에서는 @_cdecl을 실험적 선행 기능으로 설명합니다. 새 예제에 그 오래된 표기를 그대로 복사하면 중요한 버전 차이가 가려집니다. C 호환 함수 및 열거형

오류가 발생한 경계에서 문제 진단하기

가장 먼저 실패한 빌드 단계를 기준으로 조사 범위를 좁히세요. 헤더 생성 실패, C 파서 오류, 미해결 심볼은 각각 다른 방식으로 해결해야 합니다.

첫 번째 실패 다음 확인 사항
Swift가 @c를 거부함 선택된 Swift 컴파일러 버전을 다시 확인하세요. 이 예제에는 Swift 6.3 이상이 필요합니다.
C가 Percent-Swift.h를 찾지 못함 Swift 명령이 성공했는지, 두 파일이 의도한 디렉터리에서 빌드되는지 확인하세요.
C가 demo_clamp_percent를 찾지 못함 새로 생성된 헤더를 살펴보고 특성 표기를 확인하세요. 소스를 바꾼 뒤에는 다시 빌드하세요.
C에서 C++ 또는 Objective-C 문법 오류가 발생함 어떤 헤더를 포함했는지, 어떤 도구 체인이 생성했는지 확인하세요. 조사 중에도 명시적인 C 컴파일을 유지하세요.
링크 단계에서 내보낸 함수를 찾지 못함 최종 명령에 Percent.o가 포함되었고 현재 Swift 소스로 다시 빌드되었는지 확인하세요.
실행 파일에서 예상과 다른 결과가 나옴 출력된 입력값과 결과를 읽고 Swift의 분기와 비교하세요.

이 순서는 Clang이 문서화한 컴파일러 단계를 따릅니다. 파싱으로 소스가 유효한지 확인하고, 오브젝트 생성으로 컴파일된 코드를 만든 다음, 링크로 컴파일된 입력들을 결합합니다. 따라서 C 컴파일만 성공해도 작업의 일부만 확인된 것입니다. Clang 명령 가이드

예제를 수정할 때는 C에서 호출할 함수를 작게 유지하고 새 매개변수마다 명확한 계약을 정의하세요. 텍스트는 인코딩과 길이 처리 방식을 정하고, 버퍼는 래퍼를 작성하기 전에 용량과 소유권을 정하세요. 이러한 인터페이스 설계는 정수만 사용하는 이 예제의 범위를 벗어납니다.

완료된 로컬 점검에서 관찰할 결과는 세 가지입니다. 명시적 C 컴파일 중 생성된 헤더가 허용되고, 두 구현이 하나의 실행 파일로 링크되며, 호출 코드가 지정한 입력값을 확인합니다. 예시 계산을 직접 작성한 Swift 함수로 바꾸더라도 이 점검을 유지하세요.

출처