外部の C のコードにインタフェースする

Cython の主な用途の一つに、 C のコードで書かれた外部のライブラリをラッ プするというものがあります。ラップするには、使いたいライブラリ中 の C の関数や変数を extern 宣言します。

ラップの方法には、public 宣言を使って、 Cython のモジュール中の C の関 数や変数を、外部の C のコードから使えるようにするものもあります。この 方法は、そうそう頻繁には必要になりませんが、例えば他のアプリケーション の中に、スクリプト言語として Python を埋め込む 場合に必要になってき ます。 Cython モジュールは Python コードから C のコードを呼び出す際の ブリッジとして使えるので、逆に C のコードから Python のコードを呼び出 すときにも使えるのです。

extern 宣言

デフォルトの挙動では、モジュールレベルで宣言した C の関数や変数は、モ ジュールローカルのスコープを持ちます (C の static 記憶クラスになります)。 extern で宣言すると、モジュール外のどこかで定義されていることを表しま す。例えば:

cdef extern int spam_counter

cdef extern void order_spam(int tons)

C のヘッダファイルを参照する

上の例のように、自分のコードの中で extern 定義を行うと、 Cython は宣言 を組み込んだ C のファイルを生成します。しかし、この方法だと、コード中 に入れた宣言と他の C のコードから見える宣言とが厳密に一致しない場合に 問題を引き起こします。既存の C ライブラリをラップするときなどには、ラ イブラリの他の部分と完全に同じ宣言で C のコードを生成してコンパイルせ ねばなりません。

この問題を解決するため、 Cython に対して、 C のヘッダから宣言を探すよ うに指示できます:

cdef extern from "spam.h":

    int spam_counter

    void order_spam(int tons)

ここで、 cdef extern from 節は、以下の三つの仕事をします:

  1. 生成した C のコード中に、指定した名前のヘッダファイルの #include 文を出力するよう Cython に指示します。
  2. Cython がブロック中の宣言に対して C のコードを生成しないよう抑制し ます。
  3. ブロック中の全ての宣言を、 cdef extern で宣言したかのように扱い ます。

大事なことは、 Cython 自身は C のヘッダファイルを読まないので、 Cython 版の宣言を自分でせねばならないということです。ただし、 Cython 版の宣言 は、必ずしも C のコードと一致している必要はありません。場合によっては 一致させてはならなかったり、できなかったりします。とりわけ:

  1. const は使えません。Cython は const の扱いかたを全く知らな いので、放っておきましょう。キャストの必要があるといったごく稀な場 合を除き、たいていは何の問題も起きません。以下のように、明示的な宣 言は可能です:

    ctypedef char* const_char_ptr "const char*"
    

    ただし、必要なケースはほとんどないでしょう。

    Warning

    const の問題は、以下のようなコードで:

    cdef extern from "grail.h":
        char *nun
    

    grail.h の中に、実際には下記のような定義があり:

    extern const char *nun;
    

    以下のような記述をした場合に起きます:

    cdef void languissement(char *s):
        #something that doesn't change s
    
        ...
    
    languissement(nun)
    

    このコードに対して、 C コンパイラは文句を言うでしょう。回避す るには、キャストして const を取り去ります:

    languissement(<char *>nun)
    
  2. __declspec() のような、プラットフォーム固有の拡張による C の宣 言はそのままにしてください。

  3. ヘッダファイルで巨大な構造体が宣言されていて、そのほんの一部のメン バしか使わない場合、対象のメンバだけを宣言すれば十分です。C コンパ イラはヘッダファイルから構造体の定義を取り出して使うので、その他の メンバは放っていても問題ありません。

    構造体メンバに一切アクセスする必要がない場合には、単に構造体本体の 宣言だけを入れておいてもかまいません:

    cdef extern from "foo.h":
        struct spam:
            pass
    

    Note

    このような書き方は、 cdef extern from ブロック下でのみ使え ます。他の場所では、空の構造体は宣言できません。

  4. ヘッダファイル中で、 word のようなプラットフォーム固有の 数値型を typedef している場合、対応する ctypedef 文 が必要です。ただし、二つの宣言を厳密に一致させる必要はなく、単に数 値型の種類 (int, float など) を合わせるだけでかまいません。例えば:

    ctypedef int word
    

    は、実際の word のサイズが何であっても (ヘッダファイルに 正しい定義がある限り) 問題なく動きます。 Python の型との相互変換で は、 ctypedef した方の型を使います。

  5. ヘッダファイルがマクロを使って定数を定義している場合、ダミーの enum 宣言に翻訳してください。

  6. ヘッダファイルでマクロを使った関数を定義している場合は、普通の関数 として宣言し、適切な引数と戻り値型を持たせてください。

  7. 古典的な理由から、 C ではパラメタを持たない関数を void で宣言す ることがあります。 Cython では、 Python と同様、引数を持たない関数 は、単に foo() のように宣言してください。

