# 機能設計書 68-動的ライブラリ

## 概要

本ドキュメントは、Juliaにおける共有ライブラリの動的ロードとシンボル解決機能（`Libdl.dlopen` / `dlsym` / `dlclose` / `cglobal`）の設計と実装を記述するものである。

### 本機能の処理概要

本機能は、実行時に共有ライブラリ（`.so` / `.dll` / `.dylib`）を動的にロードし、そこからシンボル（関数ポインタやグローバル変数）を取得する機能を提供する。`LazyLibrary` による遅延ロード機構も含む。

**業務上の目的・背景**：Julia のエコシステムでは、多数の外部 C/C++ ライブラリ（BLAS, LAPACK, GMP, libcurl 等）を JLL パッケージとして配布・利用する。これらのライブラリを実行時に動的にロードし、ccall で関数を呼び出すための基盤が本機能である。特に `LazyLibrary` は Julia 1.11 で導入され、ライブラリの遅延ロードによる起動時間短縮を実現する。

**機能の利用シーン**：JLL パッケージによるバイナリライブラリの利用、プラグインシステムの実装、プラットフォーム依存ライブラリの条件付きロード、ccall での外部関数呼び出しに利用される。

**主要な処理内容**：
1. `dlopen(libname, flags)` による共有ライブラリのロード
2. `dlsym(handle, symbol)` によるシンボル（関数ポインタ）の取得
3. `dlclose(handle)` によるライブラリのアンロード
4. `find_library(names, paths)` によるライブラリの検索
5. `dlpath(handle)` によるライブラリのフルパス取得
6. `dllist()` によるロード済みライブラリ一覧の取得
7. `LazyLibrary` による遅延ロードとスレッドセーフな初期化

**関連システム・外部連携**：OS の動的リンカ（`dlopen`/`LoadLibrary`）、libuv、LLVM。

**権限による制御**：ファイルシステムの読み取りパーミッションに依存。

## 関連画面

該当なし。本機能はプログラマティックに使用されるAPI。

## 機能種別

外部連携（ライブラリ管理）

## 入力仕様

### 入力パラメータ

| パラメータ名 | 型 | 必須 | 説明 | バリデーション |
|-------------|-----|-----|------|---------------|
| libfile | `AbstractString` | Yes（dlopen） | ライブラリファイル名またはパス | - |
| flags | `Integer` | No | dlopen フラグ（RTLD_LAZY 等） | 有効なフラグの組み合わせ |
| handle | `Ptr{Cvoid}` | Yes（dlsym/dlclose） | ライブラリハンドル | C_NULL でないこと |
| sym | `Union{Symbol, AbstractString}` | Yes（dlsym） | シンボル名 | - |
| throw_error | `Bool` | No | 失敗時に例外を投げるか | デフォルト `true` |

### 入力データソース

ファイルシステム上の共有ライブラリファイル、`DL_LOAD_PATH` リスト

## 出力仕様

### 出力データ

| 項目名 | 型 | 説明 |
|--------|-----|------|
| handle | `Ptr{Cvoid}` | ライブラリハンドル |
| sym_ptr | `Ptr{Cvoid}` or `Nothing` | シンボルアドレス |
| path | `String` | ライブラリのフルパス |
| libraries | `Vector{String}` | ロード済みライブラリ一覧 |

### 出力先

呼び出し元に値として返却

## 処理フロー

### 処理シーケンス

```
1. ライブラリパスの解決
   └─ DL_LOAD_PATH → システムパスの順に検索
2. ライブラリのロード
   └─ jl_load_dynamic_library でOS のdlopenを呼び出し
3. シンボルの解決
   └─ jl_dlsym でシンボルアドレスを取得
4. 関数呼び出し
   └─ 取得したアドレスを ccall で使用
5. ライブラリのアンロード（必要に応じて）
   └─ jl_dlclose でライブラリを解放
```

### フローチャート

