Dla małego natywnego narzędzia na Maca, zacznij od biblioteki statycznej, gdy kod jest dostarczany wraz z plikiem wykonywalnym. Wybierz bibliotekę dynamiczną, gdy użyteczny jest oddzielny komponent środowiska uruchomieniowego, a framework, gdy potrzebujesz pakietowania w formie bundla Apple. Ten przewodnik buduje ten sam pomocnik C++ obiema sposobami.

Konkretnym zadaniem jest spakowanie wielokrotnie używalnego obliczenia siatki dla lokalnego narzędzia macOS desktopowego. Przykład używa syntetycznych danych wejściowych i poleceń terminala, dzięki czemu możesz zobaczyć kompilację, łączenie i ładowanie oddzielnie przed wyborem formatu dla projektu aplikacji.

Co tak naprawdę wybierasz?

Są dwie decyzje: jak kod jest linkowany i jak jest pakowany jego dostarczalny artefakt. Biblioteka statyczna, zazwyczaj kończąca się na .a, to archiwum plików obiektowych. Biblioteka dynamiczna, kończąca się na .dylib, pozostaje osobnym obrazem środowiska uruchomieniowego. Framework to bundel organizujący bibliotekę i powiązane materiały. Apple opisuje te formaty bazowe w An Apple Library Primer.

Framework może również zawierać bibliotekę statyczną. Apple wspiera tworzenie frameworków statycznych w Xcode 15 i nowszych, w tym zasobów. W konsekwencji, sam sufiks .framework nie określa, czy biblioteka linkowana jest statycznie czy dynamicznie. Zobacz Creating a static framework.

Użyj tego poradnika decyzyjnego dla pomocnika należącego do aplikacji, który budujesz ze źródła:

Twoje wymaganie Początkowy wybór Wprowadzona odpowiedzialność
Jeden plik wykonywalny i pomocnik przebudowywany razem Statyczna .a Przelinkuj plik wykonywalny, gdy pomocnik się zmieni
Pomocnik pozostający osobnym komponentem środowiska uruchomieniowego Dynamiczna .dylib Dostarcz bibliotekę i działającą ścieżkę wyszukiwania w czasie wykonywania
Komponent Xcode z publicznymi nagłówkami i zasobami w bundlu Framework Wybierz jego linkowanie oddzielnie i skonfiguruj członkostwo w targetach
Warianty binarne dla wielu platform Apple XCFramework Zbuduj i spakuj wymagane warianty platform

To praktyczne rekomendacje wynikające z udokumentowanych formatów. Nie ustanawiają one zwycięzcy pod kątem wydajności. Dla tego narzędzia siatki, archiwum statyczne jest naturalnym punktem startowym: jest jeden klient, nie ma bundla zasobów i nie ma wymagania zastąpienia osobnej biblioteki.

XCFramework pakuje warianty binarne; nie zastępuje decyzji statyczny-versus-dynamiczny. Apple dopuszcza frameworki i biblioteki statyczne lub dynamiczne w tym formacie dystrybucji. Zobacz Creating a multiplatform binary framework bundle.

Sprawdź kompatybilność przed wyborem kontenera

Zgodna nazwa pliku lub architektura CPU jest niewystarczająca. macOS, Mac Catalyst i iOS to odrębne platformy budowania. Biblioteka arm64 skompilowana dla jednej platformy nie może zostać po prostu zlinkowana do pliku wykonywalnego innej platformy. Apple jawnie rozróżnia platformę od architektury w An Apple Library Primer.

Dla tego zadania zachowaj spójność tych danych wejściowych: target macOS, wymagana architektura CPU, minimalna wersja macOS oraz kompatybilne ustawienia języka/środowiska uruchomieniowego. Uniwersalny artefakt Mac potrzebuje pasujących wycinków zależności dla każdej obsługiwanej architektury. Building a universal macOS binary Apple obejmuje przebudowę bibliotek i plików wykonywalnych.

