読者です 読者をやめる 読者になる 読者になる

ゲーム業界エンジニアの独り言

クライアント、サーバ、インフラなどなど興味あることなんでも紹介

C++ APIスタイル(フラットC API)

今、「C++のためのAPIデザイン」を読んでます。 API機能を表現するための方法(スタイル)についてまとめてみました。

C++のためのAPIデザイン

C++のためのAPIデザイン

APIスタイルについて・・・

この4つのスタイルのAPIを解説し、状況によって適切なスタイルの 選び方を提示します。

この記事では、以下のAPIスタイルを解説します。

  • フラットC API

フラットC API

フラットC APIとは・・・

CコンパイラコンパイルできるAPI。 自由関数の集合と、それをサポートするデータ構造と定数で構築される。 オブジェクトや継承のないインターフェイスであるため「フラット」と呼ばれる。

C APIの代表例

標準Cライブラリ Cでプログラムを記述するプログラマにはお馴染みライブラリ。

Windows API Microsoft Windowsのアプリケーションを開発するために使用するための インターフェイスのコアセット。

LinuxカーネルAPI LinuxカーネルはすべてプレーンなCで記述されている。

画像ライブラリ 各種の画像ファイル形式のサポートをアプリケーションに追加する 大半のオープンソース画像ライブラリは、すべてCで記述されている。

ANSI Cの機能

C++に慣れているプログラマがプレーンなCでAPIを記述する場合には 多くの言語機能が使えなくなることに注意が必要です。

C言語APIは一般に以下の機能のみから構成されます。

  • int, float, char, doubleなどの基本型とこれらの配列とポインタ
  • typedef, enumキーワードで作成したカスタム型
  • struct, unionキーワードで宣言したカスタム構造体
  • グローバルな自由関数
  • defineなどのプリプロセッサ命令

C言語は、C++の厳密なサブセットではありませんが、 多くの場合は優れたANSI CプログラムはC++のプログラムでもあります。

一般に、CコンパイラよりC++コンパイラのほうが型にチェックが厳しいため プレーンなCでAPIを記述する際は、まずC++コンパイラコンパイルして 発生する警告やエラーを修正するのも効果的です。

C APIコンパイルC++コンパイルを使うと厳しい型チェックが行われ C++でも使えるAPIになる。

ANSI Cの利点

C言語APIを記述する主な理由は、完全にCで記述された既存のプロジェクトと 統合できる事です。 先程、示したように大規模なCのAPIは今日でも使われています。 例えば、LinuxカーネルAPIを使っている場合はCで記述する必要があります。

もちろん、CとC++の双方で動くAPIを作る事も出来ます。 C++記述によってオブジェクト指向の機能を活用する一方で、このインターフェイスの プレーンなCのラッパーを構築してC言語のみプロジェクトで使ったりする事も可能です。

ANSI CによるAPIの記述

C言語ではクラスがサポートされてされていないため、処理を行うメソッドと共に データをオブジェクトにカプセル化することは出来ません。 その代わり、データを格納する構造体を宣言してデータを処理する関数に渡します。

以下のC++のクラスの定義を見ていきましょう。

class Stack
{
public:
    void Push(int value);
    int Pop();
    bool IsEmpty() const;

private:
    int *stack_;
    int current_size_;
};

これをフラットなCのAPIにすると以下のようになります。

struct Stack
{
    int *stack_;
    int current_size_;
};

void StackPush(struct Stack *stack, int value);
int StackPop(struct Stack *stack);
bool StackIsEmpty(struct Stack *stack);

スタックに関するCの関数は、データ構造のStackをパラメータとして 受け取る必要があります。

また、関数名はC++のようにクラス宣言で名前空間を定める事が出来ないため、 操作対象のデータを示す文字を含める事が多いです。 ここでは、Stackを接頭辞として付けてデータ構造のStackを操作する 関数であることを明確にしています。

以下のように、オペークポインタを使ってプライベートデータを隠蔽することも可能です。

typedef struct Stack *StackPtr

void StackPush(StackPtr stack, int value);
int StackPop(StackPtr stack);
bool StackIsEmpty(StackPtr stack);

また、Cはコンストラクタやデストラクタをサポートしていないため、 クライアントが明示的に初期化し、破棄しなくてはいけません。 これを行うためには、データ構造の作成と破棄のAPIを追加します。

StackPtr StackCreate();
void StackDestroy(StackPtr stack);

さて、同じタスクを行うC APIC++ APIのコードを比較したので、 今度は、それぞれのAPIスタイルを使うためのクライアントの記述を見ていきます。

まずは、C++ APIを使った例では以下のようになります。

Stack *stack = new Stack();
if (stack)
{
    stack->Push(10);
    stack->Push(3);
    while (!stack->IsEmpty())
    {
        stack->Pop()
    }
    delete stack;
}

次のC APIは以下のようなります。

StackPtr stack = StackCreate();
if (stack)
{
    StackPush(stack, 10);
    StackPush(stack, 3);
    while (!StackIsEmpty(stack))
    {
        StackPop(stack);
    }
    StackDestroy(stack);
}

C++からのC関数の呼び出し

C++コンパイラはCコードをコンパイルする事も出来ます。 現在の開発ではC APIだからといってC++コンパイラを使わない手はありません。 しかも、比較的簡単な作業なので、C APIをリリースする際には行って損はありません。

最初に確認すべきは、コンパイル出来るかどうかです。 先に述べた通り、C言語の規格はC++より厳しくないためC++コンパイルで比べると、 Cコンパイラは不注意なコードでもコンパイル出来る可能性は高いです。

この作業では、コード内にC++の予約名が使われていないかの確認も必要です。 例えば、以下のコードはCでは正しいですが、C++ではエラーになります。 理由はC++では[class]がC++では予約語だからです。

enum class {RED, GREEN, BLUE};

最後にCの関数はC++とはリンゲージが異なります。 つまり、CコンパイラC++コンパイが作成したオブジェクトファイルでは、 同じ関数でも表現が異なります。 これはC++でサポートされたオーバーロードが原因の一つです。 その結果、C++の関数名は、パラメータや個数や型などの追加情報を 符号化するために「マングル」されます。

例えば、DoAction()というか関数を使ったC++コードをコンパイルしても、 DoAction()関数をを定義するCコンパイラで生成したライブラリにリンク出来ません。

この問題を切り抜けるには、extern"C"構造にC APIをラップして、 格納された関数にはCスタイルのリンゲージを使うように、C++コンパイラに明示します。 このステートメントはCコンパイラではパース出来ないため、C++コンパイラのみで 条件付きコンパイルしましょう。

#ifdef __cplusplus
extern "C" {
#endif

// あなたのC API宣言
#ifdef __cplusplus
}
#endif

C APIのヘッダにextern "C"スコープを使って、C++があなたのAPIを正しくコンパイルとリンクが出来るようにしよう。