```mermaid
flowchart TD
    A[dlopen libname flags] --> B{ライブラリ検索}
    B -->|DL_LOAD_PATH| C[パスリストを検索]
    B -->|システム| D[システムパスを検索]
    C --> E[jl_load_dynamic_library]
    D --> E
    E --> F{ロード成功?}
    F -->|Yes| G[ハンドル返却]
    F -->|No throw_error| H[例外送出]
    F -->|No !throw_error| I[nothing 返却]
    G --> J[dlsym handle sym]
    J --> K[jl_dlsym]
    K --> L{シンボル発見?}
    L -->|Yes| M[Ptr Cvoid 返却]
    L -->|No| N[nothing or 例外]
```

## ビジネスルール

### 業務ルール

| ルールNo | ルール名 | 内容 | 適用条件 |
|---------|---------|------|---------|
| BR-68-01 | 拡張子自動付加 | dlopen は `.so` / `.dll` / `.dylib` を自動的に付加して検索する | 拡張子なしのライブラリ名が指定された場合 |
| BR-68-02 | DL_LOAD_PATH 優先 | DL_LOAD_PATH のパスがシステムパスより先に検索される | dlopen 呼び出し時 |
| BR-68-03 | LazyLibrary スレッドセーフ | LazyLibrary の初期化は1スレッドのみが行い、他はブロックする | LazyLibrary の初回アクセス時 |
| BR-68-04 | 依存関係順序 | LazyLibrary はdependenciesを先にロードする | LazyLibrary のdependenciesが設定されている場合 |
| BR-68-05 | @executable_path | Julia 1.6 以降、`@executable_path/` はJulia実行ファイルのパスに置換される | dlopen 呼び出し時 |

### 計算ロジック

該当なし。

## データベース操作仕様

該当なし。

## エラー処理

### エラーケース一覧

| エラーコード | エラー種別 | 発生条件 | 対処方法 |
|------------|----------|---------|---------|
| - | ArgumentError | NULL ハンドルに対して dlsym を呼んだ場合 | 有効なハンドルを使用する |
| - | ErrorException | ライブラリが見つからない場合 | ライブラリパスを確認、DL_LOAD_PATH を設定 |
| - | ErrorException | シンボルが見つからない場合 | シンボル名を確認 |
| - | TypeError | dlopen に不正な型が渡された場合 | String, Symbol, LazyLibrary を使用 |

### リトライ仕様

該当なし。

## トランザクション仕様

該当なし。

## パフォーマンス要件

- LazyLibrary による遅延ロードで起動時間を短縮
- dlopen/dlsym のキャッシュにより繰り返し呼び出しのオーバーヘッドを削減

## セキュリティ考慮事項

- 動的ライブラリのロードにより任意のネイティブコードが実行される
- DL_LOAD_PATH の操作により意図しないライブラリがロードされる可能性がある（DLLインジェクション）

## 備考

- `dlext` 定数はプラットフォームに応じて `"so"`, `"dll"`, `"dylib"` のいずれか
- `LazyLibrary` は Julia 1.11 で導入された

---

## コードリーディングガイド

本機能を理解するために参照すべきファイルと、推奨する読み解き順序を以下に示す。

### 推奨読解順序

#### Step 1: データ構造を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 1-1 | libdl.jl | `base/libdl.jl` | RTLD_* 定数（24-31行目）: dlopen のフラグ定数 |
| 1-2 | libdl.jl | `base/libdl.jl` | `LazyLibraryPath` 構造体（339-342行目）: 遅延評価されるライブラリパス |
| 1-3 | libdl.jl | `base/libdl.jl` | `LazyLibrary` 構造体（428-459行目）: 遅延ロードライブラリ。path, flags, dependencies, on_load_callback, lock, handle を保持 |

**読解のコツ**: `LazyLibrary` は `@atomic handle` フィールドにロード済みハンドルをキャッシュする。`C_NULL` ならまだロードされていない。スレッドセーフな初期化は `@lock ll.lock` と `@atomic :acquire/:release` で実現されている。

