Para una pequeña utilidad nativa de Mac, empieza con una biblioteca estática cuando el código se distribuya junto con el ejecutable. Elige una biblioteca dinámica cuando resulte útil tener un componente de ejecución independiente, y un framework cuando necesites el empaquetado en bundles de Apple. Esta guía compila el mismo componente auxiliar de C++ de ambas formas.

La tarea concreta consiste en empaquetar un cálculo de cuadrícula reutilizable para una utilidad de escritorio local de macOS. El ejemplo usa entradas sintéticas y comandos de terminal para que puedas ver por separado la compilación, el enlazado y la carga antes de elegir un formato para un proyecto de app.

¿Qué estás eligiendo exactamente?

Hay dos decisiones: cómo se enlaza el código y cómo se empaqueta el producto. Una biblioteca estática, que suele terminar en .a, es un archivo de objetos. Una biblioteca dinámica, que termina en .dylib, permanece como una imagen de ejecución independiente. Un framework es un bundle que organiza una biblioteca y material relacionado. Apple describe estos formatos subyacentes en Introducción a las bibliotecas de Apple.

Un framework también puede contener una biblioteca estática. Apple admite la creación de frameworks estáticos en Xcode 15 y versiones posteriores, incluidos los recursos. Por lo tanto, el sufijo .framework por sí solo no permite determinar si la biblioteca se enlaza de forma estática o dinámica. Consulta Crear un framework estático.

Usa esta guía para decidir qué hacer con un componente auxiliar propiedad de la app que compilas desde el código fuente:

Tu requisito Opción inicial Responsabilidad que implica
Un ejecutable y un componente auxiliar que se recompilan juntos .a estático Volver a enlazar el ejecutable cuando cambie el componente auxiliar
Un componente auxiliar que permanece como componente de ejecución independiente .dylib dinámico Proporcionar la biblioteca y una ruta de búsqueda de ejecución que funcione
Un componente de Xcode con encabezados públicos y recursos incluidos Framework Elegir por separado su tipo de enlace y configurar la pertenencia a los destinos
Variantes binarias para varias plataformas de Apple XCFramework Compilar y empaquetar las variantes de plataforma necesarias

Estas son recomendaciones prácticas derivadas de los formatos documentados. No determinan cuál ofrece mejor rendimiento. Para esta utilidad de cuadrícula, un archivo estático es la opción inicial natural: hay un solo cliente, no hay un paquete de recursos y no se necesita reemplazar una biblioteca independiente.

Un XCFramework empaqueta variantes binarias; no reemplaza la decisión entre enlace estático y dinámico. Apple permite usar frameworks y bibliotecas estáticos o dinámicos dentro de ese formato de distribución. Consulta Crear un bundle de framework binario multiplataforma.

Comprueba la compatibilidad antes de elegir un contenedor

Que el nombre del archivo o la arquitectura de CPU coincidan no es suficiente. macOS, Mac Catalyst e iOS son plataformas de compilación distintas. Una biblioteca arm64 compilada para una plataforma no se puede enlazar directamente con un ejecutable de otra plataforma. Apple distingue explícitamente entre plataforma y arquitectura en Introducción a las bibliotecas de Apple.

Para esta tarea, mantén coherentes estas entradas: el destino de macOS, la arquitectura de CPU requerida, la versión mínima de macOS y la configuración compatible del lenguaje y del entorno de ejecución. Un producto universal para Mac necesita segmentos de dependencias que coincidan para cada arquitectura admitida. En Compilar un binario universal para macOS, Apple explica cómo recompilar bibliotecas además de ejecutables.

La compatibilidad con C++ requiere atención aparte. Seleccionar -std=c++17 elige un modo del lenguaje; no certifica la compatibilidad binaria entre componentes compilados por separado. LLVM documenta las garantías de ABI y las opciones de configuración del proveedor para libc++, incluidos los ajustes que modifican la disposición de tipos. Para una dependencia propiedad de la app, una opción predeterminada útil es recompilarla con la cadena de herramientas de la app y una configuración coherente. Consulta Garantías de ABI de libc++.