小技と tips:

  • ある C のヘッダファイルを使うために、別のヘッダファイルを include す る必要があり、宣言は不要の場合には、 extern-from ブロックに pass を 入れて下さい:

    cdef extern from "spam.h":
        pass
    
  • 外部の宣言を include する必要があるが、ヘッダファイルを指定したくな い場合 (すでに他のヘッダで include した、などの場合) には、ヘッダファ イル名の代わりに * を指定してください:

    cdef extern from *:
        ...
    

構造体、共用体、 enum の宣言スタイル

C のヘッダファイル中で構造体、共用体、 enum を宣言する方法には、主に二 つのスタイルがあります。一つはタグ名を使う方法、もう一つは typedef です。他にも、二つを様々に組み合わせたバリエーションがいくつかあります。

肝心なのは、 Cython でも、ヘッダファイルで使われている宣言スタイルに合 わせて、コードを生成する際に、型への適切な参照を出力させねばならないと いうことです。そのため、 Cython では、構造体、共用体、enum 型の宣言に は、二種類の文法を用意しています。これまでの例で紹介してきたのは、タグ 名を使う方法でした。もう一つの方法で宣言するには、この後示すように、宣 言の前に ctypedef を置きます。

以下のテーブルには、ヘッダファイルで使われている幾つかのスタイルと、そ れに合わせて cdef extern from ブロックに書かねばならない Cython の 宣言をまとめてあります。ここでは構造体の宣言で例を書いていますが、共用 体や enum の宣言も同様です。

C のコード Cython のコードとして書けるもの コメント
struct Foo {
  ...
};
cdef struct Foo:
  ...
Cython の生成した C のコード中では、構造体は struct Foo と宣言 したかのように扱われます。
typedef struct {
  ...
} Foo;
ctypedef struct Foo:
  ...
Cython の生成した C のコード中では、構造体は単に Foo と宣言 したかのように扱われます。
typedef struct foo {
  ...
} Foo;
cdef struct foo:
  ...
ctypedef foo Foo # 省略可

または:

ctypedef struct Foo:
  ...
C ヘッダがタグと typedef を 別の 名前にしている場合、どちらの形式の 宣言も使えます。 (ただし、前方参照を行う必要がある場合には、前者の 形式しか使えません。)
typedef struct Foo {
  ...
} Foo;
cdef struct Foo:
  ...
ヘッダがタグと typedef を 同じ 名前にしている場合、 ctypedef を含めた宣言はできなくなります – とはいえ、 その必要はないでしょう

以降の例では、 Cython のコード中では、構造体の型を struct Foo でな く、単に Foo で参照しているので注意してください。

Python/C API ルーチンにアクセスする

cdef extern from 文特有の使い方の一つに、 Python/C API のルーチン へのアクセスがあります。例えば:

cdef extern from "Python.h":

    object PyString_FromStringAndSize(char *s, Py_ssize_t len)

のように書けば、ヌルバイトを含む Python 文字列を生成できます。

特殊型

Cython は Python/C API ルーチン用に Py_ssize_t という型名をあらか じめ定義しています。拡張モジュールを 64 ビットシステムと互換にしたけれ ば、常にこの型を指定してください。詳しくは Python/C API ルーチンのドキュ メントを参照してください。

Windows での呼び出し規約

Cython では、 __stdcall__cdecl といった呼び出し規約を使え ます。これらの呼び出し規約は、 Windows 用の C コンパイラと同じ構文を使 います。例えば:

cdef extern int __stdcall FrobnicateWindow(long handle)

cdef void (__stdcall *callback)(void *)

__stdcall を使った場合、同じシグネチャを持つ __stdcall 関数だ けが、その関数と互換性を持つとみなされます。

名前の衝突を解決する - C の名前指定

