Python C API使用メモ  

PythonからCを使用したときのメモです。Cはあまり得意ではないのでおかしい部分もあるかも・・・
PythonからCを呼び出す基本的な方法は検索すればいくつか出てくると思いますので、ここでは取り上げません。PerlXSと比べてかなり楽だと思います。

参考

PythonからCのコードを呼び出すのは基本さえ理解すればそんなに難しくないので知っておいてもいいかも。

cStringIOを使う  

2011-07-05

どのぐらい速度にメリットがあるかは分かりませんが(Python側からもC側からも同じバッファにアクセスしまくりの時は役に立つ?)、cStringIOのアクセスを行ってみます。
cStringIOへのアクセスについては/usr/include/python/cStringIO.hに関数などは記述されていますが、ドキュメントは見つかりませんでしたので適当に使ってみました。

CでcStringIOオブジェクトへ書き込む場合
#include <Python.h>
#include <cStringIO.h>
PyObject* cstr_write_from_c(PyObject *self, PyObject *args) {
    PyObject *pycStr;
    PycString_IMPORT;
    if(!PyArg_ParseTuple(args, "O", &pycStr)) return NULL;
    if(PycStringIO_OutputCheck(pycStr)){
        PycStringIO->cwrite(pycStr, "Hello", 5);
    }else{
        printf("Arg1 is not cStringIO object.\n");
    }
    return Py_BuildValue("");
}

PycStringIOを使う時はPycString_IMPORTマクロで初期化する必要があることに注意してください。これをPythonからコール(引数はcStringIOオブジェクト)するとcStringIOに"Hello"と書き込まれます。その他のメソッドについてはcStringIO.hを参照してみてください。
双方から文字列へのアクセスするのにこんな方法もある、ってことで。

組み込み型の比較  

2011-07-05

引数の型をチェックするのは簡単ですが、引数に組み込み型を渡されたときのチェックに少しつまずいたのでメモ。動作はしましたが、正しいかどうかはちょっと微妙 [huh]

引数が組み込み型のstrかどうかをチェックします
#include <Python.h>
PyObject* cstr_chkstr(PyObject *self, PyObject *args) {
    PyObject *pyType;
    if(!PyArg_ParseTuple(args, "O", &pyType)) return NULL;
    if(PyType_IsSubtype((PyTypeObject*)pyType, &PyString_Type)){
        printf("Arg1 is str type.\n");
    }
    return Py_BuildValue("");
}

組み込み型はPyTypeObjectで表現され、型の比較はPyType_IsSubtype(PyTypeObject*, PyTypeObject*)で行われます。ここで使用しているPyString_TypeはPythonのstr型を表す実体です。

PythonファイルハンドラとFILE構造体(Windows)  

2011-07-15

FAQ級の問題みたいですが、ちゃんとした解説が見つからなかったのでメモしておきます。MinGW使いの人には常識なのかも。Linux環境下では発生しません。
Windows環境において、Python側で生成したファイルハンドラをPyFile_AsFile()でC側のファイルポインタに変換したときにセグメンテーションフォルトを起こすという問題があるようです。以下に例を示します。

mymod.c
#include <stdio.h>
#include <Python.h>

PyObject* mymod_hello(PyObject *self, PyObject *args) {
    PyObject *pyFh;
    FILE *fp;
    if(!PyArg_ParseTuple(args, "O", &pyFh)) return NULL;
    fp = PyFile_AsFile(pyFh);
    fprintf(fp, "Hello, world!\n");
    return Py_BuildValue("");
}

static PyMethodDef objmethods[] = {
    {"hello", mymod_hello, METH_VARARGS},
    {NULL},
};

void initmymod() {
    Py_InitModule("mymod", objmethods);
}
call.py
import mymod
fh = open("test.out", "wb")
mymod.hello(fh)

モジュールメソッドmymod.hello()をcall.pyから呼び出すという単純な例です。Pythonで書き出し用ファイルをopenし、mymod_hello()でファイルポインタに変換してHello, world!!を書き込むという手順です。
このmymod.cをMinGWのgccでビルドして、call.pyをWindows版Pythonで実行すると、セグメンテーションフォルトで落ちてしまいます。なお、cygwin環境で行った場合(実行もcygwinのPythonを使う)は問題ありません。
これは使用するPythonにリンクされているMSVCRT(ランタイム)ライブラリのバージョンと作成したmymodのそれが一致していないことが問題で、どうやらファイルポインタのバイナリ互換性がないことが原因らしいです。

通常MinGWでビルドした際はmsvcrt.dllがリンクされますが、併せて使用するPythonに一致したMSVCRTをリンクする必要があります。MinGW gccの非公式リリースのhttp://www.develer.com/oss/GccWinBinariesによると、

  • For Python 2.6, 2.7, 3.0 or 3.1, you want MSVCR90.DLL
  • For Python 2.4 or 2.5, you want MSVCR71.DLL
  • For Python 2.3 or older, you want MSVCRT.DLL

だそうです。従って、Python 2.7が対象の場合はMSVCR90.DLLをリンクすればいいことになります。以下に簡単なMakefileの例を示します(PythonはC:\Python27にインストールされていると仮定します)。

Makefile
CC=gcc
CFLAGS=-I/c/Python27/include
LDFLAGS=-L/c/Python27/libs
LIBS=-lpython27 -lmsvcr90
mymod.pyd: mymod.o
    $(CC) -shared $(LDFLAGS) -o mymod.pyd mymod.o $(LIBS)
mymod.o: mymod.c
    $(CC) $(CFLAGS) -o mymod.o -c mymod.c

ビルドで作成したmymod.pydがリンクしているDLLを見てみると、

$ objdump -p mymod.pyd | grep "DLL Name"
        DLL Name: python27.dll
        DLL Name: KERNEL32.dll
        DLL Name: msvcr90.dll
        DLL Name: msvcrt.dll

となれば成功です。このようにビルドしておくと想定通り動作してくれます [smile]。例では示していませんがos.popenによるパイプ処理も同様の問題があるので同じように対応可能です。

補足  

コメント