El ejemplo expone una función mediante un encabezado compatible con C, aunque su implementación esté escrita en C++. La declaración protegida extern \"C\" especifica el enlace del lenguaje C para la compilación de C++. No convierte clases arbitrarias de C++ en interfaces C ni hace que un binario sea portable entre plataformas. El borrador de trabajo de C++ define este límite en Especificaciones de enlace.

Compila el mismo componente auxiliar como biblioteca estática y dinámica

Necesitas un Mac con las herramientas de desarrollo de Apple y un SDK de macOS. Xcode completo incluye las herramientas de línea de comandos; Apple también ofrece un paquete independiente de estas herramientas. Si no tienes ninguno instalado, ejecuta xcode-select --install y termina el instalador antes de continuar. Consulta Instalar las herramientas de línea de comandos.

El siguiente script completo crea un directorio temporal nuevo y solo usa archivos de código fuente sintéticos. Ejecútalo en Terminal. El script apunta explícitamente a la arquitectura del entorno del shell actual y a macOS 11.0 como versión mínima de implementación ilustrativa. Es un ejercicio para una sola arquitectura; usa otra versión mínima si tu proyecto lo requiere.

Revisé el ejemplo, pero no pude ejecutar la compilación para Mac: el entorno de investigación usa Linux y no tiene clang++. Los resultados que se muestran son los esperados, no la salida de una prueba 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

Ambas ejecuciones deberían imprimir Grid cells: 12. El cálculo es deliberadamente pequeño: tres filas multiplicadas por cuatro columnas. El contrato de entrada declarado para el componente auxiliar excluye las dimensiones cuyo producto no cabe en int; este ejemplo no implementa una API general para validar entradas.

Qué demuestra la compilación estática

La invocación con -c produce grid.o sin enlazar un ejecutable. El comando de archivado empaqueta ese objeto en libGrid.a; sus opciones r, c y s solicitan la inserción o sustitución, la creación del archivo y un índice de símbolos. Clang documenta las etapas de compilación en su guía de comandos del compilador, y LLVM explica las operaciones de archivado en llvm-ar — archivador de LLVM.

El cliente se enlaza mediante la ruta explícita del archivo. Así, cuando existen las versiones estática y dinámica, la entrada seleccionada no deja lugar a dudas. La lista esperada del archivo contiene grid.o, mientras que la lista de dependencias del ejecutable no debería mostrar una dependencia de ejecución de libGrid.a.

En este caso, el enlace estático se aplica al componente auxiliar Grid. No significa que el ejecutable resultante sea independiente de las bibliotecas de ejecución de macOS; inspecciona las entradas restantes que muestra otool -L. Apple distingue el enlazador en tiempo de compilación del cargador en tiempo de ejecución en Introducción a las bibliotecas de Apple.

Qué demuestra la compilación dinámica

La invocación con -dynamiclib crea una biblioteca independiente. Su nombre de instalación es @rpath/libGrid.dylib. El ejecutable registra una ruta de búsqueda de @loader_path/lib, que, para la entrada de ruta de búsqueda de este ejecutable, apunta al directorio lib contiguo. Clang enumera las opciones pertinentes del controlador en su referencia de argumentos de línea de comandos.

La salida esperada de otool -L para el cliente dinámico incluye @rpath/libGrid.dylib; la salida de los comandos de carga debería contener una entrada LC_RPATH con @loader_path/lib. Apple documenta cómo colaboran los nombres de instalación y las rutas de búsqueda del ejecutable en Bibliotecas dependientes de rutas de búsqueda.

Esto te permite probar el empaquetado en la práctica: mueve todo el directorio build/dynamic junto, conservando su estructura interna, y luego ejecuta el ejecutable desde la nueva ubicación. Si mueves solo el ejecutable, la dependencia Grid se queda atrás. Que el enlace se complete correctamente demuestra que se encontraron las entradas de compilación; no demuestra que el paquete de ejecución esté completo.

¿Cuándo debería convertirse el componente auxiliar en un framework?

Elige un framework cuando su estructura de bundle satisfaga las necesidades del proyecto; por ejemplo, si el componente necesita encabezados públicos y recursos que se administren juntos en Xcode. Para este componente auxiliar que solo hace cálculos, un framework añade trabajo de empaquetado sin satisfacer ningún requisito adicional.