#### Step 2: エントリーポイントを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 2-1 | libdl.jl | `base/libdl.jl` | `dlopen(s, flags)` 関数（119-125行目）: ライブラリのロード |
| 2-2 | libdl.jl | `base/libdl.jl` | `dlsym(hnd, s)` 関数（59-70行目）: シンボルの解決 |
| 2-3 | libdl.jl | `base/libdl.jl` | `dlclose(p)` 関数（167-169行目）: ライブラリのアンロード |

**主要処理フロー**:
1. **120行目**: `ccall(:jl_load_dynamic_library, ...)` でCランタイムに委譲
2. **62-64行目**: `ccall(:jl_dlsym, ...)` でシンボルアドレスを取得
3. **168行目**: `ccall(:jl_dlclose, ...)` でハンドルを閉じる

#### Step 3: LazyLibrary を理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 3-1 | libdl.jl | `base/libdl.jl` | `dlopen(ll::LazyLibrary, ...)` 関数（490-529行目）: 遅延ロードの実装 |
| 3-2 | libdl.jl | `base/libdl.jl` | `add_dependency!` 関数（480-484行目）: 動的な依存関係追加 |

**主要処理フロー**:
- **491行目**: `@atomic :acquire ll.handle` でキャッシュ確認
- **493-513行目**: `@lock ll.lock` でスレッドセーフな初期化
- **497-499行目**: 依存ライブラリを先にロード
- **502行目**: `dlopen(string(ll.path), flags)` で自身をロード
- **503行目**: `@atomic :release ll.handle = handle` でキャッシュ格納
- **506-508行目**: on_load_callback の実行

#### Step 4: ユーティリティを理解する

| 順序 | ファイル | パス | 読解ポイント |
|-----|---------|------|-------------|
| 4-1 | libdl.jl | `base/libdl.jl` | `find_library` 関数（199-218行目）: ライブラリ検索 |
| 4-2 | libdl.jl | `base/libdl.jl` | `dlpath` 関数（225-247行目）: ライブラリパス取得 |
| 4-3 | libdl.jl | `base/libdl.jl` | `dllist` 関数（294-318行目）: ロード済みライブラリ一覧 |

### プログラム呼び出し階層図

```
Libdl.dlopen(libname, flags)
    └─ jl_load_dynamic_library()     [src/dlload.c]
           └─ dlopen() / LoadLibrary()  [OS]

Libdl.dlsym(handle, sym)
    └─ jl_dlsym()                    [src/dlload.c]
           └─ dlsym() / GetProcAddress()  [OS]

Libdl.dlclose(handle)
    └─ jl_dlclose()                  [src/dlload.c]

dlopen(ll::LazyLibrary)
    │
    ├─ @atomic :acquire ll.handle    # キャッシュ確認
    │
    ├─ @lock ll.lock                 # スレッドセーフ初期化
    │      ├─ dlopen(dep)            # 依存ライブラリロード
    │      ├─ dlopen(ll.path)        # 自身のロード
    │      └─ on_load_callback()     # コールバック実行
    │
    └─ handle 返却

dllist()
    ├─ [macOS] _dyld_image_count / _dyld_get_image_name
    ├─ [Linux/BSD] dl_iterate_phdr
    └─ [Windows] jl_dllist
```

### データフロー図

```
[入力]                       [処理]                      [出力]

ライブラリ名 ────────▶ dlopen() ──────▶ Ptr{Cvoid} (handle)
DL_LOAD_PATH ────────▶     │
RTLD_* フラグ ───────▶     │
                           ▼
handle + シンボル名 ──▶ dlsym() ──────▶ Ptr{Cvoid} (func ptr)
                           │
                           ▼
                      ccall で使用 ──▶ C 関数呼び出し
```

### 関連ファイル一覧

| ファイル | パス | 種別 | 役割 |
|---------|------|------|------|
| libdl.jl | `base/libdl.jl` | ソース | Libdl モジュールの主実装 |
| dlload.c | `src/dlload.c` | ソース（C） | 動的ロードのランタイム実装 |
| c.jl | `base/c.jl` | ソース | ccall / cglobal の定義（No.66） |
| Libdl/ | `stdlib/Libdl/` | ソース | stdlib Libdl パッケージ（base/libdl.jl の再エクスポート） |
