[C言語] 共有ライブラリと静的ライブラリを整理する


なんとなく分かった気でいるふたつのライブラリ。
それを小さなテストコードを使いながら整理してみます。
*（ちなみに今回紹介するのはMacでのものです。Linuxではフォーマット、コンパイル方法などが異なります）*

## 静的ライブラリをコンパイルする

まずは静的ライブラリです。
ちなみにMacではMach-Oというフォーマットを使います。
*（Linuxでは「ELF(Executable and Linking Format)」というらしい）*

[こちらの記事](http://d.hatena.ne.jp/kanonji/20121018/1350538932)が分かりやすかったです。

また、静的ライブラリはオブジェクトファイルをまとめたただのアーカイブです。
（なので`ar`（アーカイブ）コマンドを使う）

例として以下のように静的ライブラリの中身を定義しました。

```c:static-library.c
#include <stdio.h>

int static_func() {
    printf("This is a static library demo.\n");
    return 0;
}
```

これをまずはオブジェクトファイルにします。

```console
$ gcc -c static-library.c
```

すると、`static-library.o`が生成されます。
これを静的ライブラリとするため、`ar`コマンドを使ってアーカイブします。

```console
ar r libstatic.a static-library.o
```

最後に、この静的ライブラリを使うファイルを作成します。

```c:main.c
#include <stdio.h>

int static_func();

int main() {
    static_func();
}
```

`int static_func();`でプロトタイプを宣言しています。
本来はここにはヘッダファイルなどを読み込ませたりしますが、今回はサンプルなので直に書いています。

あとはコンパイル時にリンクしてやれば無事、静的ライブラリがリンクされます。

```console
$ gcc main.c -lstatic
```

引数オプション（`-l`）に、ファイル名から`lib`と拡張子を取ったものを指定します。
（今回の例では`libstatic.a`を生成しているので`-lstatic`としています）

これを実行すると以下のようになります。

```console
$ ./a.out
This is a static library demo.
```

ちなみに「リンカ」がやってくれることをざっくり[説明してくれている記事](http://www.yunabe.jp/docs/static_library.html)がありました。

リンカがやっている概要を引用させてもらうと、

>### 大雑把な流れ
リンカは未解決の参照のリストを保持している。はじめ、このリストにはmainが入っている [1]。 リンカはオブジェクトファイル(.o)を受け取り、参照を解決し、新しい未解決の参照がみつかったらリストに追加する（リンクする）。 静的ライブラリ(.a)を受け取った場合は、未解決の参照を定義しているオブジェクトファイルのみを静的ライブラリから抜き出し、リンクを行う。 いままで処理したオブジェクトファイルから参照されていないオブジェクトファイルは 静的ライブラリに含まれていても無視される。 

ということのようです。

-------------------------------------------

## 共有ライブラリをコンパイルする

### Mac版

次に共有ライブラリをコンパイルします。

```c:shared-lib.c
#include <stdio.h>

int hoge_func() {
	printf("This is a shared library.\n");
	return 0;
}
```

単純に`printf`を実行するだけの関数です。
これを以下のようにしてコンパイルします。
*（ちなみに共有ライブラリの拡張子は`.dylib`です）*

```console
$ gcc -dynamiclib -o libshared.dylib shared-lib.c
```

### Windows版

[2018.02.20 追記]

Windowsで同様のことをやる場合は、Cygwinを利用すると楽です。
コマンドで以下のようにします。

```console
$ gcc -c dlltest.c
$ ls
dlltest.c dlltest.o
```

`-c`オプションは、「`Compile and assemble, but do not link`」と書かれているので、コンパイルだけを行うオプションです。

コンパイルが成功するとオブジェクトファイル（拡張子が`.o`のもの）が生成されます。
これを、DLLに変換します。

```console
$ gcc -shared -o dlltest.dll dlltest.o
$ ls
dlltest.c  dlltest.dll*  dlltest.o
```


## 共有ライブラリをリンクする

### Mac版

次に、今作った共有ライブラリをリンクして使う`main`関数を作ります。

```c:main.c
#include <stdio.h>

int hoge_func();

int main() {
    hoge_func();
}
```

これを以下のようにコンパイルします。

```console
$ gcc -o main main.c libshared.dylib
```

### Windows版

Windowsでは以下のようにリンクさせてコンパイルします。

```console
$ gcc -I./ -L./ -o hoge.exe main.c -ldlltest
$ ls
dlltest.c  dlltest.dll*  dlltest.o  hoge.exe*  main.c
```


そして実行すると、

```console
$ ./main
This is a shared library.
```

と出力されます。

ただ、これだと静的ライブラリとの違いが分からないので、今度は共有ライブラリを修正してみます。

```c:shared-lib.c
#include <stdio.h>

int my_func() {
    printf("This is a shared library. hogehoge\n");
    return 0;
}
```

`printf`の中身をちょっとだけ変えました。

これを、前回と同じようにライブラリだけコンパイルしなおします。
そのあとで、`main`はコンパイルしなおさずに実行してみます。

```console
$ ./main
This is a shared library. hogehoge
```

`main.c`はコンパイルしなくても出力が変わるのが確認できます。
これが共有ライブラリの意味ですね。


### 共有ライブラリについてメモ

どうやらMacとLinuxでは共有ライブラリについての仕組みが異なるようです。
拡張子やフォーマットの違いから始まり、さらには`Dynamic Linking`と`Dynamic Loading`という2種類があるようです。

MacのMach-Oフォーマットではこれらは厳密に区別されており、前者を`.dylib`で、後者を`.bundle`で区別するようです。
*（ちなみに`.bundle`はAppleが推奨しているだけで、拡張子はなんでもいいようです。Linuxと合わせるために`.so`で生成している場合もあるみたいです）*

このあたりの[詳しい話はこちら](http://d.hatena.ne.jp/kanonji/20121018/1350538932)。


### 共有ライブラリを動的にロードしてみる

上記の例ではコンパイル時にライブラリをリンクしていましたが、実行時にロードする方法もあります。

```c:dynamic-load
#include <stdio.h>
#include <dlfcn.h>

typedef void (*hoge_test)(void);

int main() {
	void *handle   = NULL;
	hoge_test func = NULL;
	
	handle = dlopen("./libshared.dylib", RTLD_LAZY);
	if (handle == NULL) {
        fprintf(stderr, "error: dlopen\n");
        return -1;
	}
	
	// handleから`hoge_func`のシンボルを取り出す。
	func = (hoge_test)dlsym(handle, "hoge_func");
	if (func == NULL) {
        fprintf(stderr, "error: dlsym\n");
        return -1;	
	}
	
	// 実行
	func();
	
	if (handle != NULL) {
		if (dlclose(handle) != 0) {
            fprintf(stderr, "error: dlclose\n");
            return -1;
		}
	}
	
	return 0;
}
```

`dlopen`、`dlsym`、`dlclose`を使って動的に読み込ませています。

ライブラリはいくつものファイルやオブジェクトを効率的にまとめる方法です。
また動的ライブラリによって再コンパイルがいらない点もメリットです。

ライブラリは普通に使えるし、なにをしてくれているかも把握していましたが、実際に自分で書いてみるとさらに色々分かるからいいですね。