C++:動的ライブラリと静的ライブラリってなんだっけ

## はじめに

この記事は「[完全に理解したTalk Advent Calendar 2020](https://qiita.com/advent-calendar/2020/easyeasy)」10日目の投稿記事です。

ライブラリを使ってたら、完全理解してしまったのでその話をします。

本記事では、cmakeとc++を使って解説していきます。

説明用にiconvというライブラリを使用します。
ちなみにiconvとは、文字コードの変換を行うライブラリになります。

## 静的ライブラリ

静的ライブラリと動的ライブラリが存在します。
まず静的ライブラリについてです。


### 静的ライブラリの作り方
cmakeでは、こう書きます。

```cmake
add_library(StaticIconv STATIC UseIconv.cpp) #静的ライブラリをビルド
target_link_libraries(StaticIconv iconv) #リンクはこれでOK
``` 
すると、`UseIconv.cpp `をビルドして生成された、
`libStaticIconv.a`
というファイルが生成されます。
これが、静的ライブラリになります。

また、`target_link_libraries`で他のライブラリをリンクすることも可能です。
今回はiconvを静的リンクしました。文字コードを変換できるライブラリになります。

### 静的ライブラリのリンクのやり方
静的ライブラリを使うには、
プログラムをビルドするときにリンクさせる必要があります。

先に、リンクの方法を書いてしまいましたが、改めて。。

では、リンクの方法をcmakeではこのようにして行います。
生成したそのライブラリをプログラムにリンク、そしてビルドするところまで書きます。

```cmake
add_executable(testStaticLink main.cpp) #メインプログラムをビルド
target_link_libraries(testStaticLink StaticIconv) #メインプログラムに静的ライブラリをリンク
```

出来上がった実行ファイルを見てみましょう！
実行ファイルにリンクするとき、静的ライブラリは実行ファイル内にコピーが行われます。
したがって、出来上がった実行ファイルは静的リンクしたライブラリが含まれます。

自分の環境では、プログラムとライブラリのサイズはこうなりました。

```
$ ls -lh
-rw-r--r--   1 ararakikochan  131K libStaticIconv.a
-rwxr-xr-x   1 ararakikochan  110K testStaticLink
```

131K-110K=21K
ざっとした計算になりますが。
ヘッダ情報が含まれるので、おおよそですが10Kがmain.cppの処理になると予想できます。

## 動的ライブラリ

### 動的ライブラリの使い方
同じく、とりあえずcmakeで書いたものを、載せます。

```
add_library(SharedIconv SHARED UseIconv.cpp) #動的ライブラリをビルド
target_link_libraries(SharedIconv iconv) #リンクはこれでOK
```

というふうに、静的ライブラリの生成と似ています。
cmake的に変わったのはSTATICがSHAREDに変わった事です。

### 動的ライブラリのリンクの方法

動的ライブラリのリンクのやり方は2つあります。
一つは、動的リンクもう一つは、動的ロードです。
動的ロードについては後述します。

#### 動的リンク
静的リンクはビルド時にリンクを行いますが、
動的リンクでできた実行ファイルは動的ライブラリのどの部分をつかうかを記録し、
実行ファイルを起動したタイミングでリンクを行います。

では、動的リンクをcmakeではこのように書きます。
生成したそのライブラリをプログラムにリンク、そしてビルドするところまで書きます。

```cmake
add_executable(testSharedLink main.cpp) #メインプログラムをビルド
target_link_libraries(testSharedLink SharedIconv) #メインプログラムに動的ライブラリをリンク
```

では、静的ライブラリの時と同じようにファイルを見てみます。

```
ls -lh
-rwxr-xr-x   1 ararakikochan  82K libSharedIconv.dylib
-rwxr-xr-x   1 ararakikochan  64K testSharedLink
```



静的ライブラリの時より動的ライブラリの方が、かなりサイズが小さくなったと思います。
ちなみに、.dylibはmacOSの動的ライブラリの拡張子になります。
.soはLinux、.dllはWindowsの動的ライブラリの拡張子になります。

動的リンクを行うと、ライブラリのインターフェース情報などの実際の処理と関係がない
部分のみが実行ファイルにコピーされます。
コピーされた情報をもとに動的ライブラリにアクセスしてメインの処理を実行します。
処理が別々のファイルに書かれていることになります。

82K+64K=146K
静的リンクしてできた実行ファイルの方が動的リンクして作った実行ファイルより、小さいことがわかります。
ただし、動的ライブラリは他の実行ファイルからもリンクは可能ですので、複数のソフトウェアが同じ動的ライブラリをリンクすると、結果的には全体のサイズは小さくなります。

`testSharedLink`を実行する前に、`libSharedIconv.dylib`を削除してしまうと、以下のように、実行ができなくなります。
これは、処理の実体が見つからないからです。

```
$ ./testSharedLink 
dyld: Library not loaded: @rpath/libSharedIconv.dylib
  Referenced from: ~/testLink/build/./testSharedLink
  Reason: image not found
Abort trap: 6
```


#### 動的ロード
あらかじめビルド時にリンクを行わない方法です。
開発しているプログラム上で動的ライブラリを開き、中のヘッダー情報を読み取ることで、
動的ライブラリに記載されている処理を実行する方法になります。

こちらの詳しい実装は
[c++で動的ライブラリを動的ロードするぞ。(.so、.dylib、プラグイン)](https://qiita.com/kikoaac/items/3bcd5d46fcc2cc922ab9)
に載せています。


### 今回のソースコード
それでは、以上の説明のために用意したソースコードを載せます。

```cmake:CMakeLists.txt

cmake_minimum_required(VERSION 3.15)
project(testLink)

set(CMAKE_CXX_STANDARD 14)

add_library(SharedIconv SHARED UseIconv.cpp)
target_link_libraries(SharedIconv iconv)
add_library(StaticIconv STATIC UseIconv.cpp)
target_link_libraries(StaticIconv iconv)

add_executable(testSharedLink main.cpp )
target_link_libraries(testSharedLink SharedIconv)
add_executable(testStaticLink main.cpp )
target_link_libraries(testStaticLink StaticIconv)
```

```c++:main.cpp
#include <iostream>
#include "UseIconv.hpp"

int main ( int argc, char* argv[] )
{
  useIconv::UseIconv conv("cp932",    // output encoding
                          "UTF-8"    // input encoding
                          );
  std::string input = "魞魹魦魲魵鮄鮊鮏鮞鮧鯁鯎鯥鯸鯽鰀鰣鱁鱏鱐鱓鱣鱥鱷";
  std::string center;
  std::string output;
  conv.convert(input, center);
  useIconv::UseIconv conv2("UTF-8",    // output encoding
                           "cp932"    // input encoding
                           );
  conv2.convert(center, output);
  std::cout << input << std::endl;
  std::cout << center << std::endl;
  std::cout << output << std::endl;
}
```

```c++:UseIconv.hpp
#ifndef TESTLINK_USEICONV_HPP
#define TESTLINK_USEICONV_HPP

#include <errno.h>
#include <iconv.h>

#include <iostream>
#include <stdexcept>
#include <vector>
namespace useIconv {
class UseIconv {
public:
  UseIconv(const std::string& out_encode, const std::string& in_encode,size_t buf_size = 1024);

  ~UseIconv();

  void convert(const std::string& input, std::string& output);

private:
  void check_convert_error();

  iconv_t iconv_;
  bool ignore_error_;
  const size_t buf_size_;
};
}    // namespace UseIconv
#endif
```

```c++:UseIconv.cpp
#include "UseIconv.hpp"

namespace useIconv {
UseIconv::UseIconv(const std::string& out_encode, const std::string& in_encode,size_t buf_size) : ignore_error_(true), buf_size_(100) {
  if (buf_size == 0) {
    throw std::runtime_error("buffer size must be greater than zero");
  }

  iconv_t conv = iconv_open(out_encode.c_str(), in_encode.c_str());
  if (conv == (iconv_t)-1) {
    if (errno == EINVAL)
      throw std::runtime_error("not supported from " + in_encode + " to " + out_encode);
    else
      throw std::runtime_error("unknown error");
  }
  iconv_ = conv;
}

UseIconv::~UseIconv() { iconv_close(iconv_); }

void UseIconv::convert(const std::string& input, std::string& output) {
  // copy the string to a buffer as Iconvcpp function requires a non-const char
  // pointer.
  std::vector<char> in_buf(input.begin(), input.end());
  char* src_ptr = &in_buf[0];
  size_t src_size = input.size();

  std::vector<char> buf(buf_size_);
  std::string dst;
  while (0 < src_size) {
    char* dst_ptr = &buf[0];
    size_t dst_size = buf.size();
    size_t res = ::iconv(iconv_, &src_ptr, &src_size, &dst_ptr, &dst_size);
    if (res == (size_t)-1) {
      if (errno == E2BIG) {
        // ignore this error
      } else if (ignore_error_) {
        // skip character
        ++src_ptr;
        --src_size;
      } else {
        check_convert_error();
      }
    }
    dst.append(&buf[0], buf.size() - dst_size);
  }
  dst.swap(output);
}

void UseIconv::check_convert_error() {
  switch (errno) {
  case EILSEQ:
  case EINVAL:
    throw std::runtime_error("invalid multibyte chars");
  default:
    throw std::runtime_error("unknown error");
  }
}
}    // namespace UseIconv
```

## 最後に
これで、ライブラリについては以上となります。
多少、ライブラリについて理解できたでしょうか。

まだまだ、奥が深いですので、ネタが尽きないと思います。
別の言語で作ったライブラリを別の言語から呼び出すことも可能ですので、
こちらは、またいずれお話しできたらと思います。