Kompatybilność C++ wymaga własnej uwagi. Wybór -std=c++17 wybiera tryb języka; nie certyfikuje on kompatybilności binarnej między niezależnie budowanymi komponentami. LLVM udokumentowało gwarancje ABI i wybory konfiguracyjne dostawców dla libc++, w tym ustawienia zmieniające układy typów. Dla zależności należącej do aplikacji, użytecznym domyślnym jest przebudowanie jej z łańcuchem narzędzi aplikacji i spójnymi ustawieniami. Zobacz libc++’s ABI Guarantees.

Przykład udostępnia jedną funkcję przez nagłówek kompatybilny z C, implementując ją w C++. Chroniona deklaracja extern "C" określa linkowanie języka C dla kompilacji C++. Nie przekształca ona dowolnych klas C++ w interfejsy C ani nie sprawia, by binarka była przenośna między platformami. Roboczy projekt C++ definiuje tę granicę w Linkage specifications.

Zbuduj jeden pomocnik jako bibliotekę statyczną i dynamiczną

Potrzebujesz Maca z narzędziami deweloperskimi Apple i SDK macOS. Pełne Xcode zawiera narzędzia wiersza poleceń; Apple udostępnia również samodzielny pakiet Command Line Tools. Jeśli żaden nie jest zainstalowany, uruchom xcode-select --install i zakończ instalację przed kontynuowaniem. Zobacz Installing the command-line tools.

Poniższy kompletny skrypt tworzy nowy katalog tymczasowy i używa wyłącznie syntetycznych plików źródłowych. Uruchom go w Terminalu. Jawnie celuje w architekturę bieżącego środowiska powłoki i macOS 11.0 jako ilustracyjny minimalny cel wdrożenia. To ćwiczenie dla pojedynczej architektury; użyj innego minimum, gdy Twój projekt tego wymaga.

Przeanalizowałem przykład, ale nie mogłem wykonać budowania na Macu: środowisko badawcze działa na Linuxie i nie ma clang++. Wyświetlane rezultaty to oczekiwane rezultaty, nie zarejestrowane wyjście testowe.

bash <<'SH'
set -eu

work_dir=$(mktemp -d "${TMPDIR:-/tmp}/mac-library-demo.XXXXXX")
cd "$work_dir"
printf 'Example directory: %s\n' "$work_dir"

mkdir include src build
mkdir -p build/static build/dynamic/lib

cat > include/grid.h <<'EOF'
#ifndef GRID_H
#define GRID_H

#ifdef __cplusplus
extern "C" {
#endif

/* Inputs: positive dimensions whose product fits in int. */
int grid_cells(int rows, int columns);

#ifdef __cplusplus
}
#endif

#endif
EOF

cat > src/grid.cpp <<'EOF'
#include "grid.h"

int grid_cells(int rows, int columns) {
    return rows * columns;
}
EOF

cat > src/main.cpp <<'EOF'
#include "grid.h"
#include <cstdio>

int main() {
    const int cells = grid_cells(3, 4);
    std::printf("Grid cells: %d\n", cells);
    return cells == 12 ? 0 : 1;
}
EOF

sdk_path=$(xcrun --sdk macosx --show-sdk-path)
build_arch=$(uname -m)
case "$build_arch" in
    arm64|x86_64) ;;
    *) printf 'Unexpected architecture: %s\n' "$build_arch" >&2; exit 1 ;;
esac

xcrun --sdk macosx clang++ \
    -std=c++17 -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -Iinclude \
    -c src/grid.cpp -o build/grid.o

xcrun --sdk macosx ar rcs build/static/libGrid.a build/grid.o

xcrun --sdk macosx clang++ \
    -std=c++17 -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -Iinclude \
    src/main.cpp build/static/libGrid.a -o build/static/grid-demo

xcrun --sdk macosx clang++ \
    -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -dynamiclib build/grid.o \
    -Wl,-install_name,@rpath/libGrid.dylib \
    -o build/dynamic/lib/libGrid.dylib

xcrun --sdk macosx clang++ \
    -std=c++17 -arch "$build_arch" -isysroot "$sdk_path" \
    -mmacosx-version-min=11.0 -Iinclude \
    src/main.cpp build/dynamic/lib/libGrid.dylib \
    -Wl,-rpath,@loader_path/lib -o build/dynamic/grid-demo

