ネイティブ macOS プログラムをリンクするには、選択されたすべてのライブラリスライスが、互換性のある言語 ABI を備え、その CPU アーキテクチャと Apple プラットフォームと一致していなければなりません。このガイドでは、C++ デスクトップコマンドラインツールで使用される小さな C ライブラリのビルドと検査の方法を示します。

具体的なタスクは、ローカルソースから macOS 実行可能ファイルを生成し、互換性のないネイティブ依存関係を診断することです。この例では合成整数演算を使用し、ダウンロードするライブラリは不要で、アーキテクチャ固有の出力を別々に保持します。

ネイティブライブラリがリンクされる前に何が一致していなければならないか?

アーキテクチャ、プラットフォーム、言語間のバイナリインターフェースの 3 つを独立して確認します。Clang のターゲット指定にはプロセッサ以上の情報が含まれ、そのターゲットと環境はコード生成とライブラリ選択に影響します。依存関係も意図したターゲット向けにビルドされている必要があります。Clang を使用したクロスコンパイル。

確認項目 この macOS タスクでの質問 不一致の意味
CPU アーキテクチャ 依存関係にリンクされる arm64 または x86_64 スライスが含まれているか? 不足しているアーキテクチャを取得またはビルドする。
Apple プラットフォーム そのスライスはネイティブ macOS 向けにビルドされているか? macOS ビルドを取得する。iOS またはシミュレータビルドは別のバリアントです。
言語 ABI 宣言、呼び出し規約、必要なランタイムが一致しているか? インターフェースを修正するか、言語がサポートする相互運用パスを使用する。

アーキテクチャ一覧ではプラットフォームの質問に答えられません。Apple の Mach-O 形式では CPU 情報とプラットフォームビルド情報が別々に記録され、macOS、iOS、Mac Catalyst、iOS シミュレータに対して異なるプラットフォーム識別子が定義されています。arm64 とだけ記述された依存関係は、さらなる検査が必要です。Apple の Mach-O 形式定義。

この例では、意図したビルド契約はネイティブ macOS、展開ターゲット macOS 13.0、アーキテクチャごとに 1 ビルドです。C ライブラリは C インターフェースを公開し、実行可能ファイルは C++17 です。このバージョンは例示的な選択であり、Mac ソフトウェアの普遍的な最小要件ではありません。

明示的なターゲットで完全な macOS 例をビルドする

現在の Xcode インストールがコマンドライン開発用に選択されている Mac を使用します。Apple は xcrun を、選択された開発者インストール内のツールを見つけたり実行したりするためのメカニズムとして文書化しています。ソース変更の調査前に選択を確認してください。コマンドラインからの Xcode ビルドに関する FAQ。

以下のコマンドは macOS 上の Bash 向けに設計されています。リサーチ環境が Linux であり Apple ツールチェーンがないため、ここでは検査済みですが実行はしていません。

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

これらのチェックが失敗した場合は、まず開発者ツールの選択またはインストールを解決してください。ビルドには macOS SDK とコンパイラが必要です。SDK は、製品がビルドされる対象のプラットフォームヘッダとライブラリを提供します。Xcode Build Settings Reference: Base SDK。

ソースファイルを作成する

Bash でこのセットアップブロックを実行します。新しい一時ディレクトリを作成し、その場所を出力するため、既存のプロジェクトファイルは影響を受けません。以降のブロックを同じシェルで続行してください。

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

このヘッダは、C++ 翻訳単位がそれを含めるときに C 言語リンケージを宣言します。これにより、C++ 呼び出し元が異なるマングルされた C++ 関数名を要求することを防ぎます。C++ FAQ でも、C++ コンパイラに最終リンクを指示させ、その必要なライブラリが含まれるようにすることを推奨しています。Standard C++ FAQ: C と C++ を混在させる方法。

各アーキテクチャをコンパイル、アーカイブ、リンクする

次のブロックでは両方の macOS バリアントをビルドします。各アーキテクチャに対して、ライブラリコンパイルと実行可能ファイルリンクに同じターゲットと SDK を渡します。Clang は -c をオブジェクトファイル生成として文書化しています。ステージ選択オプションなしでは、ドライバはリンクまで進行します。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

ここで -target はアーキテクチャと macOS 展開ターゲットを選択し、-isysroot は SDK ルートを提供します。これらは別々の入力です。明示的なアーカイブパスも、選択された依存関係をコマンドで可視化します。Clang クロスコンパイルガイダンス、Clang コマンドライン引数リファレンス。

ar rcs コマンドは、コンパイル済みオブジェクトを静的アーカイブにパッケージし、シンボルインデックスを提供します。アーカイブは既存オブジェクトをパッケージするものであり、別のターゲット向けにコンパイルするものではありません。LLVM はアーカイブ操作と他の ar 実装との互換性を文書化しています。LLVM アーカイバドキュメント。

サンプル入力 3 と 4 は Tiles: 12 を生成するはずです。この小さな関数はリンクの例に過ぎず、そのインターフェースは、積が int の範囲を超える任意の寸法に対する動作を定義しません。

アーキテクチャ、プラットフォーム、シンボルを検査するには?

実際の実行可能ファイル、ライブラリ、またはオブジェクトファイルを検査します。Apple はアーキテクチャ一覧のための lipo -archs を文書化し、アプリの実行可能ファイルがバンドルの Contents/MacOS/ ディレクトリ内にあることを指摘しています。ユニバーサル macOS バイナリのビルド。

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

