Aby zlinkować natywny program macOS, każdy wybrany wariant biblioteki musi być zgodny z architekturą CPU i platformą Apple oraz mieć zgodny ABI języka. W tym przewodniku pokazano, jak zbudować i sprawdzić małą bibliotekę C używaną przez narzędzie wiersza poleceń w języku C++ na komputerze.

Celem jest utworzenie pliku wykonywalnego macOS z lokalnego kodu źródłowego i zdiagnozowanie niezgodnej zależności natywnej. Przykład wykorzystuje syntetyczne działania na liczbach całkowitych, nie wymaga pobierania bibliotek i przechowuje wyniki dla poszczególnych architektur osobno.

Co musi być zgodne, zanim można zlinkować bibliotekę natywną?

Sprawdź niezależnie trzy kwestie: architekturę, platformę i interfejs binarny między językami. Specyfikacja celu Clanga obejmuje więcej niż procesor; cel i środowisko wpływają na generowanie kodu oraz wybór bibliotek. Zależności również muszą być zbudowane dla zamierzonego celu. Kompilacja skrośna za pomocą Clanga.

Sprawdzenie Pytanie dotyczące tego zadania dla macOS Znaczenie niezgodności
Architektura CPU Czy zależność zawiera wariant arm64 lub x86_64, który jest linkowany? Uzyskaj lub zbuduj brakującą architekturę.
Platforma Apple Czy ten wariant został zbudowany dla natywnego macOS? Uzyskaj kompilację dla macOS; kompilacja dla iOS lub symulatora to osobny wariant.
ABI języka Czy deklaracje, konwencje wywołań i wymagane środowiska uruchomieniowe są zgodne? Popraw interfejs albo użyj obsługiwanej przez język ścieżki współdziałania.

Lista architektur nie odpowiada na pytanie o platformę. Format Mach-O firmy Apple zapisuje informacje o CPU oddzielnie od informacji o platformie użytej podczas kompilacji i definiuje odrębne identyfikatory platform dla macOS, iOS, Mac Catalyst i symulatora iOS. Zależność opisana wyłącznie jako arm64 wymaga zatem dalszej inspekcji. Definicje formatu Mach-O firmy Apple.

W tym przykładzie założenia kompilacji to natywny macOS, minimalna wersja wdrożeniowa macOS 13.0 i osobna kompilacja dla każdej architektury. Biblioteka C udostępnia interfejs C, a plik wykonywalny używa C++17. Wersja jest przykładowym wyborem, a nie powszechnym minimum dla oprogramowania na Maca.

Zbuduj kompletny przykład dla macOS z jawnymi celami

Użyj Maca z aktualną instalacją Xcode wybraną do pracy z wiersza poleceń. Apple opisuje xcrun jako mechanizm wyszukiwania narzędzi w wybranej instalacji deweloperskiej lub ich uruchamiania. Przed badaniem zmian w kodzie źródłowym sprawdź wybraną instalację. Często zadawane pytania dotyczące kompilacji z wiersza poleceń za pomocą Xcode.

Poniższe polecenia są przeznaczone dla powłoki Bash w systemie macOS. Zostały sprawdzone, ale nie zostały tutaj wykonane, ponieważ środowisko badawcze to Linux bez narzędzi Apple.

xcode-select --print-path
xcrun --sdk macosx --find clang
xcrun --sdk macosx --find clang++
xcrun --sdk macosx --show-sdk-path

Jeśli te sprawdzenia się nie powiodą, najpierw rozwiąż problem wyboru lub instalacji narzędzi deweloperskich. Do kompilacji potrzebny jest zarówno SDK macOS, jak i kompilator. SDK udostępnia nagłówki i biblioteki platformy, względem których budowany jest produkt. Dokumentacja ustawień kompilacji Xcode: Base SDK.

Utwórz pliki źródłowe

Uruchom ten blok przygotowawczy w powłoce Bash. Tworzy on nowy katalog tymczasowy i wyświetla jego lokalizację, więc istniejące pliki projektu pozostają nietknięte. Kontynuuj, uruchamiając kolejne bloki w tej samej powłoce.

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

Nagłówek deklaruje powiązanie z językiem C, gdy dołącza go jednostka translacji C++. Dzięki temu wywołujący kod C++ nie żąda nazwy funkcji C++ z innym manglingiem. FAQ C++ zaleca również, aby ostateczne linkowanie prowadził kompilator C++, dzięki czemu dołączane są wymagane biblioteki. FAQ standardu C++: Jak łączyć kod C i C++.

