Pour un petit utilitaire Mac natif, commencez par une bibliothèque statique lorsque le code est livré avec l’exécutable. Choisissez une bibliothèque dynamique lorsqu’un composant d’exécution distinct est utile, et un framework lorsque vous avez besoin de l’empaquetage en bundle d’Apple. Ce guide compile le même utilitaire C++ de deux façons.
La tâche concrète consiste à empaqueter un calcul de grille réutilisable pour un utilitaire de bureau macOS local. L’exemple utilise des entrées synthétiques et des commandes de terminal afin que vous puissiez observer séparément la compilation, l’édition de liens et le chargement avant de choisir un format pour un projet d’application.
Que choisissez-vous exactement ?
Il y a deux décisions à prendre : la façon dont le code est lié et la façon dont le livrable est empaqueté. Une bibliothèque statique, généralement avec l’extension .a, est une archive de fichiers objet. Une bibliothèque dynamique, avec l’extension .dylib, reste une image d’exécution distincte. Un framework est un bundle qui organise une bibliothèque et les éléments associés. Apple décrit ces formats sous-jacents dans An Apple Library Primer.
Un framework peut également contenir une bibliothèque statique. Apple permet de créer des frameworks statiques dans Xcode 15 et les versions ultérieures, y compris avec des ressources. Par conséquent, le suffixe .framework ne suffit pas à déterminer si la bibliothèque est liée de façon statique ou dynamique. Voir Creating a static framework.
Utilisez ce guide de décision pour un utilitaire appartenant à l’application et compilé à partir des sources :
| Votre besoin | Choix initial | Responsabilité associée |
|---|---|---|
| Un exécutable et un utilitaire recompilés ensemble | Statique .a |
Relier de nouveau l’exécutable lorsque l’utilitaire change |
| Un utilitaire qui reste un composant d’exécution distinct | Dynamique .dylib |
Fournir la bibliothèque et un chemin de recherche à l’exécution fonctionnel |
| Un composant Xcode avec des en-têtes publics et des ressources intégrées | Framework | Choisir séparément son type de liaison et configurer l’appartenance aux cibles |
| Des variantes binaires pour plusieurs plateformes Apple | XCFramework | Compiler et empaqueter les variantes requises pour les plateformes |
Ce sont des recommandations pratiques fondées sur les formats documentés. Elles ne désignent pas de gagnant en matière de performances. Pour cet utilitaire de grille, une archive statique est le choix de départ naturel : il n’y a qu’un seul client, aucun bundle de ressources et aucune exigence de remplacement d’une bibliothèque distincte.
Un XCFramework empaquette des variantes binaires ; il ne remplace pas le choix entre statique et dynamique. Apple autorise les frameworks et bibliothèques statiques ou dynamiques dans ce format de distribution. Voir Creating a multiplatform binary framework bundle.
Vérifiez la compatibilité avant de choisir un conteneur
Un nom de fichier ou une architecture CPU correspondante ne suffit pas. macOS, Mac Catalyst et iOS sont des plateformes de compilation distinctes. Une bibliothèque arm64 compilée pour une plateforme ne peut pas simplement être liée à un exécutable d’une autre plateforme. Apple distingue explicitement la plateforme de l’architecture dans An Apple Library Primer.
Pour cette tâche, gardez ces paramètres cohérents : la cible macOS, l’architecture CPU requise, la version minimale de macOS et les paramètres compatibles du langage et de l’environnement d’exécution. Un livrable Mac universel nécessite des tranches de dépendances correspondantes pour chaque architecture prise en charge. Le guide d’Apple Building a universal macOS binary explique comment recompiler les bibliothèques ainsi que les exécutables.
La compatibilité C++ exige une attention particulière. Choisir -std=c++17 sélectionne un mode de langage ; cela ne garantit pas la compatibilité binaire entre des composants compilés indépendamment. LLVM documente les garanties ABI et les choix de configuration du fournisseur pour libc++, notamment les paramètres qui modifient la disposition des types. Pour une dépendance appartenant à l’application, il est généralement préférable de la recompiler avec la chaîne d’outils de l’application et des paramètres cohérents. Voir libc++’s ABI Guarantees.
L’exemple expose une fonction par l’intermédiaire d’un en-tête compatible avec C, tout en l’implémentant en C++. La déclaration protégée extern \"C\" spécifie une liaison de langage C pour la compilation C++. Elle ne transforme pas des classes C++ arbitraires en interfaces C et ne rend pas un binaire portable entre plateformes. Le projet de norme C++ définit cette limite dans Linkage specifications.
Compilez un utilitaire à la fois en bibliothèque statique et dynamique
Vous avez besoin d’un Mac équipé des outils de développement d’Apple et d’un SDK macOS. Xcode complet comprend les outils en ligne de commande ; Apple propose également un package autonome de ces outils. Si aucun n’est installé, exécutez xcode-select --install et terminez l’installation avant de continuer. Voir Installing the command-line tools.
Le script complet ci-dessous crée un nouveau répertoire temporaire et utilise uniquement des fichiers source synthétiques. Exécutez-le dans Terminal. Il cible explicitement l’architecture de l’environnement du shell actuel et macOS 11.0 comme version minimale de déploiement illustrative. Il s’agit d’un exercice à architecture unique ; choisissez une autre version minimale si votre projet l’exige.
J’ai examiné l’exemple, mais je n’ai pas pu exécuter la compilation Mac : l’environnement de recherche fonctionne sous Linux et ne dispose pas de clang++. Les résultats présentés sont les résultats attendus, et non des résultats de tests enregistrés.
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
Les deux exécutions devraient afficher Grid cells: 12. Ce calcul est volontairement simple : trois lignes multipliées par quatre colonnes. Le contrat d’entrée indiqué pour l’utilitaire exclut les dimensions dont le produit ne peut pas tenir dans int ; cet exemple ne met pas en œuvre une API générale de validation des entrées.
Ce que montre la compilation statique
L’invocation avec -c produit grid.o sans lier d’exécutable. La commande d’archivage regroupe cet objet dans libGrid.a ; ses options r, c et s demandent respectivement l’insertion ou le remplacement, la création de l’archive et la création d’un index des symboles. Clang décrit les étapes de compilation dans son compiler command guide, et LLVM explique les opérations sur les archives dans llvm-ar — LLVM archiver.
Le client est lié à l’aide du chemin explicite de l’archive. Ainsi, l’entrée sélectionnée est sans ambiguïté lorsque les versions statique et dynamique existent toutes deux. La liste attendue de l’archive contient grid.o, tandis que la liste des dépendances de l’exécutable ne devrait indiquer aucune dépendance d’exécution à libGrid.a.
La liaison statique s’applique ici à l’utilitaire Grid. Cela ne signifie pas que l’exécutable obtenu est indépendant des bibliothèques d’exécution macOS ; examinez les autres entrées renvoyées par otool -L. Apple distingue l’éditeur de liens à la compilation du chargeur à l’exécution dans An Apple Library Primer.
Ce que montre la compilation dynamique
L’invocation avec -dynamiclib crée une bibliothèque distincte. Son nom d’installation est @rpath/libGrid.dylib. L’exécutable enregistre un chemin de recherche @loader_path/lib qui, pour l’entrée de chemin de recherche de cet exécutable, pointe vers le répertoire lib adjacent. Clang répertorie les options de pilote concernées dans sa command-line argument reference.
La sortie attendue de otool -L pour le client dynamique comprend @rpath/libGrid.dylib ; la sortie des commandes de chargement devrait contenir une entrée LC_RPATH avec @loader_path/lib. Apple documente la façon dont les noms d’installation et les chemins de recherche de l’exécutable fonctionnent ensemble dans Run-Path Dependent Libraries.
Vous disposez ainsi d’un test d’empaquetage concret : déplacez tout le répertoire build/dynamic en préservant son organisation interne, puis exécutez son exécutable depuis le nouvel emplacement. Si vous ne déplacez que l’exécutable, sa dépendance Grid reste à sa place. Une édition de liens réussie confirme que les entrées de compilation ont été trouvées ; elle ne confirme pas que le package d’exécution est complet.
Quand l’utilitaire doit-il devenir un framework ?
Choisissez un framework lorsque sa structure en bundle répond aux besoins du projet, par exemple lorsque le composant nécessite des en-têtes publics et des ressources gérés ensemble dans Xcode. Pour cet utilitaire limité à un calcul, un framework ajoute du travail d’empaquetage sans répondre à un besoin supplémentaire.
Pour un framework statique dans Xcode 15 ou une version ultérieure, Apple documente la séquence suivante :
- Choisissez
File > New > Target, sélectionnez macOS et utilisez le modèleFramework. - Sélectionnez les
Build Settingsde la nouvelle cible et réglezMach-O TypesurStatic Library. - Ajoutez les fichiers d’implémentation à
Compile Sourceset les ressources àCopy Bundle Resources. - Ajoutez les en-têtes exportés à la phase de compilation
Headerset incluez les en-têtes publics dans l’en-tête fédérateur. - Ajoutez le framework à l’application cliente et suivez la configuration d’intégration documentée par Apple.
Pour un framework statique contenant des ressources, Apple indique Embed & Sign. Xcode omet du bundle intégré le binaire principal déjà lié tout en conservant les éléments du bundle. Suivez Creating a static framework au lieu de considérer tout framework statique comme une archive qui n’a pas besoin d’être intégré.
Si vous distribuez des variantes binaires, utilisez un XCFramework. Pour un fichier .a autonome existant, Apple documente les paramètres -library et -headers de xcodebuild -create-xcframework ; Apple déconseille de déguiser manuellement l’archive en framework. Xcode complet est requis pour xcodebuild. Voir Creating a multiplatform binary framework bundle et Installing the command-line tools.
Quel est l’effet de Swift sur la décision ?
Swift ajoute des exigences de consommation de modules et d’évolution à la décision de liaison sous-jacente. La stabilité ABI, la stabilité des modules et l’évolution des bibliothèques répondent à des préoccupations de compatibilité différentes ; un conteneur .framework ne les active pas automatiquement.
Les recommandations de Swift indiquent que les frameworks compilés et distribués avec leurs clients ne devraient généralement pas activer l’évolution des bibliothèques. Pour un framework binaire compilé et mis à jour séparément, décidez dès la première version si l’évolution doit être prise en charge. Dans Xcode, BUILD_LIBRARY_FOR_DISTRIBUTION active l’évolution des bibliothèques et la stabilité des modules. Ces choix influent sur les caractéristiques de performance et les modifications d’API autorisées, comme l’explique Library Evolution in Swift.
Respectez la portée des garanties telle qu’elle est documentée. La documentation Swift décrit la compatibilité binaire entre compilateurs sur les plateformes Apple ; n’étendez pas cette garantie à toutes les plateformes prises en charge par Swift et ne l’interprétez pas comme une autorisation d’effectuer des modifications arbitraires de l’API. L’en-tête compatible avec C de cet exemple ne constitue pas non plus une configuration complète de module Swift.
Diagnostiquez un échec de compilation dans l’ordre où il survient
Utilisez l’étape de l’échec pour déterminer la vérification suivante :
| Symptôme | Première vérification | Action suivante |
|---|---|---|
Le compilateur ne trouve pas grid.h |
L’en-tête existe sous include |
Vérifiez le répertoire de travail et -Iinclude |
| Référence indéfinie à la fonction Grid | L’implémentation et la déclaration correspondent | Confirmez que l’archive ou la bibliothèque dynamique voulue figure dans la commande d’édition de liens |
| Incompatibilité d’architecture ou de plateforme | Tranches réelles des dépendances et plateforme de compilation | Recompilez la dépendance pour la cible macOS du client |
| L’exécutable est lié, mais ne peut pas charger Grid | Dépendance enregistrée et chemin de recherche | Comparez otool -L, otool -l et l’organisation du répertoire empaqueté |
| Les compilations Intel et Apple silicon se comportent différemment | Couverture architecturale de chaque dépendance | Inspectez chaque binaire et testez l’architecture visée |
Le compiler command guide de Clang distingue la compilation de l’édition de liens. Run-Path Dependent Libraries d’Apple explique la résolution à l’exécution, tandis que Building a universal macOS binary documente l’inspection des architectures avec lipo -archs. Ces distinctions rendent l’ordre de diagnostic utile : modifier les chemins d’en-têtes ne peut pas corriger l’absence d’une bibliothèque d’exécution.
Pour l’utilitaire de grille synthétique, conservez l’archive statique jusqu’à ce qu’un besoin concret d’empaquetage ou d’exécution justifie un autre choix. Si un tel besoin apparaît, gardez le même petit calcul comme vérification de compilation, puis vérifiez séparément la bibliothèque choisie, la compatibilité de la cible et l’organisation finale du répertoire.
Sources
- An Apple Library Primer — Assistance technique aux développeurs Apple.
- Creating a static framework — Apple.
- Creating a multiplatform binary framework bundle — Apple.
- Building a universal macOS binary — Apple.
- Installing the command-line tools — Apple.
- Run-Path Dependent Libraries — Archives de documentation Apple.
- clang — the Clang C, C++, and Objective-C compiler — Projet LLVM.
- Clang command line argument reference — Projet LLVM.
- llvm-ar — LLVM archiver — Projet LLVM.
- libc++’s ABI Guarantees — Projet LLVM.
- Linkage specifications — Projet de norme C++, rendu par eel.is.
- Library Evolution in Swift — Swift.org.