Flat C APIのススメ

C言語への感謝の正拳突き 4日目です。

どうも最近、社内で
「CのAdventCalendarを乗っ取ってC++のステマをするなんて、、、、」
などという誤解を招いているらしく、
その誤解を晴らすためにも、マクロネタは止めて、
CのAPIに関して紹介しようと思います。

#概要

Flat C APIというのは、「C++のためのAPIデザイン」という本で紹介されているAPIの分類のうちの一つで、、
こちらの投稿が大変わかりやすく纏められています。
http://qiita.com/narumi888/items/dcae9a58dfd63adc68b4

または、こちらのCppCon2014の資料が大変詳しいです。
https://github.com/CppCon/CppCon2014/tree/master/Presentations/Hourglass%20Interfaces%20for%20C%2B%2B%20APIs

Flat C APIというのは、ざっくりいうと、C89の関数 + opaque pointerで構成されたAPIのことで、
C++のライブラリがFlat C APIも並行して提供していることがあります。
C++のAPIをFlat C APIに変換して外部に公開するのは、C++のAPIをCから使いやすくすることもありますが、
ライブラリのABI互換性を保ちやすくします。

# 具体例
leveldbから引用します。
https://github.com/google/leveldb/blob/master/include/leveldb/c.h

```C++:
// A DB is a persistent ordered map from keys to values.
// A DB is safe for concurrent access from multiple threads without
// any external synchronization.
class DB {
 public:
  // Open the database with the specified "name".
  // Stores a pointer to a heap-allocated database in *dbptr and returns
  // OK on success.
  // Stores NULL in *dbptr and returns a non-OK status on error.
  // Caller should delete *dbptr when it is no longer needed.
  static Status Open(const Options& options,
                     const std::string& name,
                     DB** dbptr);

  DB() { }
  virtual ~DB();

  // Set the database entry for "key" to "value".  Returns OK on success,
  // and a non-OK status on error.
  // Note: consider setting options.sync = true.
  virtual Status Put(const WriteOptions& options,
                     const Slice& key,
                     const Slice& value) = 0;

  // Remove the database entry (if any) for "key".  Returns OK on
  // success, and a non-OK status on error.  It is not an error if "key"
  // did not exist in the database.
  // Note: consider setting options.sync = true.
  virtual Status Delete(const WriteOptions& options, const Slice& key) = 0;

  // If the database contains an entry for "key" store the
  // corresponding value in *value and return OK.
  //
  // If there is no entry for "key" leave *value unchanged and return
  // a status for which Status::IsNotFound() returns true.
  //
  // May return some other Status on an error.
  virtual Status Get(const ReadOptions& options,
                     const Slice& key, std::string* value) = 0;

```

C++のAPI以外に、Cの関数が定義されています。

```C:include/leveldb/c.h

/* Exported types */

typedef struct leveldb_t               leveldb_t;
typedef struct leveldb_options_t       leveldb_options_t;
typedef struct leveldb_readoptions_t   leveldb_readoptions_t;
typedef struct leveldb_writeoptions_t  leveldb_writeoptions_t;
/* DB operations */

extern leveldb_t* leveldb_open(
    const leveldb_options_t* options,
    const char* name,
    char** errptr);

extern void leveldb_close(leveldb_t* db);

extern void leveldb_put(
    leveldb_t* db,
    const leveldb_writeoptions_t* options,
    const char* key, size_t keylen,
    const char* val, size_t vallen,
    char** errptr);

extern void leveldb_delete(
    leveldb_t* db,
    const leveldb_writeoptions_t* options,
    const char* key, size_t keylen,
    char** errptr);

/* Returns NULL if not found.  A malloc()ed array otherwise.
   Stores the length of the array in *vallen. */
extern char* leveldb_get(
    leveldb_t* db,
    const leveldb_readoptions_t* options,
    const char* key, size_t keylen,
    size_t* vallen,
    char** errptr);

```
比較してみると分かりますが、
C++のclassをopaque pointerにし、
各メソッドは、第一引数にopaque pointerをとるCの関数になっています。


# 外部に公開するシンボルの制御(attribute)

Flat C APIとして外部に公開する以外にも、
不要なシンボルを外部に公開しないように制御することにより、ABI互換性を保ちやすくなります。

leveldb本家では行われていませんが、BoostCon2014の資料のほうでは、
マクロ(attribute)を使って外部に公開する関数のvisibilityをコントロールしています。

https://github.com/CppCon/CppCon2014/blob/master/Presentations/Hourglass%20Interfaces%20for%20C%2B%2B%20APIs/code/visibility.h


```C:visibility.h
#pragma once

#if defined(_WIN32) || defined(__CYGWIN__)
  #ifdef hairpoll_EXPORTS
    #ifdef __GNUC__
      #define HAIRPOLL_EXPORT __attribute__ ((dllexport))
    #else
      #define HAIRPOLL_EXPORT __declspec(dllexport)
    #endif
  #else
    #ifdef __GNUC__
      #define HAIRPOLL_EXPORT __attribute__ ((dllimport))
    #else
      #define HAIRPOLL_EXPORT __declspec(dllimport)
    #endif
  #endif
#else
  #if __GNUC__ >= 4
    #define HAIRPOLL_EXPORT __attribute__ ((visibility ("default")))
  #else
    #define HAIRPOLL_EXPORT
  #endif
#endif
```

# 外部に公開するシンボルの制御(Linker)

外部に公開するシンボルに関しては、linkerでさらに細かく制御することができます。
Linkerでの制御が必要になるのは、他のライブラリをstatic linkした際に、ライブラリの不要なシンボルを隠したいときなどです。
例をあげると、C++のAPIをFlat C APIとして公開した上で、内部で使用しているSTLやBoostのシンボルが外部に漏れないようにするときでしょうか。
Windowsの場合、モジュール定義ファイルを用意すればよいです。
https://msdn.microsoft.com/ja-jp/library/28d6s79h.aspx

```C:leveldb.def
LIBRARY leveldb
EXPORTS
        leveldb_open
        leveldb_close
        leveldb_get
        leveldb_delete
        leveldb_put

```

Linuxの場合、ldの入力に指定するversion scriptで同様のことができます。
http://www.scoberlin.de/content/media/http/informatik/gcc_docs/ld_3.html#SEC39

```C:leveldb.map
{
  global:
  leveldb_open;
  leveldb_close;
  leveldb_get;
  leveldb_delete;
  leveldb_put;
  local: *;
};

```

mapファイルの先頭にBOMとか付いてるとlink errorになるので注意が必要です。
また、ld.bfd ld.goldの双方がサポートしています。

# 最後に

ABI互換が保たれたFlat C APIの上に、さらにModernなC++のヘッダ・オンリー・ライブラリレイヤを被せることにより、より使いやすいライブラリが構築できるようです。

以上