./build/static/grid-demo
./build/dynamic/grid-demo

xcrun --sdk macosx ar t build/static/libGrid.a
xcrun --sdk macosx otool -L build/static/grid-demo
xcrun --sdk macosx otool -L build/dynamic/grid-demo
xcrun --sdk macosx otool -l build/dynamic/grid-demo
xcrun --sdk macosx lipo -archs build/dynamic/grid-demo
SH

Oba uruchomienia powinny wydrukować Grid cells: 12. To obliczenie jest celowo małe: trzy wiersze pomnożone przez cztery kolumny. Deklarowany kontrakt wejściowy pomocnika wyklucza wymiary, których iloczyn nie mieści się w int; ten przykład nie implementuje ogólnego API walidacji wejścia.

Co demonstruje budowa statyczna

Wywołanie -c produkuje grid.o bez linkowania pliku wykonywalnego. Polecenie archiwizacji pakuje ten obiekt do libGrid.a; jego opcje r, c i s żądają wstawienia/zastąpienia, tworzenia archiwum i indeksu symboli. Clang dokumentuje etapy kompilacji w swoim compiler command guide, a LLVM wyjaśnia operacje archiwizacji w llvm-ar — LLVM archiver.

Klient linkuje używając jawnej ścieżki archiwum. To sprawia, że wybrany input jest niejednoznaczny, gdy istnieją obie wersje statyczna i dynamiczna. Oczekiwane wyświetlenie archiwum zawiera grid.o, podczas gdy wyświetlenie zależności pliku wykonywalnego nie powinno mieć zależności w czasie wykonywania od libGrid.a.

Linkowanie statyczne tutaj dotyczy pomocnika Grid. Nie oznacza to, że powstały plik wykonywalny jest niezależny od bibliotek runtime macOS; sprawdź pozostałe wpisy raportowane przez otool -L. Apple rozróżnia linker czasu budowania od loadera czasu wykonywania w An Apple Library Primer.

Co demonstruje budowa dynamiczna

Wywołanie -dynamiclib tworzy oddzielną bibliotekę. Jej nazwa instalacji to @rpath/libGrid.dylib. Plik wykonywalny rejestruje ścieżkę uruchomieniową @loader_path/lib, która, dla wpisu run-path tego pliku wykonywalnego, wskazuje na sąsiedni katalog lib. Clang wymienia odpowiednie opcje sterownika w swoim command-line argument reference.

Oczekiwane wyjście otool -L dla klienta dynamicznego zawiera @rpath/libGrid.dylib; wyjście poleceń load powinno zawierać wpis LC_RPATH z @loader_path/lib. Apple dokumentuje, jak nazwy instalacji i ścieżki uruchomieniowe plików wykonywalnych współpracują w Run-Path Dependent Libraries.

To daje konkretny test pakietowania: przenieś cały katalog build/dynamic razem, zachowując jego wewnętrzny układ, a następnie uruchom jego plik wykonywalny z nowej lokalizacji. Przeniesienie samego pliku wykonywalnego zostawia jego zależność Grid w tle. Udane linkowanie potwierdza, że wejścia budowania zostały znalezione; nie potwierdza to, że pakiet runtime jest kompletny.

Kiedy pomocnik powinien stać się frameworkiem?

Wybierz framework, gdy jego struktura bundlu służy projektowi — na przykład, gdy komponent potrzebuje publicznych nagłówków i zasobów zarządzanych razem w Xcode. Dla tego pomocnika tylko do obliczeń, framework dodaje pracę pakietowania bez zaspokojenia dodatkowego wymagania.

Dla frameworku statycznego w Xcode 15 lub nowszym, Apple dokumentuje tę sekwencję:

  1. Wybierz File > New > Target, wybierz macOS i użyj szablonu Framework.
  2. Wybierz Build Settings nowego targetu i zmień Mach-O Type na Static Library.
  3. Dodaj pliki implementacji do Compile Sources i zasoby do Copy Bundle Resources.
  4. Dodaj eksportowane nagłówki do fazy budowania Headers i dołącz publiczne nagłówki do nagłówka umbrella.
  5. Dodaj framework do aplikacji konsumującej i postępuj zgodnie z dokumentowaną konfiguracją embedding Apple.

