16
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

Flat C APIのススメ

Last updated at Posted at 2015-12-03

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

// 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の関数が定義されています。

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をコントロールしています。

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

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

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++のヘッダ・オンリー・ライブラリレイヤを被せることにより、より使いやすいライブラリが構築できるようです。

以上

16
21
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
16
21

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?