Per collegare un programma macOS nativo, ogni slice selezionata della libreria deve corrispondere all’architettura della CPU e alla piattaforma Apple, con un ABI del linguaggio compatibile. Questa guida mostra come compilare e ispezionare una piccola libreria C usata da uno strumento a riga di comando desktop in C++.

L’obiettivo concreto è produrre un eseguibile macOS dal codice sorgente locale e diagnosticare una dipendenza nativa incompatibile. L’esempio usa operazioni aritmetiche sintetiche su interi, non richiede librerie scaricate e mantiene separati gli output specifici per architettura.

Che cosa deve corrispondere prima che una libreria nativa possa essere collegata?

Verifica separatamente tre aspetti: architettura, piattaforma e interfaccia binaria tra i linguaggi. La specifica di destinazione di Clang include più del processore; la destinazione e l’ambiente influiscono sulla generazione del codice e sulla selezione delle librerie. Anche le dipendenze devono essere compilate per la destinazione prevista. Compilazione incrociata con Clang.

Verifica Domanda per questa attività macOS Significato di una mancata corrispondenza
Architettura CPU La dipendenza contiene la slice arm64 o x86_64 da collegare? Procurati o compila l’architettura mancante.
Piattaforma Apple La slice è compilata per macOS nativo? Procurati una build macOS; una build iOS o Simulator è una variante distinta.
ABI del linguaggio Le dichiarazioni, le convenzioni di chiamata e i runtime richiesti sono compatibili? Correggi l’interfaccia o usa il percorso di interoperabilità supportato dal linguaggio.

L’elenco delle architetture non consente di determinare la piattaforma. Il formato Mach-O di Apple registra separatamente le informazioni sulla CPU e quelle sulla piattaforma di build, e definisce identificatori di piattaforma distinti per macOS, iOS, Mac Catalyst e iOS Simulator. Una dipendenza descritta soltanto come arm64 richiede quindi un’ulteriore ispezione. Definizioni del formato Mach-O di Apple.

Per questo esempio, il contratto di build previsto è macOS nativo, destinazione di distribuzione macOS 13.0 e una build per ogni architettura. La libreria C espone un’interfaccia C; l’eseguibile usa C++17. La versione è una scelta esemplificativa, non un requisito minimo universale per il software Mac.

Compilare un esempio macOS completo con destinazioni esplicite

Usa un Mac con un’installazione aggiornata di Xcode selezionata per lo sviluppo da riga di comando. Apple descrive xcrun come il meccanismo per trovare o eseguire strumenti nell’installazione per sviluppatori selezionata. Verifica la selezione prima di indagare sulle modifiche al codice sorgente. FAQ sulla compilazione dalla riga di comando con Xcode.

I comandi seguenti sono pensati per Bash su macOS. Sono stati esaminati, ma non eseguiti qui perché l’ambiente di ricerca è Linux e non dispone della toolchain Apple.

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

Se queste verifiche non riescono, risolvi prima il problema di selezione o installazione degli strumenti per sviluppatori. La build richiede l’SDK macOS oltre a un compilatore. Un SDK fornisce le intestazioni e le librerie della piattaforma per cui viene compilato il prodotto. Riferimento alle impostazioni di build di Xcode: SDK di base.

Creare i file sorgente

Esegui questo blocco di inizializzazione in Bash. Crea una nuova directory temporanea e ne stampa il percorso, così i file di progetto esistenti restano intatti. Continua con i blocchi successivi nella stessa shell.

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

L’intestazione dichiara il collegamento al linguaggio C quando un’unità di traduzione C++ la include. In questo modo si evita che il chiamante C++ richieda un nome di funzione C++ con una decorazione diversa. Le FAQ sul C++ raccomandano inoltre di lasciare che il compilatore C++ diriga il collegamento finale, così da includere le librerie necessarie. FAQ sul C++ standard: come combinare C e C++.

Compilare, archiviare e collegare ogni architettura

Il blocco seguente compila entrambe le varianti macOS. Per ogni architettura, passa la stessa destinazione e lo stesso SDK alla compilazione della libreria e al collegamento dell’eseguibile. Clang documenta -c come opzione che produce un file oggetto; senza un’opzione di selezione della fase, il driver prosegue con il collegamento. Guida ai comandi del compilatore 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

Qui -target sceglie l’architettura e la destinazione di distribuzione macOS, mentre -isysroot fornisce la radice dell’SDK. Sono input distinti. Il percorso esplicito dell’archivio rende visibile anche la dipendenza selezionata nel comando. Indicazioni di Clang sulla compilazione incrociata, Riferimento agli argomenti della riga di comando di Clang.

Il comando ar rcs impacchetta l’oggetto compilato in un archivio statico e fornisce un indice dei simboli. L’archiviazione impacchetta oggetti esistenti; non li compila per un’altra destinazione. LLVM documenta le operazioni sugli archivi e la loro compatibilità con altre implementazioni di ar. Documentazione dell’archiviatore LLVM.

Gli input di esempio 3 e 4 dovrebbero produrre Tiles: 12. Questa funzione essenziale serve solo come esempio di collegamento; la sua interfaccia non definisce il comportamento per dimensioni arbitrarie il cui prodotto supera l’intervallo di int.

Come si ispezionano architettura, piattaforma e simboli?

Ispeziona l’eseguibile, la libreria o il file oggetto effettivo. Apple documenta lipo -archs per elencare le architetture e segnala che l’eseguibile di un’app si trova nella directory Contents/MacOS/ del bundle. Compilare un binario macOS universale.

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

