Cythonを32bit Windowsで使う

うっかりCのライブラリが世の中リリースされてたりされてなかったりする訳ですけども、
プロトタイプ的な何かを作ろうと思った時に、ヘタレはCで頑張ったり出来なかったりするのですね、主に僕とか。
いや、Cが全然書けないって言うつもりは無いけど、漢の子ならゴリゴリ書けるだろJKとか言われても困る訳で。
と言う訳で、Pythonとか使っちゃうのです。実は僕、Pythonの事良く知らないんだけどもさ。


本日のお題は、CythonをWindowsXPの32bit版かつVisual Studio 2008 Express上で使ってみますよ、と言う話。

Cythonをインストールした上で、Visual Studio経由でコンパイルする手順

Python2.7がインストール済みで、PATHに通っているものとします。


とりあえず僕がダウンロードしたのは、

  • Cython-0.13.zip

と言うアーカイブアーカイブを展開して、そのディレクトリでインストールコマンドを実行します。

C:\Cython-0.13>python setup.py install

その後、binディレクトリに入っているcython.batをpython.exeがあるディレクトリにコピーします。

C:\Cython-0.13>copy bin\cython.bat C:\Python27

次に、Visual Studioでプロジェクトを作ります。



何か色々余計なモノをつけてくれようとするのだけど、全部拒否ってDLLを生成する空のプロジェクトにします。



まず、新しいフィルタとしてCythonを追加します。ここは、分かり易ければ名前は何でも良いです。



次にプロジェクトのあるディレクトリをエクスプローラで開きCythonのHelloCython.pyxファイルを新規作成します。
Cythonのデフォルトの挙動では、ファイル名と同じモジュールをエクスポートしようとします。
Visual Studioのデフォルトの挙動ではプロジェクト名と同じDLLを出力しようとします。
両者にズレがあると余計な設定をする必要があるので、プロジェクト名と.pyxのファイル名は合わせておいて下さい。



そしてVisual Studioのプロジェクトに、当該ファイルを取り込みます。



最初の一回だけはこの謎のダイアログの後にウィザードが始まります。
うっかり「いいえ」を押してしまったら、Visual Studioを再起動しましょう。



上からテキトーに値を入力していきます。



そして、最後に「ビルド規則の追加」ボタンを押します。
更にダイアログが表示されますので必要な項目を入力していきます。



自分で入力する必要があるのは以下の項目です。

コマンドライン cython "$(InputPath)"
ファイル拡張子 pyx
出力 $(InputName).c
表示名 Cython
名前 Cython

コマンドラインの中では、半角スペースがプロジェクトのパスに含まれる可能性があるので、クオートします。
ファイル拡張子はpyxだけ入れると前の部分は勝手に追加してくれます。
出力は、Cythonの実行結果としてどんなファイルが出力されるのかなので、クオートしません。ファイル名であってパス名では無いのです。


HelloCython.pyxをVisual Studio上で開きチュートリアル的なコードを入力します。

cdef extern from "math.h":
    double sin(double)

cpdef double f(double x):
    return sin(x*x)

そして、HelloCython.pyxを選択し、コンテキストメニューからコンパイルを選択します。



Cythonの実行に成功すれば、HelloCython.cが生成されるので、これをプロジェクトに取り込みます。



最後にCコンパイラ及びリンカの設定をしていきますが、プリプロセッサの_DEBUGフラグを立ててビルドすると、pyconfig.hの動作によって、

  • python27_d.lib

が足りない等という事になるので、リリースビルドだけを行います。
リリースビルドでもデバッグシンボルである.pdbファイルは生成されるので、恐らくきっと実用上は問題ない筈です。
どうしてもデバッグビルドをしたい人は、pythonのビルドからやって下さい。



まずプロジェクトのプロパティにおいて「C/C++」の「全般」を選択し、「追加のインクルード ディレクトリ」を編集します。
Cythonでは、「Python.h」を参照しますので、pythonのインストールディレクトリにあるincludeディレクトリを指定します。
尚、空のプロジェクトで開始していますので、.cや.cpp等C系リソースを追加しないと、プロジェクトのプロパティに「C/C++」が出現しません。



