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の関数が定義されています。
/* 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をコントロールしています。
#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
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
{
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++のヘッダ・オンリー・ライブラリレイヤを被せることにより、より使いやすいライブラリが構築できるようです。
以上