オブジェクトファイル・実行ファイル・静的ライブラリ・動的ライブラリ・DLL¶
一言で言うと¶
- オブジェクトファイル(.o / .obj): 1翻訳単位ぶんの機械語+シンボル表。未完成品
- 静的ライブラリ(.a / .lib): .oを束ねた「アーカイブ」。リンク時に実行ファイルへ焼き込まれる
- 動的ライブラリ(.so / .dll): 完成品に近いバイナリ。実行時にロードして結合される
- 実行ファイル(.exe / ELF): ローダーが読める完成品
オブジェクトファイルの中身¶
enemy.o
├─ .text : 機械語(Enemy::TakeDamage の命令列など)
├─ .data : 初期化済みグローバル/静的変数の初期値
├─ .bss : ゼロ初期化変数の「サイズだけ」(実体は実行時に確保)
├─ .rodata : 定数・文字列リテラル
├─ シンボル表: 提供する名前 / 必要とする名前(未解決)
└─ 再配置情報: 「この命令のこの部分は、後でXのアドレスを書き込む」のリスト
「未解決の名前」と「再配置情報」があるから未完成品であり、それを埋めるのがリンカです。
静的ライブラリ vs 動的ライブラリ¶
静的リンク: 動的リンク:
game.exe game.exe ──実行時に参照──> physics.dll
├─ main.o ├─ main.o (別ファイルのまま)
├─ physics.a の中の使われた.o └─ 「physics.dllのSimulateを使う」という記録
└─ ...(全部入り・自己完結)
| 静的リンク | 動的リンク(DLL/.so) | |
|---|---|---|
| 実行ファイル | 大きい(全部入り) | 小さい(参照だけ) |
| 起動・実行 | 速い・自己完結 | ロードの手間、ファイルが欠けると起動不能 |
| 更新 | 再ビルド・再配布が必要 | DLL差し替えだけで更新可(パッチ、プラグイン) |
| 複数プログラムでの共有 | 各自にコピー | メモリ・ディスクを共有できる |
| 最適化 | LTO(リンク時最適化)でライブラリ境界を越えたインライン化が可能 | 境界を越えた最適化は不可 |
| ABI問題 | ビルド時に固定(比較的安全) | 実行時に一致が必要(→ 下記) |
- 静的ライブラリのリンクは「使われた.oだけ取り込む」(アーカイブ全体ではない)。「リンクしたのにシンボルが見つからない」の一因はリンク順・参照順(→ リンクエラー)
- Windowsでは動的リンクでもインポートライブラリ(.lib)をリンク時に使う(「このDLLにこの関数がある」という目録)。「.libには静的ライブラリとインポートライブラリの2種類がある」ことは混乱の名所
DLLの実務(Windows/ゲーム)¶
// DLL側: 公開する関数に印を付ける(Windows)
extern "C" __declspec(dllexport) int PluginVersion() { return 3; }
// 使う側(暗黙リンク): ヘッダー+インポートライブラリで普通の関数のように呼ぶ
// 使う側(明示ロード): LoadLibrary("plugin.dll") → GetProcAddress("PluginVersion")
- C++の型・クラスをDLL境界で渡すのはABIの罠(→ 前ページ)。異なるコンパイラ/設定/CRTでビルドされたDLLとexeの間でstd::stringやnew/deleteの所有権を渡すと壊れる。境界はC ABI+プレーンなデータが鉄則
- 「確保したモジュールが解放する」(DLL内でnewしたものはDLL内でdelete)——CRTが別だとヒープも別のため
ゲーム開発での使われ方¶
- エンジン・ミドルウェアの配布形態: FMOD/Steamworks等はDLL+Cヘッダ(まさにABI安全策)
- ホットリロード: ゲームロジックをDLL化し、実行中に差し替えて反映(開発イテレーション高速化)。UEのLive Codingもモジュール(DLL)差し替えが基盤(→ 第10部)
- プラグイン/MOD: 明示ロード(LoadLibrary)+extern "C"エントリポイント
- コンソール機では静的リンクが基本(単一実行ファイル文化)——プラットフォームで流儀が変わる
C#との対応¶
- C#の.dll(アセンブリ)は別物: 機械語ではなくIL+メタデータで、ロード・結合は.NETランタイムが行い、ABI問題がない。「UnityのManaged DLL」と「ネイティブDLL」は同じ拡張子の別世界
- Unityの
Plugins/フォルダのネイティブプラグイン(.dll/.so/.dylib)がこのページの動的ライブラリで、P/Invoke([DllImport])がGetProcAddressに相当 - Assembly Definition(.asmdef)は「C#コードの静的ライブラリ分割」に似た開発時の構造化(→ 第9部)
Unity開発者が誤解しやすい点¶
- 「DLLを置けば動く」— ネイティブDLLはプラットフォーム・アーキテクチャ(x64/ARM64)・依存DLL(VCランタイム等)の一致が必要
- C# DLLの感覚でネイティブDLLにクラスを公開しようとする——境界はCの関数とデータに落とすのが定石
- 「実行ファイルは1つの塊」— 実際は起動時に多数のDLL(システム・ドライバ層含む)と結合されて動いている(Process Explorer等で見られる)
使う場面 / 使わない場面¶
- 静的リンク: 自プロジェクトの内部分割(既定)、コンソール機、配布を単純にしたいツール
- 動的リンク: ミドルウェアSDK、プラグイン/MOD、ホットリロード、更新パッチをexe再配布なしで当てたい場合
- 内部コードをむやみにDLL分割しない——境界維持(ABI・エクスポート管理)のコストは安くない
理解度チェック¶
- .oが「未完成品」である理由を2つの中身(未解決シンボル・再配置情報)で説明できますか。
- 静的/動的リンクのトレードオフを3つ挙げてください。
- DLL境界でstd::stringを渡すことが危険な理由は?
演習¶
samples/tu_demo/ で ar rcs libenemy.a enemy.o として静的ライブラリを作り、g++ main.o radar.o -L. -lenemy でリンクしてください。次にリンク順を変えて(-lenemyを先頭に)エラーが出るか観察してください(GNU ldのリンク順依存の体験)。
前: シンボルとリンケージ | カテゴリ目次 | 次: テンプレートの実体化とinlineの実際