ここでは、

C:\Python27\include

を指定しました。


次に、プロジェクトのプロパティにおいて「リンカ」の「全般」を選択し、「出力ファイル」を編集します。
ここは、最初から

$(OutDir)\$(ProjectName).dll

と入っていますが、

$(OutDir)\$(ProjectName).pyd

に出力されるファイルの拡張子を「.pyd」に変更します。
Windows版のPythonはモジュールの検索対象として「.dll」を使用せずに、「.pyd」を使用します。
何故この様な仕様になっているのか、理解に苦しみますが、特に興味も無いので、ハイハイと従います。
何か合理的な理由があるのでしょう。linuxMacだと標準的な.soでOKなのにさ。どうなっているのかと。


更に、「追加のライブラリ ディレクトリ」を編集します。



ここでは、

C:\Python27\libs

を指定しました。


これでついにビルドできます。ソリューションのビルドを選択しビルドを実行します。
ソリューションのReleaseディレクトリにビルドの実行結果が出力されます。



このディレクトリにHelloCythonTest.pyを新規作成します。
Pythonをインストールすると一緒にインストールされるIDLE等を使うと便利です。
こんなコードを書いて実行するとCの関数をPythonから呼び出せる事が確認出来ます。

from HelloCython import f

print(f(3.1))

これで、ある程度単純なCの関数ならば、簡単にPythonから呼出す事が出来るのが分りました。

Pythonの関数をCython経由でCのライブラリにコールバック関数として関数ポインタを渡す話

Cのライブラリを動作させる上で決して避けて通れない仕組みの一つが関数ポインタを引き渡してコールバックしてもらう事です。
CとPythonを接続する上で、既存のCライブラリで要求するコールバック関数を出来る事ならばPythonで実装したいものです。
Cythonではある程度のグルーコードを書くだけで、これを実現する事が出来ます。


僕が知っているタイプのコールバック系のコードを二つばかし用意してみました。
他にも何か色々ある様な気がしますが、直ぐに思いつくのはこれくらいって事で一つお願いします。

  • callback.h
#ifndef _CALLBACK_H_
#define _CALLBACK_H_

typedef int (*simple_callback)(int,int);

int simple(int a, int b, simple_callback callback);


typedef int (*withcfg_calback)(int,int,void*);

int withcfg(int a, int b, withcfg_calback callback, void *cfg);

#endif

その実装はこんな感じ。

  • callback.c
#include "callback.h"

int simple(int a, int b, simple_callback callback) {
	return (callback)(a,b);
}

int withcfg(int a, int b, withcfg_calback callback, void *cfg) {
	return (callback)(a,b,cfg);
}


それに対して、Cythonではこういう風にコードを書きます。

  • HelloCython.pyx
cdef extern from "callback.h":
    ctypedef int (*simple_callback)(int,int)
    int simple(int a, int b, simple_callback func)
    
    ctypedef int (*withcfg_calback)(int,int,void*)
    int withcfg(int a, int b, withcfg_calback func, void *cfg)

"""
use global variable callback
"""
cdef simple_callback_py
cdef int simple_callback_proxy(int a, int b):
    return simple_callback_py(a,b)

def simple_call(a, b, f):
    if(callable(f) == False):
        raise TypeError, ('%s is not callable' % f)
    
    global simple_callback_py
    simple_callback_py = f
    return simple(a,b,simple_callback_proxy)
    
"""
use local variable callback
"""
cdef int withcfg_calback_proxy(int a, int b, void *cfg):
    return (<object>cfg)(a,b)

def withcfg_call(a, b, f):
    if(callable(f) == False):
        raise TypeError, ('%s is not callable' % f)
    
    return withcfg(a, b, withcfg_calback_proxy, <void*>f)

さっき作ったプロジェクトのpyxファイルを編集しています。
globalを使う方がキャストが少ないので、僕としては好みです。
Pythonの機能は良く分らないのですけども、globalと言うキーワードは使うのがためらわれる感じがします。