Cython モジュールは、モジュールレベルの名前空間を一つだけ持ち、その名 前空間で Python と C の両方の名前を管理しています。そのため、ある外部 の C の関数をラップして、それを同じ名前の Python の関数として Python ユーザから見えるようにしたい場合には、若干不便です。

Cython では、この問題を二つのやり方で解決できます。ベストな方法、とり わけラップ対象の関数がたくさんある場合の方法は、この節で後で述べる、 Cython モジュール間で宣言を共有する方法を使って、C の関数を別の名前空 間に押し込めてしまい、 extern で宣言するというものです。

もう一つの方法は、 C の名前指定 (name specification) を使って、C の関 数に対して、 Cython と C の間で別の名前を使わせるというものです。例え ば、 eject_tomato() という外部の関数をラップしたいとしましょう。 以下のように宣言すると:

cdef extern void c_eject_tomato "eject_tomato" (float speed)

C での関数名は eject_tomato ですが、 Cython モジュールでの名前は c_eject_tomato になります。そこで、以下のようにラップすれば:

def eject_tomato(speed):
    c_eject_tomato(speed)

モジュールからは、関数を eject_tomato のように参照できます。

この機能のもう一つの用途は、外部から取り込みたい名前が、図らずも Cython のキーワードとして使われている場合の解決です。例えば、 print という外部の関数を呼び出したいときに、Cython モジュールの中 では別の名前にできます。

関数と同様、変数、構造体、共用体、enum、構造体や共用体のメンバ、enumの 値にも、 C の名前指定を適用できます。例えば:

cdef extern int one "ein", two "zwei"
cdef extern float three "drei"

cdef struct spam "SPAM":
  int i "eye"

cdef enum surprise "inquisition":
  first "alpha"
  second "beta" = 3

Cython の宣言を C から使う

Cython には、Cython モジュール由来の C の宣言を他の C のコードで使える ようにする方法が二つあります。一つは public 宣言、もう一つは C API 宣 言です。

Note

単にある Cython モジュールを他の Cython モジュールから使いたいだけ の場合には、ここで説明するどちらの宣言も必要ありません – cimport 文を使うだけです。 Cython モジュール間で宣言を共有する を参照してください。

public 宣言

public キーワードを使えば、 Cython モジュールで定義した C の型、変数、関数を、 Cython モジュールにリンクする C のコードからもア クセスできます。:

cdef public struct Bunny: # 型の public 宣言
    int vorpalness

cdef public int spam # 変数の public 宣言

cdef public void grail(Bunny *): # 関数の public 宣言
    print "Ready the holy hand grenade"

Cython モジュール内で public 宣言を使うと、 Cython は モジュール名.h という名前のファイルを生成します。このファイル には、 public の宣言した内容と等価な C の宣言が、他の C コードから include できる形で入っています。

Cython を使って、 Python を C のプログラム中に埋め込みたい場合、 Py_Initialize()Py_Finalize() を忘れずに呼び出して下さい。 例えば、以下のスニペットでは、 modulename.h を include してい ます:

#include <Python.h>
#include "modulename.h"

void grail() {
    Py_Initialize();
    initmodulename();
    Bunny b;
    grail(b);
    Py_Finalize();
}

public 宣言した名前を利用する C のコードは、静的あるいは動的な形で、拡 張モジュールにリンクせねばなりません。

Cython のモジュールがパッケージ内にある場合、 .h ファイルの名前は、 モジュールのドット名表記になります。例えば、 foo.spam というモ ジュールからは、 foo.spam.h というファイルを生成します。

C API 宣言

C のコードから宣言を使えるようにするもう一つの方法は、 api キーワードを使うというものです。このキーワードは、 C の関数と拡張型の 宣言に対して使えます。 api 宣言を使うと、 Cython は モジュール名_api.h という名前のファイルを生成します。このファ イルには、関数や拡張型の宣言と、 import_モジュール名() という名 前の関数の宣言が入っています。

API 宣言した関数や拡張型を使う C のコードは、このヘッダを include して、 まず最初に import_モジュール名() 関数を呼びださねばなりません。 その後で、他の関数を呼び出したり、拡張型を使ったりできます。

モジュール名_api.h を include すれば、 Cython モジュール内で pubic 宣言した C の型や拡張型も使えるようになります:

# delorean.pyx
cdef public struct Vehicle:
    int speed
    float power

cdef api void activate(Vehicle *v):
    if v.speed >= 88 and v.power >= 1.21:
        print "Time travel achieved"