Para crear un framework estático en Xcode 15 o versiones posteriores, Apple documenta esta secuencia:

  1. Elige File > New > Target, selecciona macOS y usa la plantilla Framework.
  2. Selecciona el Build Settings del nuevo destino y cambia Mach-O Type a Static Library.
  3. Añade los archivos de implementación a Compile Sources y los recursos a Copy Bundle Resources.
  4. Añade los encabezados exportados a la fase de compilación Headers e incluye los encabezados públicos en el encabezado paraguas.
  5. Añade el framework a la app que lo consume y sigue la configuración de inclusión documentada por Apple.

Para un framework estático con recursos, Apple especifica Embed & Sign. Xcode omite del bundle incluido el binario principal, que ya está enlazado, y conserva el material del bundle. Sigue Crear un framework estático, en lugar de tratar todos los frameworks estáticos como archivos que no necesitan inclusión.

Si distribuyes variantes binarias, usa un XCFramework. Para un .a independiente existente, Apple documenta las entradas -library y -headers de xcodebuild -create-xcframework; recomienda no disfrazar manualmente el archivo como framework. Para xcodebuild se requiere Xcode completo. Consulta Crear un bundle de framework binario multiplataforma e Instalar las herramientas de línea de comandos.

¿Cómo cambia la decisión con Swift?

Swift añade requisitos de consumo de módulos y evolución a la decisión subyacente sobre el tipo de enlace. La estabilidad de ABI, la estabilidad de módulos y la evolución de bibliotecas responden a distintos problemas de compatibilidad; un contenedor .framework no los habilita automáticamente.

Las pautas de Swift indican que, por lo general, no se debería habilitar la evolución de bibliotecas en los frameworks que se compilan y distribuyen junto con sus clientes. Para un framework binario que se compila y actualiza por separado, decide desde la primera versión si necesitas compatibilidad con la evolución. En Xcode, BUILD_LIBRARY_FOR_DISTRIBUTION habilita la evolución de bibliotecas y la estabilidad de módulos. Estas opciones afectan las características de rendimiento y los cambios de API permitidos, como se explica en Evolución de bibliotecas en Swift.

Limita esas garantías al alcance de las plataformas documentadas. La documentación de Swift describe la compatibilidad binaria entre compiladores en las plataformas de Apple; no extiendas esa promesa a todas las plataformas compatibles con Swift ni la interpretes como permiso para realizar cambios arbitrarios en la API. El encabezado compatible con C de este ejemplo tampoco constituye una configuración completa de módulo Swift.

Diagnostica una compilación fallida en el orden en que ocurre

Usa la etapa en la que se produce el fallo para acotar qué comprobar a continuación:

Síntoma Primera comprobación Siguiente paso
El compilador no encuentra grid.h El encabezado existe bajo include Comprueba el directorio de trabajo y -Iinclude
Referencia indefinida a la función Grid La implementación y la declaración coinciden Confirma que el archivo previsto o la biblioteca dinámica estén en el comando de enlace
Incompatibilidad de arquitectura o plataforma Los segmentos reales de la dependencia y la plataforma de compilación Recompila la dependencia para el destino macOS del cliente
El ejecutable se enlaza, pero no puede cargar Grid La dependencia registrada y la ruta de búsqueda Compara otool -L, otool -l y la estructura del directorio empaquetado
Las compilaciones para Intel y Apple silicon se comportan de forma distinta Cobertura de arquitecturas de cada dependencia Inspecciona cada binario y prueba la arquitectura prevista

La guía de comandos del compilador de Clang separa la compilación del enlace. Bibliotecas dependientes de rutas de búsqueda, de Apple, explica la resolución en tiempo de ejecución, mientras que Compilar un binario universal para macOS documenta la inspección de arquitecturas con lipo -archs. Estas distinciones hacen útil el orden del diagnóstico: cambiar las rutas de encabezados no puede reparar una biblioteca de ejecución ausente.

Para la utilidad sintética de cuadrícula, conserva el archivo estático hasta que un requisito concreto de empaquetado o ejecución justifique otra opción. Si surge ese requisito, conserva el mismo cálculo pequeño como comprobación de compilación y luego verifica por separado la biblioteca seleccionada, la compatibilidad del destino y la estructura final de directorios.

Fuentes