Skompiluj, zarchiwizuj i zlinkuj każdą architekturę

Poniższy blok buduje oba warianty dla macOS. Dla każdej architektury przekazuje ten sam cel i SDK zarówno podczas kompilowania biblioteki, jak i linkowania pliku wykonywalnego. Dokumentacja Clanga opisuje -c jako opcję tworzącą plik obiektowy; bez opcji wyboru etapu sterownik przechodzi do linkowania. Przewodnik po poleceniach kompilatora 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

Tutaj -target wybiera architekturę i minimalną wersję wdrożeniową macOS, a -isysroot wskazuje katalog główny SDK. To odrębne parametry. Jawna ścieżka archiwum pozwala też zobaczyć w poleceniu, która zależność jest wybierana. Wskazówki Clanga dotyczące kompilacji skrośnej, Dokumentacja argumentów wiersza poleceń Clanga.

Polecenie ar rcs pakuje skompilowany plik obiektowy do archiwum statycznego i tworzy indeks symboli. Archiwizacja pakuje istniejące pliki obiektowe; nie kompiluje ich dla innego celu. LLVM dokumentuje operacje na archiwach i ich zgodność z innymi implementacjami ar. Dokumentacja archiwizatora LLVM.

Dane wejściowe przykładu 3 i 4 powinny dać wynik Tiles: 12. Ta niewielka funkcja służy wyłącznie jako przykład linkowania; jej interfejs nie określa zachowania dla dowolnych wymiarów, których iloczyn przekracza zakres typu int.

Jak sprawdzić architekturę, platformę i symbole?

Sprawdź rzeczywisty plik wykonywalny, bibliotekę lub plik obiektowy. Apple dokumentuje lipo -archs jako polecenie wyświetlające architektury i informuje, że plik wykonywalny aplikacji znajduje się wewnątrz jej pakietu, w katalogu Contents/MacOS/. Budowanie uniwersalnego pliku binarnego 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

Dwa pierwsze polecenia powinny wskazać odpowiednie architektury. Aby sprawdzić platformę, przejrzyj polecenia ładowania wyświetlone przez otool -l; zgodne z nim narzędzie LLVM llvm-otool dokumentuje tę samą opcję. Narzędzie LLVM do wyświetlania zawartości Mach-O.

Szukaj LC_BUILD_VERSION. Jego pole platform powinno wskazywać macOS, a minos zapisuje minimalną wersję systemu operacyjnego. Pole sdk jest odrębne. Niektóre starsze pliki binarne używają zamiast tego polecenia LC_VERSION_MIN_*. Definicje formatu Apple opisują obie reprezentacje, w tym identyfikator platformy macOS 1. Struktury wersji kompilacji Mach-O.

W przypadku biblioteki statycznej sprawdź obiektowe pliki składowe, zamiast traktować opakowanie archiwum jako dowód zgodności platformy. W tym przykładzie zachowano tile.o właśnie w tym celu. W przypadku nieznanego archiwum użyj xcrun ar t, aby wyświetlić jego składowe, a następnie sprawdź je w osobnym katalogu roboczym. LLVM dokumentuje operacje wyświetlania i rozpakowywania archiwów. Dokumentacja archiwizatora LLVM.

Na koniec sprawdź symbole. Przykładowa biblioteka powinna definiować _tile_total w Mach-O. W tradycyjnym wyniku nm symbol T oznacza globalny symbol kodu, a U — niezdefiniowane odwołanie. Definicja i odwołanie to różne dowody. Dokumentacja tabeli symboli LLVM.

Skorzystaj z tej sekwencji diagnostycznej, aby wybrać następne działanie:

  1. Brakuje wymaganej architektury CPU: przebuduj zależność dla tej architektury lub uzyskaj zgodny plik binarny.
  2. Architektura CPU jest zgodna, ale platforma jest inna: uzyskaj wariant dla natywnego macOS. Zmiana architektury programu wywołującego nie zapewnia zgodności platformy.
  3. Architektura CPU i platforma są zgodne, ale funkcja pozostaje nierozwiązana: porównaj deklarację funkcji z symbolem eksportowanym przez bibliotekę i potwierdź, że zamierzone archiwum znajduje się w poleceniu linkowania.
  4. Nazwy różnią się między C i C++: popraw powiązanie z językiem we wspólnym nagłówku, a następnie przebuduj program wywołujący.
  5. Wersje wdrożeniowe są różne: sprawdź minimalną wersję zależności i, jeśli to obsługiwane, przebuduj ją dla zamierzonego celu wdrożeniowego albo świadomie podnieś wymagania aplikacji.