# marty.c
#include "delorean_api.h"

Vehicle car;

int main(int argc, char *argv[]) {
    import_delorean();
    car.speed = atoi(argv[1]);
    car.power = atof(argv[2]);
    activate(&car);
}

Note

Cython モジュール内の関数をエクスポートする場合は、その関数の引数、 戻り値に使っている型の定義も全て public に宣言する必要があります。 さもないと、Cython が生成するヘッダにそれらの型情報が入らないので、 ヘッダを使って C のファイルをコンパイルしようとするとエラーになり ます。

api メソッドを使う場合は、 モジュール名_api.h を include する C のコードをコンパイルする際に、拡張モジュールをリンクす る必要はありません。 Python の import 機構を使って、動的に接続を行うか らです。ただし、この方法でアクセスできるのは関数だけで、変数にはアクセ スできません。

同じ関数に対して、 publicapi の両方を使えば、 どちらの方法でも関数をエクスポートできます。例えば:

cdef public api void belt_and_braces():
    ...

ただし、C のファイルから include できるのは モジュール名.hモジュール名_api.h だけです。両方同時に include すると、二つの 定義が衝突してしまいます。

Cython モジュールがパッケージ内に入っている場合、ヘッダファイルや関数 の名前付け規則は、以下のようになります:

  • ヘッダファイルには、ドット名表記の完全なモジュールパスが使われます。
  • import_モジュール名 のモジュール名部分には、ドットをアンダースコ アに置き換えたモジュールパスが使われます。

例えば、 foo.spam というモジュールの API ヘッダファイルは foo.spam_api.h で、 import 用の関数は import_foo__spam() です。

複数の public/api 宣言をまとめる

publicapi が複数ある場合、一つの cdef ブロックにまとめて宣言できます。例えば:

cdef public api:
    void order_spam(int tons)
    char *get_lunch(float tomato_size)

この書き方は、 .pxd ファイル (Cython モジュール間で宣言を共有する 参照) の中で、 C, Cython, Python の全てについてモジュールの公開インタフェー スを定義するときに使えます。

GIL の取得とリリース

Cython には、 グローバルインタプリタロック Global Interpreter Lock, GIL) を取得したり、リリースしたりする機能があります。

この機能は、処理がブロックする可能性のある (外部の Cの) コードを呼び出 したり、C のコールバックの中で Python を使いたい場合に便利です。

GIL のリリース

コード中のある部分で GIL をリリースするには、 with nogil 文を使い ます。:

with nogil:
    <code to be executed with the GIL released>

GIL をリリースした状態のコードでは、いかなる形でも Python のオブジェク トを操作してはなりません。また、GIL を再取得しないまま、 Python のオブ ジェクトを操作するような外部の処理も一切呼び出してはなりません。現状で は、 Cython はこの制約をチェックしません。

GIL の取得

GIL を取得していない実行状態の C のコードから呼び出される C のコールバッ ク関数の中で、 Python オブジェクトを操作するには、まず GIL を取得せね ばなりません。関数の実行時に GIL を取得させるには、関数ヘッダに with gil を使います:

cdef void my_callback(void *data) with gil:
    ...

コールバックが他の Python でないスレッドから呼び出されている場合、 PyEval_InitThreads() を使って、まず GIL を初期化するといったケアが必要です。

モジュール内ですでに cython.parallel を使っていれば、 GIL の初期化のケアは不要です。

GIL の取得は、 with gil 文でもできます:

with gil:
    <execute this block with the GIL acquired>

関数を GIL なしのコーラブルとして宣言する

C の関数ヘッダや関数の型に nogil を指定すれば、 GIL がなく てもその関数を安全に呼び出せることを宣言できます:

cdef void my_gil_free_func(int spam) nogil:
    ...

Cython でこのような関数を定義する場合、関数の引数、ローカル変数、戻り 値に Python の値は使えません。また、関数内ではいかなる Python オブジェ クトの操作も行えず、GIL を再取得しないまま、 Python のオブジェクトを操 作するような外部の処理も一切呼び出してはなりません。現状、Cython はこ の制約を部分的にチェックしていますが、完全ではありません。将来的には、 より厳格なチェックを行う可能性があります。

Note

この宣言は、関数が GIL 無しで呼び出し可能なことを宣言してい るのであって、 GIL のリリースは行いません。

関数を gil 付きで宣言すると、シグネチャは暗黙のうちに nogil になります。