呼出し側が好き勝手なポインタを設定出来るAPIと言うのは拡張性に優れている様な気がしないでもないですが、

void*がそこらじゅうにとっ散らかる上にキャストを連発せざるをえないので、どうも安全性が気がかりです。
うっかり不思議空間にポインタがすっ飛んでいったりしない様に気をつけないといけません。


細かいCythonの文法についてはマニュアルを参照して下さい。


これを動かすpythonのコードは例えば以下の様になります。

from HelloCython import *

def add(a,b):
    return a+b

print(simple_call(1,3,add))
print(withcfg_call(3,5,add))

以下の様に出力されますね。

4
8

簡単過ぎるせいなのか分らないですけども、Cythonのマニュアルには具体的なコードが見当たらず、少し苦労しました。
もっと良い方法を知っている方は教えて下さい。

Cから構造体のポインタをCython経由でPythonから触れる様にラップして渡す話

ある程度作りの良いCのライブラリではデータ構造をきちんと構造体として定義しています。
その値をそのまま一々コピーしていたのではメモリが幾らあっても足りないので、構造体のポインタを引き渡す事を行います。


しかしながら、CにおけるポインタをPythonの世界にそのまま持ってくる事は出来ません。
そこで、Cythonでは構造体のポインタをクラスでラッピングしてPythonに引き渡します。
具体的には、以下の様なCのコードがあったとします。

  • struct_pointer.h
#ifndef _STRUCT_POINTER_H_
#define _STRUCT_POINTER_H_

typedef struct _color {
	int red;
	int green;
	int blue;
} Color;

typedef void (*pick_color_callback)(Color*);

void pick_color(pick_color_callback callback);

#endif
  • struct_pointer.c
#include "struct_pointer.h"

void pick_color(pick_color_callback callback) {
	Color c;
	c.red = 3;
	c.green = 5;
	c.blue = 7;
	(callback)(&c);
}


これをラップするCythonのコードは以下の様になります。

"""
use structure pointer
"""
cdef extern from "struct_pointer.h":
    ctypedef struct Color:
        int red
        int green
        int blue
    ctypedef void (*pick_color_callback)(Color*)
    void pick_color(pick_color_callback callback)

cdef class PyColor:
    cdef Color* c
    
    property red:
        def __get__(self):
            return self.c.red
        
        def __set__(self, value):
            self.c.red = value
    
    property green:
        def __get__(self):
            return self.c.green
        
        def __set__(self, value):
            self.c.green = value

    property blue:
        def __get__(self):
            return self.c.blue
        
        def __set__(self, value):
            self.c.blue = value

cdef pick_color_callback_py
cdef void pick_color_proxy(Color* c):
    pc = PyColor()
    pc.c = c
    pick_color_callback_py(pc)

def pick_color_call(f):
    if(callable(f) == False):
        raise TypeError, ('%s is not callable' % f)

    global pick_color_callback_py
    pick_color_callback_py = f
    pick_color(pick_color_proxy)

Cython固有の構文を使っていますが、その詳細については、Cythonのドキュメントを確認して下さい。

これを動かすPythonのコードは以下の様になります。

def pick(c):
    print("red %(red)s / green %(green)s / blue %(blue)s"
          % {'red':c.red,'green':c.green,'blue':c.blue})
    c.red = 2
    print(c.red)

pick_color_call(pick)

以下の様に出力されますね。

red 3 / green 5 / blue 7
2

これもまたやってみれば、どうという話では無いものの、Cythonのドキュメントにはサンプルが無くどうしたものか色々と試行錯誤しました。


追記:

もしCythonのサンプルが必要でしたら http://www.infopile.jp/download/infopile-src-0.9.3.tgz が参考になるかも。中にある pymfc はPyRex製Win32/MFCラッパで、けっこういろいろやってます。

ありがとうございます。軽く見た感じ色々な使い方をしておりかなり参考になります。