Para um pequeno utilitário nativo para Mac, comece com uma biblioteca estática quando o código for distribuído junto com o executável. Escolha uma biblioteca dinâmica quando for útil ter um componente de execução separado, e um framework quando precisar do empacotamento em bundle da Apple. Este guia compila o mesmo auxiliar em C++ das duas formas.
A tarefa concreta é empacotar um cálculo de grade reutilizável para um utilitário de desktop local para macOS. O exemplo usa entradas sintéticas e comandos de terminal para que você possa ver a compilação, a vinculação e o carregamento separadamente antes de escolher um formato para um projeto de app.
O que você está escolhendo, afinal?
Há duas decisões: como o código é vinculado e como o artefato é empacotado. Uma biblioteca estática, que geralmente termina em .a, é um arquivo de arquivos objeto. Uma biblioteca dinâmica, terminada em .dylib, permanece uma imagem de execução separada. Um framework é um bundle que organiza uma biblioteca e materiais relacionados. A Apple descreve esses formatos subjacentes em An Apple Library Primer.
Um framework também pode conter uma biblioteca estática. A Apple oferece suporte à criação de frameworks estáticos no Xcode 15 e posteriores, incluindo recursos. Portanto, o sufixo .framework, por si só, não indica se a biblioteca é vinculada estaticamente ou dinamicamente. Consulte Creating a static framework.
Use este guia de decisão para um auxiliar pertencente ao app que você compila a partir do código-fonte:
| Seu requisito | Opção inicial | Responsabilidade que ela implica |
|---|---|---|
| Um executável e um auxiliar recompilados juntos | .a estático |
Vincular novamente o executável quando o auxiliar mudar |
| Um auxiliar que permanece como componente de execução separado | .dylib dinâmico |
Fornecer a biblioteca e um caminho de busca válido durante a execução |
| Um componente do Xcode com cabeçalhos públicos e recursos agrupados | Framework | Escolher a vinculação separadamente e configurar a associação aos targets |
| Variantes binárias para várias plataformas Apple | XCFramework | Compilar e empacotar as variantes de plataforma necessárias |
Estas são recomendações práticas derivadas dos formatos documentados. Elas não determinam qual opção oferece melhor desempenho. Para este utilitário de grade, um arquivo estático é o ponto de partida natural: há apenas um cliente, nenhum bundle de recursos e nenhum requisito para substituir uma biblioteca separada.
Um XCFramework empacota variantes binárias; ele não substitui a decisão entre estático e dinâmico. A Apple permite frameworks e bibliotecas estáticos ou dinâmicos dentro desse formato de distribuição. Consulte Creating a multiplatform binary framework bundle.
Verifique a compatibilidade antes de escolher um contêiner
Um nome de arquivo ou uma arquitetura de CPU correspondente não é suficiente. macOS, Mac Catalyst e iOS são plataformas de compilação distintas. Uma biblioteca arm64 compilada para uma plataforma não pode simplesmente ser vinculada ao executável de outra plataforma. A Apple distingue explicitamente plataforma de arquitetura em An Apple Library Primer.
Para esta tarefa, mantenha consistentes estas entradas: o destino macOS, a arquitetura de CPU necessária, a versão mínima do macOS e as configurações compatíveis de linguagem e ambiente de execução. Um artefato universal para Mac precisa de fatias correspondentes para cada arquitetura compatível. O artigo da Apple Building a universal macOS binary aborda a recompilação tanto de bibliotecas quanto de executáveis.
A compatibilidade com C++ requer atenção específica. Selecionar -std=c++17 define um modo de linguagem; isso não certifica a compatibilidade binária entre componentes compilados independentemente. O LLVM documenta as garantias de ABI e as opções de configuração de fornecedores para libc++, incluindo configurações que alteram o layout dos tipos. Para uma dependência pertencente ao app, um padrão útil é recompilá-la com a cadeia de ferramentas do app e configurações consistentes. Consulte libc++’s ABI Guarantees.
O exemplo expõe uma função por meio de um cabeçalho compatível com C, embora a implemente em C++. A declaração extern \"C\" protegida especifica a vinculação de linguagem C na compilação em C++. Ela não converte classes C++ arbitrárias em interfaces C nem torna um binário portátil entre plataformas. O rascunho de trabalho de C++ define esse limite em Linkage specifications.
Compile o mesmo auxiliar como biblioteca estática e dinâmica
Você precisa de um Mac com as ferramentas de desenvolvimento da Apple e um SDK do macOS. O Xcode completo inclui as ferramentas de linha de comando; a Apple também oferece um pacote independente dessas ferramentas. Se nenhum deles estiver instalado, execute xcode-select --install e conclua o instalador antes de continuar. Consulte Installing the command-line tools.
O script completo abaixo cria um novo diretório temporário e usa apenas arquivos-fonte sintéticos. Execute-o no Terminal. Ele define explicitamente como destino a arquitetura do ambiente de shell atual e o macOS 11.0 como versão mínima ilustrativa de implantação. É um exercício para uma única arquitetura; use outra versão mínima se o seu projeto exigir isso.
Inspecionei o exemplo, mas não pude executar a compilação para Mac: o ambiente de pesquisa usa Linux e não tem clang++. Os resultados exibidos são os resultados esperados, não uma saída de teste registrada.
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
As duas execuções devem imprimir Grid cells: 12. Este cálculo é deliberadamente simples: três linhas multiplicadas por quatro colunas. O contrato de entrada declarado para o auxiliar exclui dimensões cujo produto não caiba em int; este exemplo não implementa uma API geral de validação de entradas.
O que a compilação estática demonstra
A invocação com -c produz grid.o sem vincular um executável. O comando de arquivamento empacota esse objeto em libGrid.a; as opções r, c e s solicitam inserção/substituição, criação do arquivo e um índice de símbolos. O Clang documenta as etapas de compilação em seu compiler command guide, e o LLVM explica as operações de arquivamento em llvm-ar — LLVM archiver.
O cliente é vinculado usando o caminho explícito do arquivo. Isso torna inequívoca a entrada selecionada quando existem versões estática e dinâmica. A listagem esperada do arquivo contém grid.o, enquanto a listagem de dependências do executável não deve mostrar uma dependência de execução de libGrid.a.
A vinculação estática aqui se aplica ao auxiliar Grid. Isso não significa que o executável resultante seja independente das bibliotecas de execução do macOS; inspecione as demais entradas informadas por otool -L. A Apple distingue o vinculador em tempo de compilação do carregador de execução em An Apple Library Primer.
O que a compilação dinâmica demonstra
A invocação com -dynamiclib cria uma biblioteca separada. Seu nome de instalação é @rpath/libGrid.dylib. O executável registra um caminho de execução @loader_path/lib, que, para a entrada de caminho de execução deste executável, aponta para o diretório lib adjacente. O Clang lista as opções relevantes do driver em sua command-line argument reference.
A saída esperada de otool -L para o cliente dinâmico inclui @rpath/libGrid.dylib; a saída dos comandos de carregamento deve conter uma entrada LC_RPATH com @loader_path/lib. A Apple documenta como os nomes de instalação e os caminhos de execução do executável funcionam em conjunto em Run-Path Dependent Libraries.
Isso oferece um teste concreto de empacotamento: mova o diretório build/dynamic inteiro, preservando sua estrutura interna, e depois execute o executável no novo local. Se mover apenas o executável, a dependência Grid ficará para trás. Uma vinculação bem-sucedida confirma que as entradas de compilação foram encontradas; não confirma que o pacote de execução esteja completo.
Quando o auxiliar deve se tornar um framework?
Escolha um framework quando sua estrutura em bundle atender às necessidades do projeto — por exemplo, quando o componente precisar de cabeçalhos públicos e recursos gerenciados juntos no Xcode. Para este auxiliar que só faz cálculos, um framework acrescenta trabalho de empacotamento sem atender a outro requisito.
Para um framework estático no Xcode 15 ou posterior, a Apple documenta esta sequência:
- Escolha
File > New > Target, selecione macOS e use o templateFramework. - Selecione
Build Settingsdo novo target e altereMach-O TypeparaStatic Library. - Adicione os arquivos de implementação a
Compile Sourcese os recursos aCopy Bundle Resources. - Adicione os cabeçalhos exportados à fase de compilação
Headerse inclua os cabeçalhos públicos no cabeçalho abrangente. - Adicione o framework ao app que o consome e siga a configuração de incorporação documentada pela Apple.
Para um framework estático que contém recursos, a Apple especifica Embed & Sign. O Xcode omite do bundle incorporado o binário principal já vinculado, mantendo os materiais do bundle. Siga Creating a static framework em vez de tratar todo framework estático como um arquivo que não precisa ser incorporado.
Se você distribuir variantes binárias, use um XCFramework. Para um .a independente já existente, a Apple documenta as entradas -library e -headers para xcodebuild -create-xcframework; ela recomenda não disfarçar manualmente o arquivo como um framework. É necessário ter o Xcode completo para xcodebuild. Consulte Creating a multiplatform binary framework bundle e Installing the command-line tools.
Como o Swift muda a decisão?
O Swift acrescenta requisitos de consumo de módulos e evolução à decisão subjacente de vinculação. Estabilidade de ABI, estabilidade de módulos e evolução de bibliotecas abordam preocupações diferentes de compatibilidade; um contêiner .framework não as habilita automaticamente.
As orientações do Swift dizem que, em geral, frameworks compilados e distribuídos junto com seus clientes não devem habilitar a evolução de bibliotecas. Para um framework binário compilado e atualizado separadamente, decida desde a primeira versão se haverá suporte à evolução. No Xcode, BUILD_LIBRARY_FOR_DISTRIBUTION habilita a evolução de bibliotecas e a estabilidade de módulos. Essas escolhas afetam as características de desempenho e as alterações de API permitidas, conforme explicado em Library Evolution in Swift.
Mantenha essas garantias dentro do escopo de plataformas documentado. A documentação do Swift descreve a compatibilidade binária entre compiladores nas plataformas Apple; não estenda essa promessa a todas as plataformas compatíveis com Swift nem a interprete como permissão para fazer alterações arbitrárias na API. O cabeçalho compatível com C deste exemplo também não constitui uma configuração completa de módulo Swift.
Diagnostique uma compilação com falha na ordem em que as etapas ocorrem
Use a etapa em que ocorreu a falha para restringir a próxima verificação:
| Sintoma | Primeira verificação | Próxima ação |
|---|---|---|
O compilador não encontra grid.h |
O cabeçalho existe em include |
Verifique o diretório de trabalho e -Iinclude |
| Referência indefinida à função Grid | A implementação e a declaração correspondem | Confirme se o arquivo ou a biblioteca dinâmica pretendida está no comando de vinculação |
| Incompatibilidade de arquitetura ou plataforma | Fatias reais da dependência e plataforma de compilação | Recompile a dependência para o destino macOS do cliente |
| O executável é vinculado, mas não consegue carregar Grid | Dependência registrada e caminho de execução | Compare otool -L, otool -l e a estrutura dos diretórios empacotados |
| As compilações para Intel e Apple silicon se comportam de modo diferente | Cobertura de arquitetura de cada dependência | Inspecione cada binário e teste a arquitetura pretendida |
O compiler command guide do Clang separa compilação de vinculação. Run-Path Dependent Libraries, da Apple, explica a resolução em tempo de execução, enquanto Building a universal macOS binary documenta a inspeção de arquiteturas com lipo -archs. Essas distinções tornam útil a ordem do diagnóstico: alterar caminhos de cabeçalhos não corrige uma biblioteca de execução ausente.
Para o utilitário sintético de grade, mantenha o arquivo estático até que um requisito concreto de empacotamento ou execução justifique outra opção. Se esse requisito surgir, mantenha o mesmo cálculo pequeno como verificação de compilação e, em seguida, verifique separadamente a biblioteca selecionada, a compatibilidade do destino e a estrutura final dos diretórios.
Fontes
- An Apple Library Primer — Suporte Técnico para Desenvolvedores da 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 — Arquivo de Documentação da Apple.
- clang — the Clang C, C++, and Objective-C compiler — Projeto LLVM.
- Clang command line argument reference — Projeto LLVM.
- llvm-ar — LLVM archiver — Projeto LLVM.
- libc++’s ABI Guarantees — Projeto LLVM.
- Linkage specifications — Rascunho de Trabalho de C++, renderizado por eel.is.
- Library Evolution in Swift — Swift.org.