I primi due comandi dovrebbero identificare le rispettive architetture. Per verificare la piattaforma, ispeziona i comandi di caricamento stampati da otool -l; anche llvm-otool, compatibile con LLVM, documenta la stessa opzione. Strumento LLVM per il dump di Mach-O.

Cerca LC_BUILD_VERSION. Il suo platform dovrebbe identificare macOS, mentre minos registra la versione minima del sistema operativo. Il campo sdk è separato. Alcuni binari meno recenti usano invece un comando LC_VERSION_MIN_*. Le definizioni del formato di Apple documentano entrambe le rappresentazioni, incluso l’identificatore della piattaforma macOS 1. Strutture della versione di build di Mach-O.

Per una libreria statica, ispeziona gli oggetti che contiene invece di considerare l’involucro dell’archivio come prova della compatibilità della piattaforma. Questo esempio conserva tile.o a tale scopo. Se l’archivio non è familiare, usa xcrun ar t per elencarne i membri e ispezionali in una directory di lavoro separata. LLVM documenta le operazioni di elencazione ed estrazione dagli archivi. Documentazione dell’archiviatore LLVM.

Infine, controlla i simboli. La libreria di esempio dovrebbe definire _tile_total su Mach-O. Nell’output tradizionale di nm, T indica un simbolo di codice globale e U un riferimento non definito. Una definizione e un riferimento sono prove diverse. Documentazione della tabella dei simboli LLVM.

Usa questa sequenza diagnostica per scegliere l’azione successiva:

  1. Manca la slice CPU richiesta: ricompila la dipendenza per quell’architettura o procurati il binario corrispondente.
  2. La CPU corrisponde, ma la piattaforma è diversa: procurati una variante macOS nativa. Cambiare l’architettura del chiamante non garantisce la compatibilità della piattaforma.
  3. CPU e piattaforma corrispondono, ma la funzione non è risolta: confronta la funzione dichiarata con il simbolo esportato dalla libreria e verifica che l’archivio previsto compaia nel comando di collegamento.
  4. I nomi differiscono tra C e C++: correggi il collegamento al linguaggio nell’intestazione condivisa, poi ricompila il chiamante.
  5. Le versioni di distribuzione differiscono: controlla la versione minima della dipendenza e, se supportato, ricompilala per la destinazione di distribuzione prevista, oppure innalza deliberatamente il requisito dell’applicazione.

Questa sequenza è uno strumento decisionale ricavato dalla documentazione sopra riportata su destinazioni, Mach-O e collegamento. Distingue il codice macchina mancante dai metadati di piattaforma incompatibili e dai problemi dell’interfaccia tra linguaggi, così ogni ricompilazione risponde a un riscontro specifico.

Che cosa cambia quando Swift chiama la libreria nativa?

Una CPU e una piattaforma corrispondenti non rendono chiamabile qualsiasi interfaccia tra linguaggi. Swift supporta per impostazione predefinita l’interoperabilità con C; l’interoperabilità diretta con C++ deve essere abilitata e supporta un sottoinsieme documentato delle funzionalità C++. L’intestazione rivolta a C di questo esempio offre una possibile interfaccia supportata, purché la build Swift la importi tramite il meccanismo configurato per le intestazioni o i moduli. Combinare Swift e C++.

Per un’interfaccia C++ diretta, Swift documenta -cxx-interoperability-mode=default per le build da riga di comando, i moduli Clang per importare le intestazioni e -Xcc per passare le impostazioni dell’importatore C++. La configurazione dell’importazione e il collegamento dell’implementazione compilata restano requisiti di build separati. Configurare progetti Swift e C++ con linguaggi misti.

Rispetta i limiti documentati: l’interoperabilità con C++ richiede Swift 5.9 o versioni successive e C++14 o versioni successive. Il codice misto Swift e C++ deve usare la stessa libreria standard C++; sulle piattaforme Apple si usa libc++. Per chiamare Swift da C++ servono l’interfaccia generata e un compilatore Clang supportato. I tipi C++ di riferimento importati richiedono inoltre una destinazione di distribuzione macOS 13.3. Prima di scegliere questo percorso, verifica l’API specifica nella documentazione aggiornata sulla compatibilità. Funzionalità supportate e vincoli dell’interoperabilità con C++.

Quando conviene usare un binario universale o un XCFramework?

Per questo eseguibile destinato solo a macOS, combina le due build macOS completate in un binario universale. Apple documenta la compilazione e il collegamento di ciascuna architettura prima di unire gli eseguibili risultanti con lipo. Compilare un binario macOS universale.

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

Su un Mac supportato con macOS 13.0 o versioni successive, il comando finale dovrebbe stampare Tiles: 12 e terminare con esito positivo. Compilare entrambe le slice è diverso dal collaudarle entrambe: un Mac Intel non può eseguire la slice arm64. Indicazioni di Apple sui binari universali.

Per una libreria che supporta più piattaforme Apple o varianti di piattaforma, usa un XCFramework per impacchettare separatamente le varianti compilate. Apple indica esplicitamente agli sviluppatori di mantenere separate le librerie statiche iOS e iOS Simulator e supporta l’inclusione di varianti macOS native e Mac Catalyst nello stesso XCFramework. Per i flussi di lavoro di archiviazione di Xcode, l’approccio documentato usa una destinazione per selezionare la piattaforma e l’SDK. Creare un bundle framework binario multipiattaforma.

Al prossimo errore di collegamento macOS, annota la destinazione prevista, ispeziona l’architettura e la piattaforma della dipendenza selezionata, quindi controlla l’interfaccia tra linguaggi. Conserva questi riscontri accanto al comando di collegamento esatto. Avrai così una base concreta per scegliere il binario corretto o ricompilarlo dal codice sorgente.

Fonti