Ta sekwencja jest pomocą w podejmowaniu decyzji opracowaną na podstawie opisanej wyżej dokumentacji celów, Mach-O i linkowania. Pozwala odróżnić brakujący kod maszynowy od niezgodnych metadanych platformy i problemów z interfejsem językowym, dzięki czemu każda kolejna kompilacja odpowiada konkretnemu ustaleniu.

Co się zmienia, gdy Swift wywołuje bibliotekę natywną?

Zgodna architektura CPU i platforma nie oznaczają, że można wywołać każdy interfejs językowy. Swift domyślnie obsługuje współdziałanie z C; bezpośrednie współdziałanie z C++ trzeba włączyć, a ponadto obsługuje ono udokumentowany podzbiór funkcji C++. Nagłówek C z tego przykładu udostępnia jeden z obsługiwanych interfejsów, pod warunkiem że kompilacja Swifta importuje go za pomocą skonfigurowanego nagłówka lub mechanizmu modułów. Łączenie Swifta i C++.

W przypadku bezpośredniego interfejsu C++ Swift dokumentuje -cxx-interoperability-mode=default do kompilacji z wiersza poleceń, moduły Clanga do importowania nagłówków oraz -Xcc do przekazywania ustawień importera C++. Konfiguracja importu i linkowanie skompilowanej implementacji pozostają odrębnymi wymaganiami kompilacji. Konfigurowanie projektów Swift i C++ z kodem w wielu językach.

Przestrzegaj udokumentowanych ograniczeń: współdziałanie z C++ wymaga Swifta 5.9 lub nowszego oraz C++14 lub nowszego. Kod łączący Swift i C++ musi korzystać z tej samej biblioteki standardowej C++; na platformach Apple jest to libc++. Wywoływanie Swifta z C++ wymaga wygenerowanego interfejsu i obsługiwanego kompilatora Clang. Importowane typy referencyjne C++ wymagają też wersji wdrożeniowej macOS 13.3 lub nowszej. Przed wyborem tej ścieżki sprawdź konkretne API w aktualnej dokumentacji zgodności. Obsługiwane funkcje i ograniczenia współdziałania z C++.

Kiedy używać pliku uniwersalnego lub XCFramework?

W przypadku tego pliku wykonywalnego przeznaczonego wyłącznie dla macOS połącz dwie ukończone kompilacje dla macOS w jeden plik uniwersalny. Apple opisuje kompilowanie i linkowanie każdej architektury przed połączeniem wynikowych plików wykonywalnych za pomocą lipo. Budowanie uniwersalnego pliku binarnego 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

Na obsługiwanym Macu z systemem macOS 13.0 lub nowszym ostatnie polecenie powinno wypisać Tiles: 12 i zakończyć się powodzeniem. Zbudowanie obu wariantów różni się od ich przetestowania: Mac z procesorem Intel nie uruchomi wariantu arm64. Wskazówki Apple dotyczące plików binarnych uniwersalnych.

W przypadku biblioteki obsługującej kilka platform Apple lub wariantów platformy użyj XCFramework do spakowania osobno zbudowanych wariantów. Apple wyraźnie zaleca oddzielne przechowywanie bibliotek statycznych dla iOS i symulatora iOS, a w tym samym XCFramework można umieścić warianty dla natywnego macOS i Mac Catalyst. W przypadku kompilacji archiwizowanych w Xcode udokumentowana metoda wykorzystuje miejsce docelowe do wyboru platformy i SDK. Tworzenie wieloplatformowego pakietu binarnego frameworka.

Przy następnym błędzie linkowania na macOS zapisz zamierzony cel, sprawdź architekturę i platformę wybranej zależności, a następnie zweryfikuj interfejs językowy. Zachowaj te ustalenia wraz z dokładnym poleceniem linkowania. Dzięki temu wybór właściwego pliku binarnego lub ponowna kompilacja z kodu źródłowego będą oparte na konkretnych danych.

Źródła