最初の 2 つのコマンドは、それぞれのアーキテクチャを特定するはずです。プラットフォームチェックでは、otool -l が出力するロードコマンドを検査します。LLVM の互換 llvm-otool も同じオプションを文書化しています。LLVM Mach-O ダンピングツール。

LC_BUILD_VERSION を探してください。その platform は macOS を特定し、minos は最小 OS バージョンを記録します。sdk フィールドは別です。一部の古いバイナリは代わりに LC_VERSION_MIN_* コマンドを使用します。Apple の形式定義では、macOS プラットフォーム識別子 1 を含む両方の表現が文書化されています。Mach-O build-version 構造。

静的ライブラリの場合、アーカイブラッパーをプラットフォーム互換性の証明として扱うのではなく、メンバオブジェクトを検査してください。この例では tile.o をその目的で保持しています。見慣れないアーカイブの場合、xcrun ar t を使用してメンバを一覧し、別の作業ディレクトリで検査してください。LLVM はアーカイブ一覧と抽出操作を文書化しています。LLVM アーカイバドキュメント。

最後にシンボルを確認します。サンプルライブラリは Mach-O 上で _tile_total を定義しているはずです。従来の nm 出力では、T はグローバルコードシンボルを、U は未定義参照を示します。定義と参照は異なる証拠です。LLVM シンボルテーブルドキュメント。

次のアクションを選択するためにこの診断シーケンスを使用してください:

  1. 必要な CPU スライスが欠落している: そのアーキテクチャ向けに依存関係をリビルドするか、一致するバイナリを取得する。
  2. CPU は一致するがプラットフォームが異なる: ネイティブ macOS バリアントを取得する。呼び出し元のアーキテクチャを変更してもプラットフォーム互換性は確立されない。
  3. CPU とプラットフォームは一致するが関数が未解決: 宣言された関数とライブラリのエクスポートシンボルを比較し、意図したアーカイブがリンクコマンドに現れることを確認する。
  4. C と C++ で名前が異なる: 共有ヘッダの言語リンケージを修正し、呼び出し元をリビルドする。
  5. 展開バージョンが異なる: 依存関係の最小バージョンを検査し、サポートされている場合は意図した展開ターゲット向けにリビルドするか、アプリケーションの要件を意図的に引き上げる。

このシーケンスは、上記のターゲット、Mach-O、リンケージドキュメントから導かれた意思決定支援です。不足しているマシンコード、互換性のないプラットフォームメタデータ、言語インターフェースの問題を分離し、各リビルドが特定の発見に対処するようにします。

Swift がネイティブライブラリを呼び出すとき、何が変わるか?

一致する CPU とプラットフォームがあっても、すべての言語インターフェースが呼び出し可能になるわけではありません。Swift はデフォルトで C 相互運用をサポートします。直接 C++ 相互運用は有効化が必要で、文書化された C++ 機能のサブセットをサポートします。この例の C 向けヘッダは、Swift ビルドが設定されたヘッダやモジュールメカニズムを通じてインポートする限り、サポートされるインターフェース選択肢の 1 つを提供します。Swift と C++ の混在。

直接 C++ インターフェースの場合、Swift はコマンドラインビルド向けの -cxx-interoperability-mode=default、ヘッダインポート用の Clang モジュール、C++ インポータ設定の受け渡し用の -Xcc を文書化しています。インポート設定とコンパイル済み実装のリンクは別々のビルド要件です。混在言語 Swift と C++ プロジェクトのセットアップ。

文書化された制限を守ってください。C++ 相互運用には Swift 5.9 以降と C++14 以降が必要です。混在 Swift と C++ コードは同じ C++ 標準ライブラリを使用する必要があります。Apple プラットフォームでは libc++ を使用します。C++ から Swift を呼び出すには、生成されたインターフェースとサポートされる Clang コンパイラが必要です。インポートされた C++ 参照型には、追加の macOS 13.3 展開要件もあります。このルートを選択する前に、正確な API を現在の互換性ドキュメントと照らし合わせて確認してください。C++ 相互運用のサポートされる機能と制約。

ユニバーサルバイナリや XCFramework を使用するのはいつか?

この macOS のみの実行可能ファイルの場合、完了した 2 つの macOS ビルドをユニバーサルバイナリにマージします。Apple は各アーキテクチャをコンパイルおよびリンクし、lipo で生成された実行可能ファイルを結合することを文書化しています。ユニバーサル macOS バイナリのビルド。

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

macOS 13.0 以降を実行するサポート対象の Mac では、最終コマンドは Tiles: 12 を出力し、成功を返すはずです。両方のスライスをビルドすることは両方をテストすることとは異なります。Intel Mac では arm64 スライスを実行できません。Apple のユニバーサルバイナリガイダンス。

複数の Apple プラットフォームまたはプラットフォームバリアントをサポートするライブラリの場合、XCFramework を使用して個別にビルドされたバリアントをパッケージします。Apple は開発者に対して iOS と iOS シミュレータのスタティックライブラリを別々に保持するよう明示的に指示しており、同じ XCFramework にネイティブ macOS と Mac Catalyst バリアントを含めることをサポートしています。Xcode アーカイブワークフローでは、その文書化されたアプローチはデスティネーションを使用してプラットフォームと SDK を選択します。マルチプラットフォームバイナリフレームワークバンドルの作成。

次の macOS リンク失敗では、意図したターゲットを記録し、選択された依存関係のアーキテクチャとプラットフォームを検査し、次に言語インターフェースを確認してください。それらの発見を正確なリンクコマンドの横に置いておきます。それが、正しいバイナリを選択するか、ソースからリビルドするための具体的な根拠になります。

ソース