Dla frameworku statycznego zawierającego zasoby, Apple określa Embed & Sign. Xcode pomija już zlinkowany główny binarny z osadzanego bundlu, zachowując materiał bundlu. Postępuj zgodnie z Creating a static framework zamiast traktować każdy framework statyczny jako archiwum, które nie potrzebuje osadzania.

Jeśli dystrybuujesz warianty binarne, użyj XCFramework. Dla istniejącego samodzielnego .a, Apple dokumentuje wejścia -library i -headers do xcodebuild -create-xcframework; odradza ręczne maskowanie archiwum jako framework. Pełne Xcode jest wymagane dla xcodebuild. Zobacz Creating a multiplatform binary framework bundle i Installing the command-line tools.

Jak Swift zmienia decyzję?

Swift dodaje wymagania konsumpcji modułów i ewolucji do bazowej decyzji linkowania. Stabilność ABI, stabilność modułu i ewolucja biblioteki adresują różne problemy kompatybilności; kontener .framework nie włącza ich automatycznie.

Wskazówki Swift mówią, że frameworki budowane i dystrybuowane razem z ich klientami ogólnie nie powinny włączać ewolucji biblioteki. Dla frameworku binarnego budowanego i aktualizowanego oddzielnie, zdecyduj o wsparciu ewolucji od pierwszego wydania. W Xcode, BUILD_LIBRARY_FOR_DISTRIBUTION włącza ewolucję biblioteki i stabilność modułu. Te wybory wpływają na charakterystyki wydajności i dozwolone zmiany API, jak wyjaśniono w Library Evolution in Swift.

Trzymaj te gwarancje w ich udokumentowanym zakresie platformy. Dokumentacja Swift opisuje kompatybilność binarną między kompilatorami na platformach Apple; nie rozszerzaj tej obietnicy na każdą platformę wspieraną przez Swift ani nie interpretuj jej jako pozwolenie na dowolne zmiany API. Nagłówek kompatybilny z C w tym przykładzie również nie stanowi kompletnej konfiguracji modułu Swift.

Zdiagnozuj nieudaną budowę w kolejności występowania

Użyj stadium błędu, aby zawęzić następną kontrolę:

Objaw Pierwsza kontrola Następna akcja
Kompilator nie może znaleźć grid.h Nagłówek istnieje pod include Sprawdź katalog roboczy i -Iinclude
Niezdefiniowana referencja do funkcji Grid Implementacja i deklaracja się zgadzają Potwierdź, że zamierzone archiwum lub biblioteka dynamiczna jest w poleceniu linkowania
Niezgodność architektury lub platformy Rzeczywiste wycinki zależności i platforma budowania Przebuduj zależność dla targetu macOS klienta
Plik wykonywalny linkuje, ale nie może załadować Grid Zarejestrowana zależność i ścieżka uruchomieniowa Porównaj otool -L, otool -l i układ spakowanego katalogu
Budowy Intel i Apple silicon zachowują się inaczej Pokrycie architektury każdej zależności Zinspekcjonuj każdą binarkę i przetestuj zamierzony architekturę

compiler command guide Clanga oddziela kompilację od linkowania. Run-Path Dependent Libraries Apple wyjaśnia rozpoznawanie w czasie wykonywania, podczas gdy Building a universal macOS binary dokumentuje inspekcję architektury z lipo -archs. Te różnice czynią kolejność diagnozy użyteczną: zmiana ścieżek nagłówków nie naprawi brakującej biblioteki runtime.

Dla syntetycznego narzędzia siatki, trzymaj archiwum statyczne, dopóki konkretne wymaganie pakietowania lub runtime nie uzasadni innego wyboru. Jeśli to wymaganie pojawi się, zachowaj to samo małe obliczenie jako kontrolę budowania, a następnie zweryfikuj wybraną bibliotekę, kompatybilność targetu i końcowy układ katalogu oddzielnie.

Źródła